--- /dev/null
+name: PPA Upload
+
+on:
+ push:
+ branches:
+ - master
+ - release/[0-9]+.[0-9]+
+ tags:
+ - v[0-9]+[0-9]+.[0-9]+
+
+jobs:
+ PPAUpload:
+ strategy:
+ fail-fast: true
+ matrix:
+ release:
+ - "20.04"
+ - "20.10"
+ - "devel"
+
+ runs-on: ubuntu-latest
+
+ env:
+ DEBFULLNAME: "Mir CI Bot"
+ DEBEMAIL: "mir-ci-bot@canonical.com"
+
+ steps:
+ - name: Import GPG key
+ uses: crazy-max/ghaction-import-gpg@v3
+ with:
+ gpg-private-key: ${{ secrets.MIR_BOT_GPG_PRIVATE_KEY }}
+
+ - name: Check out code
+ uses: actions/checkout@v2
+ with:
+ # Need full history for version determination
+ fetch-depth: 0
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get install --no-install-recommends --yes \
+ debhelper \
+ devscripts \
+ dput \
+ python3-launchpadlib
+
+ - name: Upload to PPA
+ env:
+ RELEASE: ${{ matrix.release }}
+ run: tools/ppa-upload.sh
--- /dev/null
+name: Spread
+
+on:
+ push:
+ branches:
+ - master
+ - staging
+ - trying
+ - release/[0-9]+.[0-9]+
+ tags:
+ - v[0-9]+[0-9]+.[0-9]+
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+
+jobs:
+ BuildAndTest:
+ strategy:
+ fail-fast: true
+ matrix:
+ spread-task:
+ - lxd:ubuntu-20.04:...:gcc
+ - lxd:ubuntu-21.04:...:gcc
+ - lxd:ubuntu-21.04:...:clang
+ - lxd:fedora-32:...:gcc
+ - lxd:fedora-34:...:gcc
+ - lxd:alpine-3.13:...:gcc
+ allow-fail: [false]
+ include:
+ - spread-task: lxd:ubuntu-devel:...:gcc
+ allow-fail: true
+ - spread-task: lxd:ubuntu-devel:...:clang
+ allow-fail: true
+ - spread-task: lxd:fedora-33:...:gcc
+ allow-fail: true
+
+ runs-on: ubuntu-latest
+
+ env:
+ DEBFULLNAME: "Mir CI Bot"
+ DEBEMAIL: "mir-ci-bot@canonical.com"
+
+ steps:
+ - name: Set up LXD
+ run: |
+ set -euo pipefail
+ sudo adduser $USER lxd
+ sudo apt-get remove --yes lxd
+ sudo snap install lxd
+ sg lxd -c 'lxd init --auto'
+
+ - name: Set up Spread
+ run: |
+ set -euo pipefail
+ curl -s -O 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
+
+ - name: Check out code
+ uses: actions/checkout@v2
+
+ - name: Run Spread task
+ env:
+ LXD_DIR: /var/snap/lxd/common/lxd
+ run: sg lxd -c 'snap run spread -v ${{ matrix.spread-task }}'
+ continue-on-error: ${{ matrix.allow-fail }}
+
+ # Report result to Bors on `staging` and `trying` branches.
+ Bors:
+ if: ${{ always() && github.event_name == 'push' && contains('refs/heads/staging refs/heads/trying', github.ref) }}
+ needs:
+ - BuildAndTest
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Report failure
+ if: ${{ needs.BuildAndTest.result != 'success' }}
+ run: exit 1
--- /dev/null
+.idea/
+/build/
+/cmake-build-debug/
--- /dev/null
+# Copyright © 2017 Canonical Ltd.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or 3 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authored by: Thomas Voss <thomas.voss@canonical.com>,
+# Alan Griffiths <alan@octopull.co.uk>,
+# Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+
+cmake_minimum_required(VERSION 3.5)
+
+cmake_policy(SET CMP0015 NEW)
+cmake_policy(SET CMP0022 NEW)
+
+project(wlcs VERSION 1.3.0)
+
+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_CXX_STANDARD 17)
+set(CMAKE_CXX_EXTENSIONS OFF)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -g -Werror -Wall -pedantic -Wextra -fPIC")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -g -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/
+)
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
--- /dev/null
+==============================
+Wayland Conformance Test Suite
+==============================
+
+``wlcs`` aspires to be a protocol-conformance-verifying test suite usable by
+Wayland compositor implementors.
+
+It is growing out of porting the existing Weston test suite to be run
+in `Mir's <https://github.com/MirServer/mir>`_ test suite,
+but it is designed to be usable by any compositor.
+
+There have been a number of previous attempts at a Wayland compositor test
+suite - the `Wayland Functional Integration Test Suite <https://github.com/intel/wayland-fits>`_,
+and the `Weston test suite <https://wayland.freedesktop.org/testing.html#heading_toc_j_3>`_
+are a couple of examples.
+
+What sets ``wlcs`` apart from the graveyard of existing test suites is its
+integration method.
+
+Previous test suites have used a Wayland protocol extension
+to interrogate the compositor state. This obviously requires that compositors
+implement that protocol, means that tests only have access to information
+provided by that protocol, and the IPC means that there isn't a canonical
+happens-before ordering available.
+
+Instead, ``wlcs`` relies on compositors providing an integration module,
+providing ``wlcs`` with API hooks to start a compositor, connect a client,
+move a window, and so on. This makes both writing aid debugging tests easier -
+the tests are (generally) in the same address space as the compositor, so there
+is a consistent global clock available, it's easier to poke around in
+compositor internals, and standard debugging tools can follow control flow from
+the test client to the compositor and back again.
+
+Usage
+-----
+
+In order for ``wlcs`` to test your compositor you need to provide an
+integration module. This needs to implement the interfaces found in
+``include/wlcs``. If your integration module is
+``awesome_compositor_wlcs_integration.so``, then running ``wlcs
+awesome_compositor_wlcs_integration.so`` will load and run all the tests.
+
+Development
+-----------
+
+``wlcs`` requires a small number of hooks into your compositor to drive the
+tests - it needs to know how to start and stop the mainloop, to get an fd to
+connect a client to, and so on.
+
+To access these hooks, ``wlcs`` looks for a symbol ``wlcs_server_integration``
+of type ``struct WlcsServerIntegration const`` (defined in
+``include/wlcs/display_server.h``) in your integration module.
--- /dev/null
+status = [ "Bors" ]
+block-labels = [ "no-merge" ]
+timeout-sec = 1800
+delete-merged-branches = true
+update_base_for_deletes = true
--- /dev/null
+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)
+ # Upstream GTestConfig.cmake doesn't provide GTEST_LIBRARY but GTEST_LIBRARIES
+ # CMake 3.20+ uses the upstream gtest config if possible.
+ if (NOT DEFINED GTEST_LIBRARY)
+ set(GTEST_LIBRARY ${GTEST_LIBRARIES})
+ endif()
+ string(REPLACE gtest gmock GMOCK_LIBRARY ${GTEST_LIBRARY})
+endif()
+
+set(GMOCK_LIBRARIES ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARY})
--- /dev/null
+wlcs (1.3.0-0ubuntu0) groovy; urgency=medium
+
+ * New upstream release. Notable changes:
+ + Check Cursor movement is not propagating to clients correctly when
+ zwp_relative_pointer_manager_v1 is enabled
+ + Support getting the latest serial sent from the compositor (useful for
+ popup grabs, and possibly other things in the future). Adding `wl_keyboard`
+ makes sure new surfaces have a serial once they're focused, and provides
+ access to keyboard focus
+ + Test that correct input event is used for interactive move
+ + Fix FindGtestGmock.cmake for new cmake (Fixes the build on Alpine Linux)
+ + Test that layer surfaces are correctly reconfigured
+ + Add tests for popup done event
+ + Test surfaces get enter/leave events
+ + Test version 2 and 3 features of zwlr_layer_shell_v1
+ + Destroy subsurfaces
+ + Show surface type names in paramaterized touch tests
+
+ -- Alan Griffiths <alan@octopull.co.uk> Thu, 27 May 2021 13:13:13 +0100
+
+wlcs (1.2.1-0ubuntu0) groovy; urgency=medium
+
+ * New upstream release. Notable changes:
+ + Fix cut & paste test
+
+ -- Alan Griffiths <alan@octopull.co.uk> Fri, 19 Feb 2021 11:11:11 +0000
+
+wlcs (1.2.0-0ubuntu0) groovy; urgency=medium
+
+ * New upstream release. Notable changes:
+ + Add tests for wlr_layer_shell_unstable_v1
+ + Build fixes for Musl libc; WLCS now builds on Alpine. Thanks, Luca Weiss
+ + More XDG Shell tests, particularly around protocol errors,
+ window-geometry, and input edge-cases.
+ + Add tests for wlr_foreign_toplevel_management_unstable_v1
+ + Many improvements to wl_subsurface tests. Notably this fixes a
+ misinterpretation of the protocol which lead to testing incorrect
+ behaviour.
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com> Mon, 31 Aug 2020 16:39:21 +1000
+
+wlcs (1.1.0-0ubuntu0) eoan; urgency=medium
+
+ * New upstream release. Relevant upstream changes:
+ + Document the compositor-integration version macros
+ + Add tests for the wl_output protocol
+ + More tests for XDG Shell, particularly around popups and window movement
+ + Add tests for wp_primary_selection_unstable_v1 protocol
+ + Add tests for gdk_primary_selection protocol
+ + Lots of build fixes for !Ubuntu systems. Thanks, Neal Gompa!
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com> Tue, 23 Jul 2019 10:37:52 +1000
+
+wlcs (1.0-0ubuntu0) disco; urgency=medium
+
+ * Initial release
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com> Tue, 08 Jan 2019 11:32:06 +1100
--- /dev/null
+Source: wlcs
+Priority: optional
+Maintainer: Christopher James Halse Rogers <raof@ubuntu.com>
+Build-Depends:
+ debhelper (>= 9),
+ cmake,
+ libboost-dev,
+ libgtest-dev,
+ libwayland-dev,
+ pkg-config,
+ google-mock
+Standards-Version: 4.1.3
+Section: devel
+Homepage: https://github.com/MirServer/wlcs
+Vcs-Browser: https://salsa.debian.org/mir-server-team/wlcs
+Vcs-Git: https://salsa.debian.org/mir-server-team/wlcs.git
+
+Package: wlcs
+Section: devel
+Architecture: any
+Multi-Arch: same
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Wayland Conformance Suite's
+ wlcs aspires to be a protocol-conformance-verifying test suite usable by
+ Wayland compositor implementations.
+ .
+ This package contains the headers necessary for a Wayland compositor to
+ provide the integration module needed to run wlcs tests, and the test
+ runner binary needed to run the tests against the compositor.
--- /dev/null
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: wlcs
+Source: https://github.com/MirServer/wlcs
+
+Files: *
+Copyright: 2017-2019 Canonical, Ltd
+License: GPL-3
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 3 as
+ published by the Free Software Foundation.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 3 can be found in the file
+ `/usr/share/common-licenses/GPL-3'.
+
+Files: tests/test_bad_buffer.cpp
+ tests/test_surface_events.cpp
+ xdg_popup_stable.cpp
+ xdg_popup_v6.cpp
+ xdg_surface_stable.cpp
+ xdg_surface_v6.cpp
+ xdg_toplevel_stable.cpp
+ xdg_toplevel_v6.cpp
+Copyright: 2012 Intel Corporation
+ 2013 Collabora, Ltd.
+ 2017-2018 Canonical Ltd.
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+ .
+ The above copyright notice and this permission notice (including the
+ next paragraph) shall be included in all copies or substantial
+ portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+
+Files: debian/*
+Copyright: 2019 Christopher James Halse Rogers <raof@ubuntu.com>
+License: GPL-2+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+
--- /dev/null
+#!/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 $@
--- /dev/null
+-----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-----
--- /dev/null
+# 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
--- /dev/null
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include <fcntl.h>
+#include <unordered_map>
+#include <deque>
+
+#include <wayland-client.h>
+#include <wayland-server.h>
+#include <mir/scene/surface_creation_parameters.h>
+#include <mir/input/composite_event_filter.h>
+#include <mir/input/device.h>
+#include <mir/input/input_device_hub.h>
+#include <mir/input/input_device_observer.h>
+#include <mir/input/cursor_listener.h>
+#include <mir/input/seat_observer.h>
+#include <mir/observer_registrar.h>
+#include <mir/executor.h>
+#include <atomic>
+#include <sys/eventfd.h>
+#include <mir/log.h>
+#include "display_server.h"
+#include "pointer.h"
+#include "touch.h"
+
+#include "mutex.h"
+
+#include "mir/fd.h"
+
+#include "mir/server.h"
+#include "mir/options/option.h"
+#include "mir_test_framework/async_server_runner.h"
+#include "mir_test_framework/headless_display_buffer_compositor_factory.h"
+
+#include "mir_test_framework/executable_path.h"
+#include "mir_test_framework/stub_server_platform_factory.h"
+#include "mir_test_framework/fake_input_device.h"
+#include "mir/input/device_capability.h"
+#include "mir/input/input_device_info.h"
+#include "mir/test/doubles/mock_gl.h"
+#include "mir/frontend/session.h"
+#include "mir/scene/session_listener.h"
+#include "mir/scene/surface.h"
+#include "mir/scene/session.h"
+#include "mir/test/signal.h"
+
+namespace mtf = mir_test_framework;
+namespace mi = mir::input;
+namespace mf = mir::frontend;
+using namespace std::chrono_literals;
+
+namespace std
+{
+// std::chrono::nanoseconds doesn't have a standard hash<> implementation?!
+template<>
+struct hash<::std::chrono::nanoseconds>
+{
+ typedef std::chrono::nanoseconds argument_type;
+ typedef size_t result_type;
+ result_type operator()(argument_type const& arg) const noexcept
+ {
+ /*
+ * The underlying type of std::chrono::nanoseconds is guaranteed to be an
+ * integer of at least 50-something bits, which will already have a perfectly
+ * functional std::hash<> implementation…
+ */
+ return std::hash<decltype(arg.count())>{}(arg.count());
+ }
+};
+}
+
+namespace
+{
+auto constexpr a_long_time = 5s;
+
+using ClientFd = int;
+
+class ResourceMapper : public mir::scene::SessionListener
+{
+public:
+ ResourceMapper()
+ : listeners{&this->state}
+ {
+ }
+
+ void starting(std::shared_ptr<mir::scene::Session> const&) override
+ {
+ }
+
+ void stopping(std::shared_ptr<mir::scene::Session> const&) override
+ {
+ }
+
+ void focused(std::shared_ptr<mir::scene::Session> const&) override
+ {
+ }
+
+ void unfocused() override
+ {
+ }
+
+ void surface_created(
+ mir::scene::Session&,
+ std::shared_ptr<mir::scene::Surface> const& surface) override
+ {
+ auto state_accessor = state.lock();
+ if (std::this_thread::get_id() == state_accessor->wayland_thread)
+ {
+ if (listeners.last_wl_window == nullptr)
+ {
+ BOOST_THROW_EXCEPTION((
+ std::runtime_error{
+ "Called Shell::create_surface() without first creating a wl_shell_surface?"}));
+ }
+
+ auto stream = surface->primary_buffer_stream();
+ auto wl_surface = state_accessor->stream_map.at(stream);
+
+ state_accessor->surface_map[wl_surface] = surface;
+ }
+ }
+
+ void destroying_surface(
+ mir::scene::Session&,
+ std::shared_ptr<mir::scene::Surface> const&) override
+ {
+ // TODO: Maybe delete from map?
+ }
+
+ void buffer_stream_created(
+ mir::scene::Session&,
+ std::shared_ptr<mir::frontend::BufferStream> const& stream) override
+ {
+ auto state_accessor = state.lock();
+ if (std::this_thread::get_id() == state_accessor->wayland_thread)
+ {
+ if (listeners.last_wl_surface == nullptr)
+ {
+ BOOST_THROW_EXCEPTION((
+ std::runtime_error{"BufferStream created without first constructing a wl_surface?"}));
+ }
+
+ state_accessor->stream_map[stream] = listeners.last_wl_surface;
+ listeners.last_wl_surface = nullptr;
+ }
+ }
+
+ void buffer_stream_destroyed(
+ mir::scene::Session&,
+ std::shared_ptr<mir::frontend::BufferStream> const& stream) override
+ {
+ state.lock()->stream_map.erase(stream);
+ }
+
+ void init(wl_display* display)
+ {
+ state.lock()->wayland_thread = std::this_thread::get_id();
+
+ listeners.client_listener.notify = &client_created;
+
+ wl_display_add_client_created_listener(display, &listeners.client_listener);
+ }
+
+ std::weak_ptr<mir::scene::Surface> surface_for_resource(wl_resource* surface)
+ {
+ if (strcmp(wl_resource_get_class(surface), "wl_surface") != 0)
+ {
+ BOOST_THROW_EXCEPTION((
+ std::logic_error{
+ std::string{"Expected a wl_surface, got: "} +
+ wl_resource_get_class(surface)
+ }));
+ }
+
+ auto state_accessor = state.lock();
+ return state_accessor->surface_map.at(surface);
+ }
+
+ wl_client* client_for_fd(int client_socket)
+ {
+ return listeners.state->lock()->client_session_map.at(client_socket);
+ }
+
+ void associate_client_socket(int client_socket)
+ {
+ auto state_accessor = state.wait_for(
+ [](State& state) { return static_cast<bool>(state.latest_client) ; },
+ std::chrono::seconds{30});
+ state_accessor->client_session_map[client_socket] = state_accessor->latest_client.value();
+ state_accessor->latest_client = {};
+ }
+
+private:
+ struct Listeners;
+ struct ResourceListener
+ {
+ ResourceListener(Listeners* const listeners)
+ : listeners{listeners}
+ {
+ }
+
+ wl_listener resource_listener;
+ Listeners* const listeners;
+ };
+ struct State
+ {
+ std::thread::id wayland_thread;
+ std::unordered_map<wl_resource*, std::weak_ptr<mir::scene::Surface>> surface_map;
+ std::unordered_map<std::shared_ptr<mir::frontend::BufferStream>, wl_resource*> stream_map;
+
+ std::experimental::optional<wl_client*> latest_client;
+ std::unordered_map<ClientFd, wl_client*> client_session_map;
+ std::unordered_map<wl_client*, ResourceListener> resource_listener;
+ };
+ wlcs::WaitableMutex<State> state;
+
+ struct Listeners
+ {
+ Listeners(wlcs::WaitableMutex<State>* const state)
+ : state{state}
+ {
+ }
+
+ wl_listener client_listener;
+
+ wl_resource* last_wl_surface{nullptr};
+ wl_resource* last_wl_window{nullptr};
+
+ wlcs::WaitableMutex<State>* const state;
+ } listeners;
+
+ static void resource_created(wl_listener* listener, void* ctx)
+ {
+ auto resource = static_cast<wl_resource*>(ctx);
+ ResourceListener* resource_listener;
+ resource_listener =
+ wl_container_of(listener, resource_listener, resource_listener);
+
+ bool const is_surface = strcmp(
+ wl_resource_get_class(resource),
+ "wl_surface") == 0;
+
+ bool const is_window = strcmp(
+ wl_resource_get_class(resource),
+ "wl_shell_surface") == 0 ||
+ strcmp(
+ wl_resource_get_class(resource),
+ "zxdg_surface_v6") == 0;
+
+ if (is_surface)
+ {
+ resource_listener->listeners->last_wl_surface = resource;
+ }
+ else if (is_window)
+ {
+ resource_listener->listeners->last_wl_window = resource;
+ }
+ }
+
+ static void client_created(wl_listener* listener, void* ctx)
+ {
+ auto client = static_cast<wl_client*>(ctx);
+ Listeners* listeners;
+ listeners =
+ wl_container_of(listener, listeners, client_listener);
+
+ wl_listener* resource_listener;
+ {
+ auto state_accessor = listeners->state->lock();
+ state_accessor->latest_client = client;
+ auto rl = state_accessor->resource_listener.emplace(client, listeners);
+ rl.first->second.resource_listener.notify = &resource_created;
+ resource_listener = &rl.first->second.resource_listener;
+ }
+ listeners->state->notify_all();
+
+ wl_client_add_resource_created_listener(client, resource_listener);
+ }
+};
+
+namespace
+{
+class WaylandExecutor : public mir::Executor
+{
+public:
+ void spawn (std::function<void()>&& work) override
+ {
+ {
+ std::lock_guard<std::recursive_mutex> lock{mutex};
+ workqueue.emplace_back(std::move(work));
+ }
+ if (auto err = eventfd_write(notify_fd, 1))
+ {
+ BOOST_THROW_EXCEPTION((std::system_error{err, std::system_category(), "eventfd_write failed to notify event loop"}));
+ }
+ }
+
+ /**
+ * Get an Executor which dispatches onto a wl_event_loop
+ *
+ * \note The executor may outlive the wl_event_loop, but no tasks will be dispatched
+ * after the wl_event_loop is destroyed.
+ *
+ * \param [in] loop The event loop to dispatch on
+ * \return An Executor that queues onto the wl_event_loop
+ */
+ static std::shared_ptr<mir::Executor> executor_for_event_loop(wl_event_loop* loop)
+ {
+ if (auto notifier = wl_event_loop_get_destroy_listener(loop, &on_display_destruction))
+ {
+ DestructionShim* shim;
+ shim = wl_container_of(notifier, shim, destruction_listener);
+
+ return shim->executor;
+ }
+ else
+ {
+ auto const executor = std::shared_ptr<WaylandExecutor>{new WaylandExecutor{loop}};
+ auto shim = std::make_unique<DestructionShim>(executor);
+
+ shim->destruction_listener.notify = &on_display_destruction;
+ wl_event_loop_add_destroy_listener(loop, &(shim.release())->destruction_listener);
+
+ return executor;
+ }
+ }
+
+private:
+ WaylandExecutor(wl_event_loop* loop)
+ : notify_fd{eventfd(0, EFD_CLOEXEC | EFD_SEMAPHORE | EFD_NONBLOCK)},
+ notify_source{wl_event_loop_add_fd(loop, notify_fd, WL_EVENT_READABLE, &on_notify, this)}
+ {
+ if (notify_fd == mir::Fd::invalid)
+ {
+ BOOST_THROW_EXCEPTION((std::system_error{
+ errno,
+ std::system_category(),
+ "Failed to create IPC pause notification eventfd"}));
+ }
+ }
+
+ std::function<void()> get_work()
+ {
+ std::lock_guard<std::recursive_mutex> lock{mutex};
+ if (!workqueue.empty())
+ {
+ auto const work = std::move(workqueue.front());
+ workqueue.pop_front();
+ return work;
+ }
+
+ return {};
+ }
+
+ static int on_notify(int fd, uint32_t, void* data)
+ {
+ auto executor = static_cast<WaylandExecutor*>(data);
+
+ eventfd_t unused;
+ if (auto err = eventfd_read(fd, &unused))
+ {
+ mir::log(
+ mir::logging::Severity::error,
+ "wlcs-integration",
+ "eventfd_read failed to consume wakeup notification: %s (%i)",
+ strerror(err),
+ err);
+ }
+
+ std::unique_lock<std::recursive_mutex> lock{executor->mutex};
+ while (auto work = executor->get_work())
+ {
+ try
+ {
+ work();
+ }
+ catch(...)
+ {
+ mir::log(
+ mir::logging::Severity::critical,
+ "wlcs-integration",
+ std::current_exception(),
+ "Exception processing Wayland event loop work item");
+ }
+ }
+
+ return 0;
+ }
+
+ static void on_display_destruction(wl_listener* listener, void*)
+ {
+ DestructionShim* shim;
+ shim = wl_container_of(listener, shim, destruction_listener);
+
+ {
+ std::lock_guard<std::recursive_mutex> lock{shim->executor->mutex};
+ wl_event_source_remove(shim->executor->notify_source);
+ }
+ delete shim;
+ }
+
+ std::recursive_mutex mutex;
+ mir::Fd const notify_fd;
+ std::deque<std::function<void()>> workqueue;
+
+ wl_event_source* const notify_source;
+
+ struct DestructionShim
+ {
+ explicit DestructionShim(std::shared_ptr<WaylandExecutor> const& executor)
+ : executor{executor}
+ {
+ }
+
+ std::shared_ptr<WaylandExecutor> const executor;
+ wl_listener destruction_listener;
+ };
+ static_assert(
+ std::is_standard_layout<DestructionShim>::value,
+ "DestructionShim must be Standard Layout for wl_container_of to be defined behaviour");
+};
+}
+
+class InputEventListener;
+
+struct MirWlcsDisplayServer : mtf::AsyncServerRunner
+{
+ testing::NiceMock<mir::test::doubles::MockGL> mockgl;
+ std::shared_ptr<ResourceMapper> const resource_mapper{std::make_shared<ResourceMapper>()};
+ std::shared_ptr<InputEventListener> event_listener;
+ std::shared_ptr<mir::Executor> executor;
+ std::atomic<double> cursor_x{0}, cursor_y{0};
+
+ mir::test::Signal started;
+};
+
+class InputEventListener : public mir::input::SeatObserver
+{
+public:
+ InputEventListener(MirWlcsDisplayServer& runner)
+ : runner{runner}
+ {
+ }
+
+ std::shared_ptr<mir::test::Signal> expect_event_with_time(
+ std::chrono::nanoseconds event_time)
+ {
+ auto done_signal = std::make_shared<mir::test::Signal>();
+ expected_events.lock()->insert(std::make_pair(event_time, done_signal));
+ return done_signal;
+ }
+
+ void seat_add_device(uint64_t /*id*/) override
+ {
+ }
+
+ void seat_remove_device(uint64_t /*id*/) override
+ {
+ }
+
+ void seat_dispatch_event(std::shared_ptr<MirEvent const> const& event) override
+ {
+ auto iev = mir_event_get_input_event(event.get());
+ auto event_time = std::chrono::nanoseconds{mir_input_event_get_event_time(iev)};
+
+ auto expected_events_accessor = expected_events.lock();
+ if (expected_events_accessor->count(event_time))
+ {
+ expected_events_accessor->at(event_time)->raise();
+ expected_events_accessor->erase(event_time);
+ }
+ }
+
+ void seat_set_key_state(
+ uint64_t /*id*/,
+ std::vector<uint32_t> const& /*scan_codes*/) override
+ {
+ }
+
+ void seat_set_pointer_state(
+ uint64_t /*id*/,
+ unsigned /*buttons*/) override
+ {
+ }
+
+ void seat_set_cursor_position(
+ float cursor_x,
+ float cursor_y) override
+ {
+ runner.cursor_x = cursor_x;
+ runner.cursor_y = cursor_y;
+ }
+
+ void seat_set_confinement_region_called(
+ mir::geometry::Rectangles const& /*regions*/) override
+ {
+ }
+
+ void seat_reset_confinement_regions() override
+ {
+ }
+
+private:
+ wlcs::Mutex<std::unordered_map<std::chrono::nanoseconds, std::shared_ptr<mir::test::Signal>>> expected_events;
+ MirWlcsDisplayServer& runner;
+};
+
+template<typename T>
+void emit_mir_event(MirWlcsDisplayServer* runner,
+ mir::UniqueModulePtr<mir_test_framework::FakeInputDevice>& emitter,
+ T event)
+{
+ auto event_time = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch());
+
+ auto event_sent = runner->event_listener->expect_event_with_time(event_time);
+
+ emitter->emit_event(event.with_event_time(event_time));
+
+ EXPECT_THAT(event_sent->wait_for(a_long_time), testing::Eq(true)) << "fake event failed to go through";
+}
+}
+
+void wlcs_server_start(WlcsDisplayServer* server)
+{
+ auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+ runner->start_server();
+
+ runner->started.wait_for(a_long_time);
+}
+
+void wlcs_server_stop(WlcsDisplayServer* server)
+{
+ auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+ runner->stop_server();
+}
+
+
+WlcsDisplayServer* wlcs_create_server(int argc, char const** argv)
+{
+ auto runner = new MirWlcsDisplayServer;
+ runner->add_to_environment("MIR_SERVER_PLATFORM_GRAPHICS_LIB", mtf::server_platform("graphics-dummy.so").c_str());
+ runner->add_to_environment("MIR_SERVER_PLATFORM_INPUT_LIB", mtf::server_platform("input-stub.so").c_str());
+ runner->add_to_environment("MIR_SERVER_ENABLE_KEY_REPEAT", "false");
+ runner->add_to_environment("MIR_SERVER_NO_FILE", "");
+ runner->add_to_environment("MIR_SERVER_WAYLAND_SOCKET_NAME", "wlcs-tests");
+ runner->add_to_environment("WAYLAND_DISPLAY", "wlcs-tests");
+ runner->server.override_the_display_buffer_compositor_factory([]
+ {
+ return std::make_shared<mtf::HeadlessDisplayBufferCompositorFactory>();
+ });
+
+ runner->server.override_the_session_listener(
+ [runner]()
+ {
+ return runner->resource_mapper;
+ });
+
+ runner->event_listener = std::make_shared<InputEventListener>(*runner);
+
+ runner->server.set_command_line(argc, argv);
+
+ runner->server.add_init_callback(
+ [runner]()
+ {
+ runner->server.run_on_wayland_display(
+ [runner](auto wayland_display)
+ {
+ runner->resource_mapper->init(wayland_display);
+ runner->executor = WaylandExecutor::executor_for_event_loop(
+ wl_display_get_event_loop(wayland_display));
+
+ // Execute all observations on the Wayland event loop…
+ runner->server.the_seat_observer_registrar()->register_interest(
+ runner->event_listener,
+ *runner->executor);
+
+ runner->started.raise();
+ });
+ });
+
+ runner->server.wrap_cursor_listener(
+ [runner](auto const& wrappee)
+ {
+ class ListenerWrapper : public mir::input::CursorListener
+ {
+ public:
+ ListenerWrapper(
+ MirWlcsDisplayServer* runner,
+ std::shared_ptr<mir::input::CursorListener> const& wrapped)
+ : runner{runner},
+ wrapped{wrapped}
+ {
+ }
+
+ void cursor_moved_to(float abs_x, float abs_y) override
+ {
+ runner->cursor_x = abs_x;
+ runner->cursor_y = abs_y;
+ wrapped->cursor_moved_to(abs_x, abs_y);
+ }
+
+ private:
+ MirWlcsDisplayServer* const runner;
+ std::shared_ptr<mir::input::CursorListener> const wrapped;
+ };
+ return std::make_shared<ListenerWrapper>(runner, wrappee);
+ });
+
+ return reinterpret_cast<WlcsDisplayServer*>(runner);
+}
+
+void wlcs_destroy_server(WlcsDisplayServer* server)
+{
+ auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+ delete runner;
+}
+
+int wlcs_server_create_client_socket(WlcsDisplayServer* server)
+{
+ auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+ try
+ {
+ auto client_fd = fcntl(
+ runner->server.open_wayland_client_socket(),
+ F_DUPFD_CLOEXEC,
+ 3);
+
+ runner->resource_mapper->associate_client_socket(client_fd);
+
+ return client_fd;
+ }
+ catch (std::exception const&)
+ {
+ mir::log(
+ mir::logging::Severity::critical,
+ "wlcs-bindings",
+ std::current_exception(),
+ "Failed to create Wayland client socket");
+ }
+
+ return -1;
+}
+
+struct FakePointer
+{
+ decltype(mtf::add_fake_input_device(mi::InputDeviceInfo())) pointer;
+ MirWlcsDisplayServer* runner;
+};
+
+WlcsPointer* wlcs_server_create_pointer(WlcsDisplayServer* server)
+{
+ auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+ auto constexpr uid = "mouse-uid";
+
+ class DeviceObserver : public mir::input::InputDeviceObserver
+ {
+ public:
+ DeviceObserver(std::shared_ptr<mir::test::Signal> const& done)
+ : done{done}
+ {
+ }
+
+ void device_added(std::shared_ptr<mir::input::Device> const& device) override
+ {
+ if (device->unique_id() == uid)
+ seen_device = true;
+ }
+
+ void device_changed(std::shared_ptr<mir::input::Device> const&) override
+ {
+ }
+
+ void device_removed(std::shared_ptr<mir::input::Device> const&) override
+ {
+ }
+
+ void changes_complete() override
+ {
+ if (seen_device)
+ done->raise();
+ }
+
+ private:
+ std::shared_ptr<mir::test::Signal> const done;
+ bool seen_device{false};
+ };
+
+ auto mouse_added = std::make_shared<mir::test::Signal>();
+ auto observer = std::make_shared<DeviceObserver>(mouse_added);
+ runner->server.the_input_device_hub()->add_observer(observer);
+
+
+ auto fake_mouse = mtf::add_fake_input_device(
+ mi::InputDeviceInfo{"mouse", uid, mi::DeviceCapability::pointer});
+
+ mouse_added->wait_for(a_long_time);
+ runner->executor->spawn([observer=std::move(observer), the_input_device_hub=runner->server.the_input_device_hub()]
+ { the_input_device_hub->remove_observer(observer); });
+
+ auto fake_pointer = new FakePointer;
+ fake_pointer->runner = runner;
+ fake_pointer->pointer = std::move(fake_mouse);
+
+ return reinterpret_cast<WlcsPointer*>(fake_pointer);
+}
+
+void wlcs_destroy_pointer(WlcsPointer* pointer)
+{
+ delete reinterpret_cast<FakePointer*>(pointer);
+}
+
+void wlcs_pointer_move_relative(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y)
+{
+ auto device = reinterpret_cast<FakePointer*>(pointer);
+
+ auto event = mir::input::synthesis::a_pointer_event()
+ .with_movement(wl_fixed_to_int(x), wl_fixed_to_int(y));
+
+ emit_mir_event(device->runner, device->pointer, event);
+}
+
+void wlcs_pointer_move_absolute(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y)
+{
+ auto device = reinterpret_cast<FakePointer*>(pointer);
+
+ auto rel_x = wl_fixed_to_double(x) - device->runner->cursor_x;
+ auto rel_y = wl_fixed_to_double(y) - device->runner->cursor_y;
+
+ wlcs_pointer_move_relative(pointer, wl_fixed_from_double(rel_x), wl_fixed_from_double(rel_y));
+}
+
+void wlcs_pointer_button_down(WlcsPointer* pointer, int button)
+{
+ auto device = reinterpret_cast<FakePointer*>(pointer);
+
+ auto event = mir::input::synthesis::a_button_down_event()
+ .of_button(button);
+
+ emit_mir_event(device->runner, device->pointer, event);
+}
+
+void wlcs_pointer_button_up(WlcsPointer* pointer, int button)
+{
+ auto device = reinterpret_cast<FakePointer*>(pointer);
+
+ auto event = mir::input::synthesis::a_button_up_event()
+ .of_button(button);
+
+ emit_mir_event(device->runner, device->pointer, event);
+}
+
+struct FakeTouch
+{
+ decltype(mtf::add_fake_input_device(mi::InputDeviceInfo())) touch;
+ int last_x{0}, last_y{0};
+ MirWlcsDisplayServer* runner;
+};
+
+WlcsTouch* wlcs_server_create_touch(WlcsDisplayServer* server)
+{
+ auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+ auto constexpr uid = "touch-uid";
+
+ class DeviceObserver : public mir::input::InputDeviceObserver
+ {
+ public:
+ DeviceObserver(std::shared_ptr<mir::test::Signal> const& done)
+ : done{done}
+ {
+ }
+
+ void device_added(std::shared_ptr<mir::input::Device> const& device) override
+ {
+ if (device->unique_id() == uid)
+ seen_device = true;
+ }
+
+ void device_changed(std::shared_ptr<mir::input::Device> const&) override
+ {
+ }
+
+ void device_removed(std::shared_ptr<mir::input::Device> const&) override
+ {
+ }
+
+ void changes_complete() override
+ {
+ if (seen_device)
+ done->raise();
+ }
+
+ private:
+ std::shared_ptr<mir::test::Signal> const done;
+ bool seen_device{false};
+ };
+
+ auto touch_added = std::make_shared<mir::test::Signal>();
+ auto observer = std::make_shared<DeviceObserver>(touch_added);
+ runner->server.the_input_device_hub()->add_observer(observer);
+
+ auto fake_touch_dev = mtf::add_fake_input_device(
+ mi::InputDeviceInfo{"touch", uid, mi::DeviceCapability::multitouch});
+
+ touch_added->wait_for(a_long_time);
+ runner->executor->spawn([observer=std::move(observer), the_input_device_hub=runner->server.the_input_device_hub()]
+ { the_input_device_hub->remove_observer(observer); });
+
+ auto fake_touch = new FakeTouch;
+ fake_touch->runner = runner;
+ fake_touch->touch = std::move(fake_touch_dev);
+
+ return reinterpret_cast<WlcsTouch*>(fake_touch);
+}
+
+void wlcs_destroy_touch(WlcsTouch* touch)
+{
+ delete reinterpret_cast<FakeTouch*>(touch);
+}
+
+void wlcs_touch_down(WlcsTouch* touch, int x, int y)
+{
+ auto device = reinterpret_cast<FakeTouch*>(touch);
+
+ device->last_x = x;
+ device->last_y = y;
+
+ auto event = mir::input::synthesis::a_touch_event()
+ .with_action(mir::input::synthesis::TouchParameters::Action::Tap)
+ .at_position({x, y});
+
+ emit_mir_event(device->runner, device->touch, event);
+}
+
+void wlcs_touch_move(WlcsTouch* touch, int x, int y)
+{
+ auto device = reinterpret_cast<FakeTouch*>(touch);
+
+ device->last_x = x;
+ device->last_y = y;
+
+ auto event = mir::input::synthesis::a_touch_event()
+ .with_action(mir::input::synthesis::TouchParameters::Action::Move)
+ .at_position({x, y});
+
+ emit_mir_event(device->runner, device->touch, event);
+}
+
+void wlcs_touch_up(WlcsTouch* touch)
+{
+ auto device = reinterpret_cast<FakeTouch*>(touch);
+
+ auto event = mir::input::synthesis::a_touch_event()
+ .with_action(mir::input::synthesis::TouchParameters::Action::Release)
+ .at_position({device->last_x, device->last_y});
+
+ emit_mir_event(device->runner, device->touch, event);
+}
+
+void wlcs_server_position_window_absolute(WlcsDisplayServer* server, wl_display* client, wl_surface* surface, int x, int y)
+{
+ auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+ try
+ {
+ auto const fd = wl_display_get_fd(client);
+ auto const client = runner->resource_mapper->client_for_fd(fd);
+ auto const id = wl_proxy_get_id(reinterpret_cast<wl_proxy*>(surface));
+
+ auto resource = wl_client_get_object(client, id);
+
+ auto mir_surface = runner->resource_mapper->surface_for_resource(resource);
+
+ if (auto live_surface = mir_surface.lock())
+ {
+ live_surface->move_to(mir::geometry::Point{x, y});
+ }
+ else
+ {
+ abort();
+ // TODO: log? Error handling?
+ }
+ }
+ catch(std::out_of_range const&)
+ {
+ abort();
+ // TODO: Error handling.
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_ACTIVE_LISTENERS_H
+#define WLCS_ACTIVE_LISTENERS_H
+
+#include <mutex>
+#include <set>
+
+namespace wlcs
+{
+class ActiveListeners
+{
+public:
+ void add(void* listener)
+ {
+ std::lock_guard<decltype(mutex)> lock{mutex};
+ listeners.insert(listener);
+ }
+
+ void del(void* listener)
+ {
+ std::lock_guard<decltype(mutex)> lock{mutex};
+ listeners.erase(listener);
+ }
+
+ bool includes(void* listener) const
+ {
+ std::lock_guard<decltype(mutex)> lock{mutex};
+ return listeners.find(listener) != end(listeners);
+ }
+
+private:
+ std::mutex mutable mutex;
+ std::set<void*> listeners;
+};
+}
+
+#endif //WLCS_ACTIVE_LISTENERS_H
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_DATA_DEVICE_H
+#define WLCS_DATA_DEVICE_H
+
+#include "active_listeners.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <wayland-client.h>
+
+#include <memory>
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(wl_data_device_manager)
+
+class DataSource
+{
+public:
+ DataSource() = default;
+
+ explicit DataSource(struct wl_data_source* ds) : self{ds, deleter} {}
+
+ operator struct wl_data_source*() const { return self.get(); }
+
+ void reset() { self.reset(); }
+
+ void reset(struct wl_data_source* ds) { self.reset(ds, deleter); }
+
+ friend void wl_data_source_destroy(DataSource const&) = delete;
+
+private:
+ static void deleter(struct wl_data_source* ds) { wl_data_source_destroy(ds); }
+
+ std::shared_ptr<struct wl_data_source> self;
+};
+
+struct DataDeviceListener
+{
+ DataDeviceListener(struct wl_data_device* data_device)
+ {
+ active_listeners.add(this);
+ wl_data_device_add_listener(data_device, &thunks, this);
+ }
+
+ virtual ~DataDeviceListener() { active_listeners.del(this); }
+
+ DataDeviceListener(DataDeviceListener const&) = delete;
+ DataDeviceListener& operator=(DataDeviceListener const&) = delete;
+
+ virtual void data_offer(
+ struct wl_data_device* wl_data_device,
+ struct wl_data_offer* id);
+
+ virtual void enter(
+ struct wl_data_device* wl_data_device,
+ uint32_t serial,
+ struct wl_surface* surface,
+ wl_fixed_t x,
+ wl_fixed_t y,
+ struct wl_data_offer* id);
+
+ virtual void leave(struct wl_data_device* wl_data_device);
+
+ virtual void motion(
+ struct wl_data_device* wl_data_device,
+ uint32_t time,
+ wl_fixed_t x,
+ wl_fixed_t y);
+
+ virtual void drop(struct wl_data_device* wl_data_device);
+
+ virtual void selection(
+ struct wl_data_device* wl_data_device,
+ struct wl_data_offer* id);
+
+private:
+ static void data_offer(
+ void* data,
+ struct wl_data_device* wl_data_device,
+ struct wl_data_offer* id);
+
+ static void enter(
+ void* data,
+ struct wl_data_device* wl_data_device,
+ uint32_t serial,
+ struct wl_surface* surface,
+ wl_fixed_t x,
+ wl_fixed_t y,
+ struct wl_data_offer* id);
+
+ static void leave(void* data, struct wl_data_device* wl_data_device);
+
+ static void motion(
+ void* data,
+ struct wl_data_device* wl_data_device,
+ uint32_t time,
+ wl_fixed_t x,
+ wl_fixed_t y);
+
+ static void drop(void* data, struct wl_data_device* wl_data_device);
+
+ static void selection(
+ void* data,
+ struct wl_data_device* wl_data_device,
+ struct wl_data_offer* id);
+
+
+ static ActiveListeners active_listeners;
+ constexpr static wl_data_device_listener thunks =
+ {
+ &data_offer,
+ &enter,
+ &leave,
+ &motion,
+ &drop,
+ &selection
+ };
+};
+
+struct DataOfferListener
+{
+ DataOfferListener() { active_listeners.add(this); }
+ virtual ~DataOfferListener() { active_listeners.del(this); }
+
+ DataOfferListener(DataOfferListener const&) = delete;
+ DataOfferListener& operator=(DataOfferListener const&) = delete;
+
+ void listen_to(struct wl_data_offer* data_offer)
+ {
+ wl_data_offer_add_listener(data_offer, &thunks, this);
+ }
+
+ virtual void offer(struct wl_data_offer* data_offer, char const* mime_type);
+
+ virtual void source_actions(struct wl_data_offer* data_offer, uint32_t dnd_actions);
+
+ virtual void action(struct wl_data_offer* data_offer, uint32_t dnd_action);
+
+private:
+ static void offer(void* data, struct wl_data_offer* data_offer, char const* mime_type);
+
+ static void source_actions(void* data, struct wl_data_offer* data_offer, uint32_t dnd_actions);
+
+ static void action(void* data, struct wl_data_offer* data_offer, uint32_t dnd_action);
+
+ static ActiveListeners active_listeners;
+ constexpr static wl_data_offer_listener thunks =
+ {
+ &offer,
+ &source_actions,
+ &action
+ };
+};
+
+class DataDevice
+{
+public:
+ DataDevice() = default;
+
+ explicit DataDevice(struct wl_data_device* dd) : self{dd, deleter} {}
+
+ operator struct wl_data_device*() const { return self.get(); }
+
+ void reset() { self.reset(); }
+
+ void reset(struct wl_data_device* dd) { self.reset(dd, deleter); }
+
+ friend void wl_data_device_destroy(DataDevice const&) = delete;
+
+private:
+ static void deleter(struct wl_data_device* dd) { wl_data_device_destroy(dd); }
+
+ std::shared_ptr<struct wl_data_device> self;
+};
+}
+
+#endif //WLCS_DATA_DEVICE_H
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include <chrono>
+#include <ostream>
+#include <iomanip>
+
+namespace std
+{
+namespace chrono
+{
+/// GTest helper to pretty-print time-points
+template<typename Clock>
+void PrintTo(time_point<Clock> const& time, ostream* os)
+{
+ auto remainder = time.time_since_epoch();
+ auto const hours = duration_cast<chrono::hours>(remainder);
+ remainder -= hours;
+ auto const minutes = duration_cast<chrono::minutes>(remainder);
+ remainder -= minutes;
+ auto const seconds = duration_cast<chrono::seconds>(remainder);
+ remainder -= seconds;
+ auto const nsec = duration_cast<chrono::nanoseconds>(remainder);
+ (*os)
+ << hours.count()
+ << ":" << setw(2) << setfill('0') << minutes.count()
+ << ":" << setw(2) << setfill('0') << seconds.count()
+ << "." << setw(9) << setfill('0') << nsec.count();
+}
+}
+}
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_PRIMARY_SELECTION_H
+#define WLCS_PRIMARY_SELECTION_H
+
+#include "generated/gtk-primary-selection-client.h"
+
+#include "active_listeners.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <memory>
+#include <mutex>
+#include <set>
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(gtk_primary_selection_device_manager)
+
+ class GtkPrimarySelectionSource
+ {
+ public:
+ using WrappedType = gtk_primary_selection_source;
+
+ GtkPrimarySelectionSource() = default;
+
+ explicit GtkPrimarySelectionSource(gtk_primary_selection_device_manager* manager) :
+ self{gtk_primary_selection_device_manager_create_source(manager), deleter} {}
+
+ operator WrappedType*() const { return self.get(); }
+
+ void reset() { self.reset(); }
+
+ void reset(WrappedType* source) { self.reset(source, deleter); }
+
+ friend void gtk_primary_selection_source_destroy(GtkPrimarySelectionSource const&) = delete;
+
+ private:
+ static void deleter(WrappedType* source) { gtk_primary_selection_source_destroy(source); }
+
+ std::shared_ptr<WrappedType> self;
+ };
+
+
+ class GtkPrimarySelectionDevice
+ {
+ public:
+ using WrappedType = gtk_primary_selection_device;
+
+ GtkPrimarySelectionDevice() = default;
+
+ GtkPrimarySelectionDevice(gtk_primary_selection_device_manager* manager, wl_seat* seat) :
+ self{gtk_primary_selection_device_manager_get_device(manager, seat), deleter} {}
+
+ operator WrappedType*() const { return self.get(); }
+
+ void reset() { self.reset(); }
+
+ void reset(WrappedType* device) { self.reset(device, deleter); }
+
+ friend void gtk_primary_selection_device_destroy(GtkPrimarySelectionDevice const&) = delete;
+
+ private:
+ static void deleter(WrappedType* device) { gtk_primary_selection_device_destroy(device); }
+
+ std::shared_ptr<WrappedType> self;
+ };
+
+ struct GtkPrimarySelectionDeviceListener
+ {
+ GtkPrimarySelectionDeviceListener(gtk_primary_selection_device* device)
+ {
+ active_listeners.add(this);
+ gtk_primary_selection_device_add_listener(device, &thunks, this);
+ }
+
+ virtual ~GtkPrimarySelectionDeviceListener() { active_listeners.del(this); }
+
+ GtkPrimarySelectionDeviceListener(GtkPrimarySelectionDeviceListener const&) = delete;
+ GtkPrimarySelectionDeviceListener& operator=(GtkPrimarySelectionDeviceListener const&) = delete;
+
+ virtual void data_offer(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer);
+
+ virtual void selection(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer);
+
+ private:
+ static void data_offer(void* data, gtk_primary_selection_device* device, gtk_primary_selection_offer* offer);
+
+ static void selection(void* data, gtk_primary_selection_device* device, gtk_primary_selection_offer* offer);
+
+ static ActiveListeners active_listeners;
+ constexpr static gtk_primary_selection_device_listener thunks =
+ {
+ &data_offer,
+ &selection
+ };
+ };
+
+ struct GtkPrimarySelectionOfferListener
+ {
+ GtkPrimarySelectionOfferListener() { active_listeners.add(this); }
+ virtual ~GtkPrimarySelectionOfferListener() { active_listeners.del(this); }
+
+ GtkPrimarySelectionOfferListener(GtkPrimarySelectionOfferListener const&) = delete;
+ GtkPrimarySelectionOfferListener& operator=(GtkPrimarySelectionOfferListener const&) = delete;
+
+ void listen_to(gtk_primary_selection_offer* offer)
+ {
+ gtk_primary_selection_offer_add_listener(offer, &thunks, this);
+ }
+
+ virtual void offer(gtk_primary_selection_offer* offer, const char* mime_type);
+
+ private:
+ static void offer(void* data, gtk_primary_selection_offer* offer, const char* mime_type);
+
+ static ActiveListeners active_listeners;
+ constexpr static gtk_primary_selection_offer_listener thunks = { &offer, };
+ };
+
+ struct GtkPrimarySelectionSourceListener
+ {
+ explicit GtkPrimarySelectionSourceListener(GtkPrimarySelectionSource const& source)
+ {
+ active_listeners.add(this);
+ gtk_primary_selection_source_add_listener(source, &thunks, this);
+ }
+
+ virtual ~GtkPrimarySelectionSourceListener() { active_listeners.del(this); }
+
+ GtkPrimarySelectionSourceListener(GtkPrimarySelectionSourceListener const&) = delete;
+ GtkPrimarySelectionSourceListener& operator=(GtkPrimarySelectionSourceListener const&) = delete;
+
+ virtual void send(gtk_primary_selection_source* source, const char* mime_type, int32_t fd);
+ virtual void cancelled(gtk_primary_selection_source* source);
+
+ private:
+ static void send(void* data, gtk_primary_selection_source* source, const char* mime_type, int32_t fd);
+ static void cancelled(void* data, gtk_primary_selection_source* source);
+
+ static ActiveListeners active_listeners;
+ constexpr static gtk_primary_selection_source_listener thunks = { &send, &cancelled };
+ };
+}
+
+#endif //WLCS_PRIMARY_SELECTION_H
--- /dev/null
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_HELPERS_H_
+#define WLCS_HELPERS_H_
+
+#include <cstddef>
+#include <memory>
+
+struct WlcsServerIntegration;
+
+namespace wlcs
+{
+namespace helpers
+{
+int create_anonymous_file(size_t size);
+
+void set_command_line(int argc, char const** argv);
+
+int get_argc();
+char const** get_argv();
+
+void set_entry_point(std::shared_ptr<WlcsServerIntegration const> const& entry_point);
+
+std::shared_ptr<WlcsServerIntegration const> get_test_hooks();
+}
+}
+
+#endif //WLCS_HELPERS_H_
--- /dev/null
+/*
+ * Copyright © 2017-2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_IN_PROCESS_SERVER_H_
+#define WLCS_IN_PROCESS_SERVER_H_
+
+#include "generated/wayland-client.h"
+#include "generated/xdg-shell-unstable-v6-client.h"
+#include "generated/xdg-shell-client.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <functional>
+#include <experimental/optional>
+#include <unordered_map>
+#include <chrono>
+
+#include "shared_library.h"
+#include "wl_handle.h"
+
+#include <wayland-client.h>
+
+struct WlcsPointer;
+struct WlcsTouch;
+struct WlcsServerIntegration;
+
+struct zwlr_layer_shell_v1;
+
+namespace wlcs
+{
+class VersionSpecifier;
+
+WLCS_CREATE_INTERFACE_DESCRIPTOR(wl_surface)
+WLCS_CREATE_INTERFACE_DESCRIPTOR(wl_subsurface)
+
+/* We need a manually-specified descriptor for wl_output,
+ * as wl_output only has a destructor for version >= 3
+ */
+namespace
+{
+void send_release_if_supported(wl_output* to_destroy)
+{
+ if (wl_output_get_version(to_destroy) >= WL_OUTPUT_RELEASE_SINCE_VERSION)
+ {
+ wl_output_release(to_destroy);
+ }
+ else
+ {
+ wl_output_destroy(to_destroy);
+ }
+}
+}
+template<>
+struct WlInterfaceDescriptor<wl_output>
+{
+ static constexpr bool const has_specialisation = true;
+ static constexpr wl_interface const* const interface = &wl_output_interface;
+ static constexpr void (* const destructor)(wl_output*) = &send_release_if_supported;
+};
+
+class Pointer
+{
+public:
+ ~Pointer();
+ Pointer(Pointer&&);
+
+ void move_to(int x, int y);
+ void move_by(int dx, int dy);
+
+ void button_down(int button);
+ void button_up(int button);
+ void click(int button);
+
+ void left_button_down();
+ void left_button_up();
+ void left_click();
+
+private:
+ friend class Server;
+ template<typename Proxy>
+ Pointer(
+ WlcsPointer* raw_device,
+ std::shared_ptr<Proxy> const& proxy,
+ std::shared_ptr<void const> const& keep_dso_loaded);
+
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+class Touch
+{
+public:
+ ~Touch();
+ Touch(Touch&&);
+
+ void down_at(int x, int y);
+ void move_to(int x, int y);
+ void up();
+
+private:
+ friend class Server;
+ template<typename Proxy>
+ Touch(
+ WlcsTouch* raw_device,
+ std::shared_ptr<Proxy> const& proxy,
+ std::shared_ptr<void const> const& keep_dso_loaded);
+
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+class Surface;
+
+class Server
+{
+public:
+ Server(
+ std::shared_ptr<WlcsServerIntegration const> const& module,
+ int argc,
+ char const** argv);
+ ~Server();
+
+ int create_client_socket();
+
+ Pointer create_pointer();
+ Touch create_touch();
+
+ void move_surface_to(Surface& surface, int x, int y);
+
+ void start();
+ void stop();
+
+ std::shared_ptr<const std::unordered_map<std::string, uint32_t>> supported_extensions();
+private:
+ class Impl;
+ std::unique_ptr<Impl> const impl;
+};
+
+class Client;
+
+class Surface
+{
+public:
+ explicit Surface(Client& client);
+ virtual ~Surface();
+
+ Surface(Surface&& other);
+
+ operator wl_surface*() const;
+
+ void attach_buffer(int width, int height);
+ void add_frame_callback(std::function<void(int)> const& on_frame);
+ void attach_visible_buffer(int width, int height);
+ void run_on_destruction(std::function<void()> callback);
+
+ Client& owner() const;
+ auto current_outputs() -> std::set<wl_output*> const&;
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+class Subsurface: public Surface
+{
+public:
+ static Subsurface create_visible(Surface& parent, int x, int y, int width, int height);
+
+ Subsurface(Surface& parent);
+ Subsurface(Subsurface &&);
+ ~Subsurface();
+
+ operator wl_subsurface*() const;
+
+ Surface& parent() const;
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+class ShmBuffer
+{
+public:
+ ShmBuffer(Client& client, int width, int height);
+ ~ShmBuffer();
+
+ ShmBuffer(ShmBuffer&& other);
+
+ operator wl_buffer*() const;
+
+ void add_release_listener(std::function<bool()> const &on_release);
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+struct OutputState
+{
+ OutputState(wl_output* output)
+ : output{output}
+ {
+ }
+
+ wl_output* output;
+ std::experimental::optional<std::pair<int, int>> geometry_position;
+ std::experimental::optional<std::pair<int, int>> mode_size;
+ std::experimental::optional<int> scale;
+};
+
+class Client
+{
+public:
+ Client(Server& server);
+ ~Client();
+
+ // Accessors
+
+ operator wl_display*() const;
+
+ wl_compositor* compositor() const;
+ wl_subcompositor* subcompositor() const;
+ wl_shm* shm() const;
+ wl_seat* seat() const;
+
+ void run_on_destruction(std::function<void()> callback);
+
+ ShmBuffer const& create_buffer(int width, int height);
+ Surface create_wl_shell_surface(int width, int height);
+ Surface create_xdg_shell_v6_surface(int width, int height);
+ Surface create_xdg_shell_stable_surface(int width, int height);
+ Surface create_visible_surface(int width, int height);
+
+ size_t output_count() const;
+ OutputState output_state(size_t index) const;
+ void add_output_done_notifier(size_t index, std::function<void()> const& notifier);
+
+ wl_shell* shell() const;
+ wl_pointer* the_pointer() const;
+ zxdg_shell_v6* xdg_shell_v6() const;
+ xdg_wm_base* xdg_shell_stable() const;
+ wl_surface* keyboard_focused_window() const;
+ wl_surface* window_under_cursor() const;
+ wl_surface* touched_window() const;
+ std::pair<wl_fixed_t, wl_fixed_t> pointer_position() const;
+ std::pair<wl_fixed_t, wl_fixed_t> touch_position() const;
+ std::experimental::optional<uint32_t> latest_serial() const;
+
+ using PointerEnterNotifier =
+ std::function<bool(wl_surface*, wl_fixed_t x, wl_fixed_t y)>;
+ using PointerLeaveNotifier =
+ std::function<bool(wl_surface*)>;
+ using PointerMotionNotifier =
+ std::function<bool(wl_fixed_t x, wl_fixed_t y)>;
+ using PointerButtonNotifier =
+ std::function<bool(uint32_t serial, uint32_t button, bool is_down)>;
+ void add_pointer_enter_notification(PointerEnterNotifier const& on_enter);
+ void add_pointer_leave_notification(PointerLeaveNotifier const& on_leave);
+ void add_pointer_motion_notification(PointerMotionNotifier const& on_motion);
+ void add_pointer_button_notification(PointerButtonNotifier const& on_button);
+
+ void dispatch_until(
+ std::function<bool()> const& predicate,
+ std::chrono::seconds timeout = std::chrono::seconds{10});
+
+ template<typename WlType>
+ auto bind_if_supported(VersionSpecifier const& version) -> WlHandle<WlType>
+ {
+ return wrap_wl_object(
+ static_cast<WlType*>(bind_if_supported(*WlInterfaceDescriptor<WlType>::interface, version)));
+ }
+
+ auto bind_if_supported(wl_interface const& interface, VersionSpecifier const& version) const -> void*;
+
+ void roundtrip();
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> const impl;
+};
+
+class ProtocolError : public std::system_error
+{
+public:
+ ProtocolError(wl_interface const* interface, uint32_t code)
+ : std::system_error(EPROTO, std::system_category(), "Wayland protocol error"),
+ interface_{interface},
+ code_{code},
+ message{
+ std::string{"Wayland protocol error: "} +
+ std::to_string(code) +
+ " on interface " +
+ interface_->name +
+ " v" +
+ std::to_string(interface->version)}
+ {
+ }
+
+ char const* what() const noexcept override
+ {
+ return message.c_str();
+ }
+
+ uint32_t error_code() const
+ {
+ return code_;
+ }
+
+ wl_interface const* interface() const
+ {
+ return interface_;
+ }
+
+private:
+ wl_interface const* const interface_;
+ uint32_t const code_;
+ std::string const message;
+};
+
+class ExtensionExpectedlyNotSupported : public std::runtime_error
+{
+public:
+ ExtensionExpectedlyNotSupported(char const* extension, VersionSpecifier const& version);
+};
+
+class Timeout : public std::runtime_error
+{
+public:
+ explicit Timeout(char const* message);
+};
+
+class InProcessServer : public testing::Test
+{
+public:
+ InProcessServer();
+
+ void SetUp() override;
+ void TearDown() override;
+
+ Server& the_server();
+private:
+ Server server;
+};
+
+class StartedInProcessServer : public InProcessServer
+{
+public:
+ StartedInProcessServer() { InProcessServer::SetUp(); }
+ ~StartedInProcessServer() { InProcessServer::TearDown(); }
+
+ void SetUp() override {}
+ void TearDown() override {}
+};
+}
+
+#endif //WLCS_IN_PROCESS_SERVER_H_
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_INPUT_METHOD_H_
+#define WLCS_INPUT_METHOD_H_
+
+#include <string>
+#include <vector>
+#include "in_process_server.h"
+
+namespace wlcs
+{
+
+struct InputMethod
+{
+ InputMethod(std::string const& name) : name{name} {}
+ virtual ~InputMethod() = default;
+
+ struct Device
+ {
+ virtual ~Device() = default;
+ /// Can only be called while decice is up
+ virtual void down_at(std::pair<int, int> position) = 0;
+ /// Can only be called while device is down
+ virtual void move_to(std::pair<int, int> position) = 0;
+ /// Can only be called while device is down
+ virtual void up() = 0;
+ };
+
+ virtual auto create_device(wlcs::Server& server) -> std::unique_ptr<Device> = 0;
+ virtual auto current_surface(wlcs::Client const& client) -> wl_surface* = 0;
+ virtual auto position_on_surface(wlcs::Client const& client) -> std::pair<int, int> = 0;
+
+ static auto all_input_methods() -> std::vector<std::shared_ptr<InputMethod>>;
+
+ std::string const name;
+};
+
+struct PointerInputMethod : InputMethod
+{
+ PointerInputMethod() : InputMethod{"pointer"} {}
+
+ struct Pointer;
+
+ auto create_device(wlcs::Server& server) -> std::unique_ptr<Device> override;
+ auto current_surface(wlcs::Client const& client) -> wl_surface* override;
+ auto position_on_surface(wlcs::Client const& client) -> std::pair<int, int> override;
+};
+
+struct TouchInputMethod : InputMethod
+{
+ TouchInputMethod() : InputMethod{"touch"} {}
+
+ struct Touch;
+
+ auto create_device(wlcs::Server& server) -> std::unique_ptr<Device> override;
+ auto current_surface(wlcs::Client const& client) -> wl_surface* override;
+ auto position_on_surface(wlcs::Client const& client) -> std::pair<int, int> override;
+};
+
+}
+
+namespace std
+{
+std::ostream& operator<<(std::ostream& out, std::shared_ptr<wlcs::InputMethod> const& param);
+}
+
+#endif // WLCS_INPUT_METHOD_H_
--- /dev/null
+/*
+ * Copyright © 2018-2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_LAYER_SHELL_V1_H
+#define WLCS_LAYER_SHELL_V1_H
+
+#include "in_process_server.h"
+#include "wl_handle.h"
+
+// Because _someone_ *cough*ddevault*cough* thought it would be a great idea to name an argument "namespace"
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wkeyword-macro"
+#endif
+#define namespace _namespace
+#include "generated/wlr-layer-shell-unstable-v1-client.h"
+#undef namespace
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+namespace wlcs
+{
+
+// We need to use a custom destructor as .destroyed() changed in v3
+namespace
+{
+void send_destroy_if_supported(zwlr_layer_shell_v1* to_destroy)
+{
+ if (zwlr_layer_shell_v1_get_version(to_destroy) >= ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION)
+ {
+ zwlr_layer_shell_v1_destroy(to_destroy);
+ }
+ else
+ {
+ wl_proxy_destroy(reinterpret_cast<wl_proxy*>(to_destroy));
+ }
+}
+}
+template<>
+struct WlInterfaceDescriptor<zwlr_layer_shell_v1>
+{
+ static constexpr bool const has_specialisation = true;
+ static constexpr wl_interface const* const interface = &zwlr_layer_shell_v1_interface;
+ static constexpr void (* const destructor)(zwlr_layer_shell_v1*) = &send_destroy_if_supported;
+};
+
+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; }
+ operator zwlr_layer_shell_v1*() const { return layer_shell; }
+
+ void dispatch_until_configure();
+ auto last_width() const -> int { return last_width_; }
+ auto last_height() const -> int { return last_height_; }
+
+private:
+ wlcs::Client& client;
+ WlHandle<zwlr_layer_shell_v1> layer_shell;
+ WlHandle<zwlr_layer_surface_v1> layer_surface;
+ int last_width_ = -1;
+ int last_height_ = -1;
+ int configure_count = 0;
+};
+
+}
+
+#endif // WLCS_LAYER_SHELL_V1_H
--- /dev/null
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2 or 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_MUTEX_H_
+#define WLCS_MUTEX_H_
+
+#include <mutex>
+#include <condition_variable>
+#include <boost/throw_exception.hpp>
+
+namespace wlcs
+{
+/**
+ * Smart-pointer-esque accessor for Mutex<> protected data.
+ *
+ * Ensures exclusive access to the referenced data.
+ *
+ * \tparam Guarded Type of data guarded by the mutex.
+ */
+template<typename Guarded>
+class MutexGuard
+{
+public:
+ MutexGuard(std::unique_lock<std::mutex>&& lock, Guarded& value)
+ : value{value},
+ lock{std::move(lock)}
+ {
+ }
+ MutexGuard(MutexGuard&& from) = default;
+ ~MutexGuard() noexcept(false)
+ {
+ if (lock.owns_lock())
+ {
+ lock.unlock();
+ }
+ }
+
+ Guarded& operator*()
+ {
+ return value;
+ }
+ Guarded* operator->()
+ {
+ return &value;
+ }
+private:
+ Guarded& value;
+ std::unique_lock<std::mutex> lock;
+};
+
+/**
+ * A data-locking mutex
+ *
+ * This is a mutex which owns the data it guards, and can give out a
+ * smart-pointer-esque lock to lock and access it.
+ *
+ * \tparam Guarded The type of data guarded by the mutex
+ */
+template<typename Guarded>
+class Mutex
+{
+public:
+ Mutex() = default;
+ Mutex(Guarded&& initial_value)
+ : value{std::move(initial_value)}
+ {
+ }
+
+ Mutex(Mutex const&) = delete;
+ Mutex& operator=(Mutex const&) = delete;
+
+ /**
+ * Lock the mutex and return an accessor for the protected data.
+ *
+ * \return A smart-pointer-esque accessor for the contained data.
+ * While code has access to the MutexGuard it is guaranteed to have exclusive
+ * access to the contained data.
+ */
+ MutexGuard<Guarded> lock()
+ {
+ return MutexGuard<Guarded>{std::unique_lock<std::mutex>{mutex}, value};
+ }
+
+protected:
+ std::mutex mutex;
+ Guarded value;
+};
+
+template<typename Guarded>
+class WaitableMutex : public Mutex<Guarded>
+{
+public:
+ using Mutex<Guarded>::Mutex;
+
+ template<typename Predicate, typename Rep, typename Period>
+ MutexGuard<Guarded> wait_for(Predicate predicate, std::chrono::duration<Rep, Period> timeout)
+ {
+ std::unique_lock<std::mutex> lock{this->mutex};
+ if (!notifier.wait_for(lock, timeout, [this, &predicate]() { return predicate(this->value); }))
+ {
+ BOOST_THROW_EXCEPTION((std::runtime_error{"Notification timeout"}));
+ }
+ return MutexGuard<Guarded>{std::move(lock), this->value};
+ }
+
+ void notify_all()
+ {
+ notifier.notify_all();
+ }
+private:
+ std::condition_variable notifier;
+};
+
+
+}
+
+#endif //WLCS_MUTEX_H_
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_POINTER_CONSTRAINTS_UNSTABLE_V1_H
+#define WLCS_POINTER_CONSTRAINTS_UNSTABLE_V1_H
+
+#include "generated/pointer-constraints-unstable-v1-client.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+struct wl_pointer;
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_pointer_constraints_v1)
+
+class Client;
+
+class ZwpPointerConstraintsV1
+{
+public:
+ ZwpPointerConstraintsV1(Client& client);
+ ~ZwpPointerConstraintsV1();
+
+ operator zwp_pointer_constraints_v1*() const;
+
+ friend void zwp_pointer_constraints_v1_destroy(ZwpPointerConstraintsV1 const&) = delete;
+
+private:
+ WlHandle<zwp_pointer_constraints_v1> const manager;
+};
+
+class ZwpConfinedPointerV1
+{
+public:
+ ZwpConfinedPointerV1(
+ ZwpPointerConstraintsV1& manager,
+ wl_surface* surface,
+ wl_pointer* pointer,
+ wl_region* region,
+ uint32_t lifetime);
+
+ ~ZwpConfinedPointerV1();
+
+ MOCK_METHOD0(confined, void());
+ MOCK_METHOD0(unconfined, void());
+
+ operator zwp_confined_pointer_v1*() const;
+
+ friend void zwp_confined_pointer_v1_destroy(ZwpConfinedPointerV1 const&) = delete;
+
+private:
+ zwp_confined_pointer_v1* const relative_pointer;
+ uint32_t const version;
+ static zwp_confined_pointer_v1_listener const listener;
+};
+
+class ZwpLockedPointerV1
+{
+public:
+ ZwpLockedPointerV1(
+ ZwpPointerConstraintsV1& manager,
+ wl_surface* surface,
+ wl_pointer* pointer,
+ wl_region* region,
+ uint32_t lifetime);
+
+ ~ZwpLockedPointerV1();
+
+ MOCK_METHOD0(locked, void());
+ MOCK_METHOD0(unlocked, void());
+
+ operator zwp_locked_pointer_v1*() const;
+
+ friend void zwp_locked_pointer_v1_destroy(ZwpLockedPointerV1 const&) = delete;
+
+private:
+ zwp_locked_pointer_v1* const locked_pointer;
+ uint32_t const version;
+ static zwp_locked_pointer_v1_listener const listener;
+};
+
+}
+
+#endif //WLCS_POINTER_CONSTRAINTS_UNSTABLE_V1_H
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_PRIMARY_SELECTION_H
+#define WLCS_PRIMARY_SELECTION_H
+
+#include "generated/primary-selection-unstable-v1-client.h"
+
+#include "active_listeners.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <memory>
+#include <mutex>
+#include <set>
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_primary_selection_device_manager_v1)
+
+ class PrimarySelectionSource
+ {
+ public:
+ using WrappedType = zwp_primary_selection_source_v1;
+
+ PrimarySelectionSource() = default;
+
+ explicit PrimarySelectionSource(zwp_primary_selection_device_manager_v1* manager) :
+ self{zwp_primary_selection_device_manager_v1_create_source(manager), deleter} {}
+
+ operator WrappedType*() const { return self.get(); }
+
+ void reset() { self.reset(); }
+
+ void reset(WrappedType* source) { self.reset(source, deleter); }
+
+ friend void zwp_primary_selection_source_v1_destroy(PrimarySelectionSource const&) = delete;
+
+ private:
+ static void deleter(WrappedType* source) { zwp_primary_selection_source_v1_destroy(source); }
+
+ std::shared_ptr<WrappedType> self;
+ };
+
+
+ class PrimarySelectionDevice
+ {
+ public:
+ using WrappedType = zwp_primary_selection_device_v1;
+
+ PrimarySelectionDevice() = default;
+
+ PrimarySelectionDevice(zwp_primary_selection_device_manager_v1* manager, wl_seat* seat) :
+ self{zwp_primary_selection_device_manager_v1_get_device(manager, seat), deleter} {}
+
+ operator WrappedType*() const { return self.get(); }
+
+ void reset() { self.reset(); }
+
+ void reset(WrappedType* device) { self.reset(device, deleter); }
+
+ friend void zwp_primary_selection_device_v1_destroy(PrimarySelectionDevice const&) = delete;
+
+ private:
+ static void deleter(WrappedType* device) { zwp_primary_selection_device_v1_destroy(device); }
+
+ std::shared_ptr<WrappedType> self;
+ };
+
+ struct PrimarySelectionDeviceListener
+ {
+ PrimarySelectionDeviceListener(zwp_primary_selection_device_v1* device)
+ {
+ active_listeners.add(this);
+ zwp_primary_selection_device_v1_add_listener(device, &thunks, this);
+ }
+
+ virtual ~PrimarySelectionDeviceListener() { active_listeners.del(this); }
+
+ PrimarySelectionDeviceListener(PrimarySelectionDeviceListener const&) = delete;
+ PrimarySelectionDeviceListener& operator=(PrimarySelectionDeviceListener const&) = delete;
+
+ virtual void data_offer(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer);
+
+ virtual void selection(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer);
+
+ private:
+ static void data_offer(void* data, zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer);
+
+ static void selection(void* data, zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer);
+
+ static ActiveListeners active_listeners;
+ constexpr static zwp_primary_selection_device_v1_listener thunks =
+ {
+ &data_offer,
+ &selection
+ };
+ };
+
+ struct PrimarySelectionOfferListener
+ {
+ PrimarySelectionOfferListener() { active_listeners.add(this); }
+ virtual ~PrimarySelectionOfferListener() { active_listeners.del(this); }
+
+ PrimarySelectionOfferListener(PrimarySelectionOfferListener const&) = delete;
+ PrimarySelectionOfferListener& operator=(PrimarySelectionOfferListener const&) = delete;
+
+ void listen_to(zwp_primary_selection_offer_v1* offer)
+ {
+ zwp_primary_selection_offer_v1_add_listener(offer, &thunks, this);
+ }
+
+ virtual void offer(zwp_primary_selection_offer_v1* offer, const char* mime_type);
+
+ private:
+ static void offer(void* data, zwp_primary_selection_offer_v1* offer, const char* mime_type);
+
+ static ActiveListeners active_listeners;
+ constexpr static zwp_primary_selection_offer_v1_listener thunks = { &offer, };
+ };
+
+ struct PrimarySelectionSourceListener
+ {
+ explicit PrimarySelectionSourceListener(PrimarySelectionSource const& source)
+ {
+ active_listeners.add(this);
+ zwp_primary_selection_source_v1_add_listener(source, &thunks, this);
+ }
+
+ virtual ~PrimarySelectionSourceListener() { active_listeners.del(this); }
+
+ PrimarySelectionSourceListener(PrimarySelectionSourceListener const&) = delete;
+ PrimarySelectionSourceListener& operator=(PrimarySelectionSourceListener const&) = delete;
+
+ virtual void send(zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd);
+ virtual void cancelled(zwp_primary_selection_source_v1* source);
+
+ private:
+ static void send(void* data, zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd);
+ static void cancelled(void* data, zwp_primary_selection_source_v1* source);
+
+ static ActiveListeners active_listeners;
+ constexpr static zwp_primary_selection_source_v1_listener thunks = { &send, &cancelled };
+ };
+}
+
+#endif //WLCS_PRIMARY_SELECTION_H
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_RELATIVE_POINTER_UNSTABLE_V1_H
+#define WLCS_RELATIVE_POINTER_UNSTABLE_V1_H
+
+#include "generated/relative-pointer-unstable-v1-client.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+struct wl_pointer;
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_relative_pointer_manager_v1)
+
+class Client;
+
+class ZwpRelativePointerManagerV1
+{
+public:
+ ZwpRelativePointerManagerV1(Client& client);
+ ~ZwpRelativePointerManagerV1();
+
+ operator zwp_relative_pointer_manager_v1*() const;
+
+ friend void zwp_relative_pointer_manager_v1_destroy(ZwpRelativePointerManagerV1 const&) = delete;
+
+private:
+ WlHandle<zwp_relative_pointer_manager_v1> manager;
+};
+
+class ZwpRelativePointerV1
+{
+public:
+ ZwpRelativePointerV1(ZwpRelativePointerManagerV1& manager, wl_pointer* pointer);
+ ~ZwpRelativePointerV1();
+
+ MOCK_METHOD6(relative_motion,
+ void(uint32_t utime_hi, uint32_t utime_lo,
+ wl_fixed_t dx, wl_fixed_t dy,
+ wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel));
+
+ operator zwp_relative_pointer_v1*() const;
+
+ friend void zwp_relative_pointer_v1_destroy(ZwpRelativePointerV1 const&) = delete;
+
+private:
+ zwp_relative_pointer_v1* const relative_pointer;
+ uint32_t const version;
+ static zwp_relative_pointer_v1_listener const listener;
+};
+
+}
+
+#endif //WLCS_RELATIVE_POINTER_UNSTABLE_V1_H
--- /dev/null
+/*
+ * Copyright © 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2 or 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef MIR_SHARED_LIBRARY_H_
+#define MIR_SHARED_LIBRARY_H_
+
+#include <string>
+
+namespace wlcs
+{
+class SharedLibrary
+{
+public:
+ explicit SharedLibrary(char const* library_name);
+ explicit SharedLibrary(std::string const& library_name);
+ ~SharedLibrary() noexcept;
+
+ template<typename FunctionPtr>
+ FunctionPtr load_function(char const* function_name) const
+ {
+ FunctionPtr result{};
+ (void*&)result = load_symbol(function_name);
+ return result;
+ }
+
+ template<typename FunctionPtr>
+ FunctionPtr load_function(std::string const& function_name) const
+ {
+ return load_function<FunctionPtr>(function_name.c_str());
+ }
+private:
+ void* const so;
+ void* load_symbol(char const* function_name) const;
+ SharedLibrary(SharedLibrary const&) = delete;
+ SharedLibrary& operator=(SharedLibrary const&) = delete;
+};
+}
+
+
+#endif /* MIR_SHARED_LIBRARY_H_ */
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_SURFACE_BUILDER_H_
+#define WLCS_SURFACE_BUILDER_H_
+
+#include <string>
+#include <vector>
+#include "in_process_server.h"
+
+namespace wlcs
+{
+
+struct SurfaceBuilder
+{
+ SurfaceBuilder(std::string const& name) : name{name} {}
+ virtual ~SurfaceBuilder() = default;
+
+ virtual auto build(
+ wlcs::Server& server,
+ wlcs::Client& client,
+ std::pair<int, int> position,
+ std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> = 0;
+
+ std::string const name;
+
+ static auto all_surface_types() -> std::vector<std::shared_ptr<SurfaceBuilder>>;
+ static auto toplevel_surface_types() -> std::vector<std::shared_ptr<SurfaceBuilder>>;
+
+ static auto surface_builder_to_string(
+ testing::TestParamInfo<std::shared_ptr<SurfaceBuilder>> builder) -> std::string;
+};
+
+struct WlShellSurfaceBuilder : SurfaceBuilder
+{
+ WlShellSurfaceBuilder() : SurfaceBuilder{"wl_shell_surface"} {}
+
+ auto build(
+ wlcs::Server& server,
+ wlcs::Client& client,
+ std::pair<int, int> position,
+ std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> override;
+};
+
+struct XdgV6SurfaceBuilder : SurfaceBuilder
+{
+ XdgV6SurfaceBuilder() : SurfaceBuilder{"zxdg_surface_v6"} {}
+
+ auto build(
+ wlcs::Server& server,
+ wlcs::Client& client,
+ std::pair<int, int> position,
+ std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> override;
+};
+
+struct XdgStableSurfaceBuilder : SurfaceBuilder
+{
+ XdgStableSurfaceBuilder(int left_offset, int top_offset, int right_offset, int bottom_offset);
+
+ auto build(
+ wlcs::Server& server,
+ wlcs::Client& client,
+ std::pair<int, int> position,
+ std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> override;
+
+ int left_offset, top_offset, right_offset, bottom_offset;
+};
+
+struct SubsurfaceBuilder : SurfaceBuilder
+{
+ SubsurfaceBuilder(std::pair<int, int> offset);
+
+ auto build(
+ wlcs::Server& server,
+ wlcs::Client& client,
+ std::pair<int, int> position,
+ std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> override;
+
+ std::pair<int, int> offset;
+};
+
+// TODO: popup surfaces
+}
+
+namespace std
+{
+std::ostream& operator<<(std::ostream& out, std::shared_ptr<wlcs::SurfaceBuilder> const& param);
+}
+
+#endif // WLCS_SURFACE_BUILDER_H_
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_VERSION_SPECIFIER_H_
+#define WLCS_VERSION_SPECIFIER_H_
+
+#include <cstdint>
+#include <experimental/optional>
+
+namespace wlcs
+{
+class VersionSpecifier
+{
+public:
+ VersionSpecifier() = default;
+ virtual ~VersionSpecifier() = default;
+
+ virtual auto select_version(uint32_t max_version) const -> std::experimental::optional<uint32_t> = 0;
+ virtual auto describe() const -> std::string = 0;
+};
+
+class ExactlyVersion : public VersionSpecifier
+{
+public:
+ explicit ExactlyVersion(uint32_t version) noexcept;
+
+ auto select_version(uint32_t max_version) const -> std::experimental::optional<uint32_t> override;
+ auto describe() const -> std::string override;
+private:
+ uint32_t const version;
+};
+
+class AtLeastVersion : public VersionSpecifier
+{
+public:
+ explicit AtLeastVersion(uint32_t version) noexcept;
+
+ auto select_version(uint32_t max_version) const -> std::experimental::optional<uint32_t> override;
+ auto describe() const -> std::string override;
+private:
+ uint32_t const version;
+};
+
+extern VersionSpecifier const& AnyVersion;
+}
+
+#endif //WLCS_VERSION_SPECIFIER_H_
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ * William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_WL_PROXY_H_
+#define WLCS_WL_PROXY_H_
+
+#include "wl_interface_descriptor.h"
+#include "boost/throw_exception.hpp"
+#include "boost/current_function.hpp"
+
+#include <string>
+#include <stdexcept>
+
+struct wl_proxy;
+
+namespace wlcs
+{
+template<typename T>
+class WlHandle
+{
+public:
+ explicit WlHandle(T* const proxy)
+ : proxy{proxy},
+ owns_wl_object{true}
+ {
+ if (proxy == nullptr)
+ {
+ BOOST_THROW_EXCEPTION((std::logic_error{"Attempt to construct a WlHandle from null Wayland object"}));
+ }
+ }
+
+ ~WlHandle()
+ {
+ if (owns_wl_object)
+ {
+ (*WlInterfaceDescriptor<T>::destructor)(proxy);
+ }
+ }
+
+ WlHandle(WlHandle&& from)
+ : WlHandle(from.proxy)
+ {
+ from.owns_wl_object = false;
+ }
+
+ WlHandle(WlHandle const&) = delete;
+
+ auto operator=(WlHandle&&) -> WlHandle& = delete;
+ auto operator=(WlHandle const&) -> bool = delete;
+
+ operator T*() const
+ {
+ // This is a precondition failure, but as this is a test-suite let's be generous and make it fail hard and fast
+ if (!owns_wl_object)
+ {
+ std::abort();
+ }
+ return proxy;
+ }
+
+ auto wl_proxy() const -> struct wl_proxy*
+ {
+ // This is a precondition failure, but as this is a test-suite let's be generous and make it fail hard and fast
+ if (!owns_wl_object)
+ {
+ std::abort();
+ }
+ return static_cast<struct wl_proxy*>(proxy);
+ }
+
+private:
+ T* const proxy;
+ bool owns_wl_object;
+};
+
+template<typename WlType>
+auto wrap_wl_object(WlType* proxy) -> WlHandle<WlType>
+{
+ static_assert(
+ WlInterfaceDescriptor<WlType>::has_specialisation,
+ "Missing specialisation for WlInterfaceDescriptor");
+ return WlHandle<WlType>{proxy};
+}
+
+}
+
+#endif // WLCS_WL_PROXY_H_
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_WL_INTERFACE_DESCRIPTOR_H
+#define WLCS_WL_INTERFACE_DESCRIPTOR_H
+
+struct wl_interface;
+
+namespace wlcs
+{
+/***
+ * A specialisable struct containing the constants and types associated with a Wayland protocol
+ *
+ * \tparam WlType The base Wayland object type (eg; wl_surface, xdg_wm_base, etc)
+ */
+template<typename WlType>
+struct WlInterfaceDescriptor
+{
+ // Needed because apparently GCC < 10 can't compare a constexpr wl_interface const* const to nullptr in constexpr context?!
+ static constexpr bool const has_specialisation = false;
+ static constexpr wl_interface const* const interface = nullptr;
+ static constexpr void (* const destructor)(WlType*) = nullptr;
+};
+}
+
+/***
+ * Declare a specialisation of WlInterfaceDescriptor for \param name
+ *
+ * This will use the standard Wayland conventions of
+ * name - name_interface - name_destroy
+ * (eg: wl_surface - wl_surface_interface - wl_surface_destroy)
+ *
+ * If an interface requires special handling, a manual specialisation can be
+ * provided (for example, see wl_output handling in in_process_server.h)
+ */
+#define WLCS_CREATE_INTERFACE_DESCRIPTOR(name) \
+ template<> \
+ struct WlInterfaceDescriptor<name> \
+ { \
+ static constexpr bool const has_specialisation = true; \
+ static constexpr wl_interface const* const interface = &name##_interface; \
+ static constexpr void(* const destructor)(name*) = &name##_destroy; \
+ };
+
+
+#endif //WLCS_WL_INTERFACE_DESCRIPTOR_H
--- /dev/null
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+#ifndef WLCS_SERVER_H_
+#define WLCS_SERVER_H_
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct wl_surface wl_surface;
+typedef struct wl_display wl_display;
+typedef struct wl_event_loop wl_event_loop;
+
+typedef struct WlcsPointer WlcsPointer;
+typedef struct WlcsTouch WlcsTouch;
+
+/**
+ * Maximum version of WlcsIntegrationDescriptor this header provides a definition for
+ */
+#define WLCS_INTEGRATION_DESCRIPTOR_VERSION 1
+typedef struct WlcsExtensionDescriptor WlcsExtensionDescriptor;
+struct WlcsExtensionDescriptor
+{
+ /**
+ * Protocol name of extension (eg: wl_shell, xdg_shell, etc)
+ */
+ char const* name;
+ /**
+ * Maximum version of extension supported
+ */
+ uint32_t version;
+};
+
+typedef struct WlcsIntegrationDescriptor WlcsIntegrationDescriptor;
+struct WlcsIntegrationDescriptor
+{
+ uint32_t version; /**< Version of the struct this instance provides */
+
+ size_t num_extensions; /**< Length of the supported_extensions array */
+ /**
+ * Array of extension descriptions
+ *
+ * This must be an array of length num_extensions; that is, accesses from
+ * supported_extensions[0] to supported_extensions[num_extensions - 1]
+ * must be valid.
+ */
+ WlcsExtensionDescriptor const* supported_extensions;
+};
+
+/**
+ * Maximum version of WlcsDisplayServer this header provides a definition for
+ */
+#define WLCS_DISPLAY_SERVER_VERSION 3
+typedef struct WlcsDisplayServer WlcsDisplayServer;
+struct WlcsDisplayServer
+{
+ uint32_t version; /**< Version of the struct this instance provides */
+
+ /**
+ * Start the display server's mainloop.
+ *
+ * This should *not* block until the mainloop exits, which implies the mainloop
+ * will need to be run in a separate thread.
+ *
+ * This does not need to block until the display server is ready to process
+ * input, but the WlcsDisplayServer does need to be able to process other
+ * calls (notably create_client_socket) once this returns.
+ */
+ void (*start)(WlcsDisplayServer* server);
+
+ /**
+ * Stop the display server's mainloop.
+ *
+ * In contrast to the start hook, this *should* block until the server's mainloop
+ * has been torn down, so that it does not persist into later tests.
+ */
+ void (*stop)(WlcsDisplayServer* server);
+
+ /**
+ * Create a socket that can be connected to by wl_display_connect_fd
+ *
+ * \return A FD to a client Wayland socket. WLCS owns this fd, and will close
+ * it as necessary.
+ */
+ int (*create_client_socket)(WlcsDisplayServer* server);
+
+ /**
+ * Position a window in the compositor coördinate space
+ *
+ * \param client the (wayland-client-side) wl_client which owns the
+ * surface
+ * \param surface the (wayland-client-side) wl_surface*
+ * \param x x coördinate (in compositor-space pixels) to move the
+ * left of the window to
+ * \param y y coördinate (in compositor-space pixels) to move the
+ * top of the window to
+ */
+ void (*position_window_absolute)(WlcsDisplayServer* server, wl_display* client, wl_surface* surface, int x, int y);
+
+ /**
+ * Create a fake pointer device
+ */
+ WlcsPointer* (*create_pointer)(WlcsDisplayServer* server);
+
+ /**
+ * Create a mock touch object
+ */
+ WlcsTouch* (*create_touch)(WlcsDisplayServer* server);
+
+ /* Added in version 2 */
+ /**
+ * Describe the capabilities of this WlcsDisplayServer
+ *
+ * WLCS will use this description to skip tests that the display server
+ * is known to not support. For example, if the set of extensions
+ * described by the WlcsIntegrationDescriptor does not include "xdg_shell"
+ * then all XDG Shell tests will be skipped.
+ *
+ * Different WlcsDisplayServer instances may report different
+ * capabilities (for example, if command line options should influence
+ * the set of extensions exposed).
+ */
+ WlcsIntegrationDescriptor const* (*get_descriptor)(WlcsDisplayServer const* server);
+
+ /* Added in version 3 */
+ /**
+ * Start the display server's event loop, blocking the calling thread.
+ *
+ * When started in this way WLCS will proxy all requests to this mainloop.
+ * All calls to WLCS interfaces will be dispatched from the
+ * wlcs_event_dispatcher loop, so implementations are required to drive
+ * this loop from their own.
+ *
+ * \note This is an optional interface. An implementation must provide at
+ * least one of {start, start_on_this_thread}, but does not need to
+ * provide both. If both are provided, start is preferred.
+
+ * \param wlcs_event_dispatcher
+ */
+ void (*start_on_this_thread)(WlcsDisplayServer* server, wl_event_loop* wlcs_event_dispatcher);
+};
+
+/**
+ * Maximum version of WlcsServerIntegration this header provides a definition of
+ */
+#define WLCS_SERVER_INTEGRATION_VERSION 1
+typedef struct WlcsServerIntegration WlcsServerIntegration;
+struct WlcsServerIntegration
+{
+ uint32_t version; /**< Version of the struct this instance provides */
+
+ /**
+ * Create a WlcsDisplayServer instance
+ *
+ * This can do any setup necessary, but should not start the compositor's
+ * mainloop.
+ *
+ * \param argc Command line argument count (after wlcs-specific options
+ * have been stripped)
+ * \param argv Command line arguments (after wlcs-specific options have
+ * been stripped)
+ * \return
+ */
+ WlcsDisplayServer* (*create_server)(int argc, char const** argv);
+ void (*destroy_server)(WlcsDisplayServer* server);
+};
+
+/**
+ * Main WLCS entry point
+ *
+ * WLCS will load this symbol to discover the compositor integration
+ * hooks.
+ */
+extern WlcsServerIntegration const wlcs_server_integration;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //WLCS_SERVER_H_
--- /dev/null
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+#ifndef WLCS_POINTER_H_
+#define WLCS_POINTER_H_
+
+#include <wayland-client-core.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Maximum version of WlcsPointer this header provides a definition for
+ */
+#define WLCS_POINTER_VERSION 1
+
+typedef struct WlcsPointer WlcsPointer;
+/**
+ * An object to manipulate the server's pointer state
+ */
+struct WlcsPointer
+{
+ uint32_t version; /**< Version of the struct this instance provides */
+
+ /**
+ * Move the pointer to the specified location, in compositor coördinate space
+ */
+ void (*move_absolute)(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y);
+ /**
+ * Move the pointer by the specified amount, in compositor coördinates.
+ */
+ void (*move_relative)(WlcsPointer* pointer, wl_fixed_t dx, wl_fixed_t dy);
+
+ /**
+ * Generate a button-up event
+ *
+ * \param button Button code (as per wl_pointer. eg: BTN_LEFT)
+ */
+ void (*button_up)(WlcsPointer* pointer, int button);
+ /**
+ * Generate a button-down event
+ *
+ * \param button Button code (as per wl_pointer. eg: BTN_LEFT)
+ */
+ void (*button_down)(WlcsPointer* pointer, int button);
+
+ /**
+ * Destroy this pointer, freeing any resources.
+ */
+ void (*destroy)(WlcsPointer* pointer);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //WLCS_SERVER_H_
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+#ifndef WLCS_TOUCH_H_
+#define WLCS_TOUCH_H_
+
+#include <wayland-client-core.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Maximum version of WlcsTouch this header provides a definition for
+ */
+#define WLCS_TOUCH_VERSION 1
+
+typedef struct WlcsTouch WlcsTouch;
+struct WlcsTouch
+{
+ uint32_t version; /**< Version of the struct this instance provides */
+
+ void (*touch_down)(WlcsTouch* touch, wl_fixed_t x, wl_fixed_t y);
+ void (*touch_move)(WlcsTouch* touch, wl_fixed_t x, wl_fixed_t y);
+ void (*touch_up)(WlcsTouch* touch);
+
+ void (*destroy)(WlcsTouch* touch);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //WLCS_TOUCH_H_
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef XDG_OUTPUT_V1_H
+#define XDG_OUTPUT_V1_H
+
+#include "generated/wayland-client.h"
+#include "generated/xdg-output-unstable-v1-client.h"
+
+#include "in_process_server.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <memory>
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zxdg_output_manager_v1)
+
+class XdgOutputManagerV1
+{
+public:
+ XdgOutputManagerV1(Client& client);
+ ~XdgOutputManagerV1();
+
+ operator zxdg_output_manager_v1*() const;
+ auto client() const -> Client&;
+
+private:
+ struct Impl;
+ std::unique_ptr<Impl> const impl;
+};
+
+class XdgOutputV1
+{
+public:
+ struct State
+ {
+ std::experimental::optional<std::pair<int, int>> logical_position;
+ std::experimental::optional<std::pair<int, int>> logical_size;
+ std::experimental::optional<std::string> name;
+ std::experimental::optional<std::string> description;
+ };
+
+ XdgOutputV1(XdgOutputManagerV1& manager, size_t output_index);
+
+ XdgOutputV1(XdgOutputV1 const&) = delete;
+ XdgOutputV1 const& operator=(XdgOutputV1 const&) = delete;
+
+ operator zxdg_output_v1*() const;
+ auto state() -> State const&;
+
+private:
+ struct Impl;
+ std::shared_ptr<Impl> const impl; // shared instead of unique because interally a weak is needed
+};
+
+}
+
+#endif // XDG_OUTPUT_V1_H
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_XDG_SHELL_STABLE_H
+#define WLCS_XDG_SHELL_STABLE_H
+
+#include "in_process_server.h"
+#include "generated/xdg-shell-client.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(xdg_wm_base)
+
+class XdgSurfaceStable
+{
+public:
+ XdgSurfaceStable(wlcs::Client& client, wlcs::Surface& surface);
+ XdgSurfaceStable(XdgSurfaceStable const&) = delete;
+ XdgSurfaceStable& operator=(XdgSurfaceStable const&) = delete;
+ ~XdgSurfaceStable();
+
+ void add_configure_notification(std::function<void(uint32_t)> notification)
+ {
+ configure_notifiers.push_back(notification);
+ }
+
+ operator xdg_surface*() const {return shell_surface;}
+
+ std::vector<std::function<void(uint32_t)>> configure_notifiers;
+
+private:
+ static void configure_thunk(void *data, struct xdg_surface *, uint32_t serial)
+ {
+ for (auto& notifier : static_cast<XdgSurfaceStable*>(data)->configure_notifiers)
+ {
+ notifier(serial);
+ }
+ }
+
+ xdg_surface* shell_surface;
+};
+
+class XdgToplevelStable
+{
+public:
+ struct State
+ {
+ State(int32_t width, int32_t height, struct wl_array *states);
+
+ int width;
+ int height;
+
+ bool maximized;
+ bool fullscreen;
+ bool resizing;
+ bool activated;
+ };
+
+ XdgToplevelStable(XdgSurfaceStable& shell_surface_);
+ XdgToplevelStable(XdgToplevelStable const&) = delete;
+ XdgToplevelStable& operator=(XdgToplevelStable const&) = delete;
+ ~XdgToplevelStable();
+
+ void add_configure_notification(std::function<void(int32_t, int32_t, struct wl_array *)> notification)
+ {
+ configure_notifiers.push_back(notification);
+ }
+
+ void add_close_notification(std::function<void()> notification)
+ {
+ close_notifiers.push_back(notification);
+ }
+
+ operator xdg_toplevel*() const {return toplevel;}
+
+ XdgSurfaceStable* const shell_surface;
+ xdg_toplevel* toplevel;
+
+ std::vector<std::function<void(int32_t, int32_t, struct wl_array *)>> configure_notifiers;
+ std::vector<std::function<void()>> close_notifiers;
+
+private:
+ static void configure_thunk(void *data, struct xdg_toplevel *, int32_t width, int32_t height,
+ struct wl_array *states)
+ {
+ for (auto& notifier : static_cast<XdgToplevelStable*>(data)->configure_notifiers)
+ {
+ notifier(width, height, states);
+ }
+ }
+
+ static void close_thunk(void *data, struct xdg_toplevel *)
+ {
+ for (auto& notifier : static_cast<XdgToplevelStable*>(data)->close_notifiers)
+ {
+ notifier();
+ }
+ }
+};
+
+class XdgPositionerStable
+{
+public:
+ XdgPositionerStable(wlcs::Client& client);
+ ~XdgPositionerStable();
+ operator xdg_positioner*() const {return positioner;}
+
+private:
+ xdg_positioner* const positioner;
+};
+
+class XdgPopupStable
+{
+public:
+ XdgPopupStable(
+ XdgSurfaceStable& shell_surface_,
+ std::experimental::optional<XdgSurfaceStable*> parent,
+ XdgPositionerStable& positioner);
+ XdgPopupStable(XdgPopupStable const&) = delete;
+ XdgPopupStable& operator=(XdgPopupStable const&) = delete;
+ ~XdgPopupStable();
+
+ void add_configure_notification(std::function<void(int32_t, int32_t, int32_t, int32_t)> notification)
+ {
+ configure_notifiers.push_back(notification);
+ }
+
+ void add_close_notification(std::function<void()> notification)
+ {
+ popup_done_notifiers.push_back(notification);
+ }
+
+ operator xdg_popup*() const {return popup;}
+
+ XdgSurfaceStable* const shell_surface;
+ xdg_popup* const popup;
+
+ std::vector<std::function<void(int32_t, int32_t, int32_t, int32_t)>> configure_notifiers;
+ std::vector<std::function<void()>> popup_done_notifiers;
+
+private:
+ static void configure_thunk(void* data, struct xdg_popup*, int32_t x, int32_t y,
+ int32_t width, int32_t height)
+ {
+ for (auto& notifier : static_cast<XdgPopupStable*>(data)->configure_notifiers)
+ {
+ notifier(x, y, width, height);
+ }
+ }
+
+ static void popup_done_thunk(void *data, struct xdg_popup*)
+ {
+ for (auto& notifier : static_cast<XdgPopupStable*>(data)->popup_done_notifiers)
+ {
+ notifier();
+ }
+ }
+};
+
+}
+
+#endif // WLCS_XDG_SHELL_STABLE_H
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_XDG_SHELL_V6_
+#define WLCS_XDG_SHELL_V6_
+
+#include "in_process_server.h"
+#include "generated/xdg-shell-unstable-v6-client.h"
+
+namespace wlcs
+{
+
+class XdgSurfaceV6
+{
+public:
+ XdgSurfaceV6(wlcs::Client& client, wlcs::Surface& surface);
+ XdgSurfaceV6(XdgSurfaceV6 const&) = delete;
+ XdgSurfaceV6& operator=(XdgSurfaceV6 const&) = delete;
+ ~XdgSurfaceV6();
+
+ void add_configure_notification(std::function<void(uint32_t)> notification)
+ {
+ configure_notifiers.push_back(notification);
+ }
+
+ operator zxdg_surface_v6*() const {return shell_surface;}
+
+ std::vector<std::function<void(uint32_t)>> configure_notifiers;
+
+private:
+ static void configure_thunk(void *data, struct zxdg_surface_v6 *, uint32_t serial)
+ {
+ for (auto& notifier : static_cast<XdgSurfaceV6*>(data)->configure_notifiers)
+ {
+ notifier(serial);
+ }
+ }
+
+ zxdg_surface_v6* shell_surface;
+};
+
+class XdgToplevelV6
+{
+public:
+ struct State
+ {
+ State(int32_t width, int32_t height, struct wl_array *states);
+
+ int width;
+ int height;
+
+ bool maximized;
+ bool fullscreen;
+ bool resizing;
+ bool activated;
+ };
+
+ XdgToplevelV6(XdgSurfaceV6& shell_surface_);
+ XdgToplevelV6(XdgToplevelV6 const&) = delete;
+ XdgToplevelV6& operator=(XdgToplevelV6 const&) = delete;
+ ~XdgToplevelV6();
+
+ void add_configure_notification(std::function<void(int32_t, int32_t, struct wl_array *)> notification)
+ {
+ configure_notifiers.push_back(notification);
+ }
+
+ void add_close_notification(std::function<void()> notification)
+ {
+ close_notifiers.push_back(notification);
+ }
+
+ operator zxdg_toplevel_v6*() const {return toplevel;}
+
+ XdgSurfaceV6* const shell_surface;
+ zxdg_toplevel_v6* toplevel;
+
+ std::vector<std::function<void(int32_t, int32_t, struct wl_array *)>> configure_notifiers;
+ std::vector<std::function<void()>> close_notifiers;
+
+private:
+ static void configure_thunk(void *data, struct zxdg_toplevel_v6 *, int32_t width, int32_t height,
+ struct wl_array *states)
+ {
+ for (auto& notifier : static_cast<XdgToplevelV6*>(data)->configure_notifiers)
+ {
+ notifier(width, height, states);
+ }
+ }
+
+ static void close_thunk(void *data, struct zxdg_toplevel_v6 *)
+ {
+ for (auto& notifier : static_cast<XdgToplevelV6*>(data)->close_notifiers)
+ {
+ notifier();
+ }
+ }
+};
+
+class XdgPositionerV6
+{
+public:
+ XdgPositionerV6(wlcs::Client& client);
+ ~XdgPositionerV6();
+ operator zxdg_positioner_v6*() const {return positioner;}
+
+private:
+ zxdg_positioner_v6* const positioner;
+};
+
+class XdgPopupV6
+{
+public:
+ XdgPopupV6(XdgSurfaceV6& shell_surface_, XdgSurfaceV6& parent, XdgPositionerV6& positioner);
+ XdgPopupV6(XdgPopupV6 const&) = delete;
+ XdgPopupV6& operator=(XdgPopupV6 const&) = delete;
+ ~XdgPopupV6();
+
+ void add_configure_notification(std::function<void(int32_t, int32_t, int32_t, int32_t)> notification)
+ {
+ configure_notifiers.push_back(notification);
+ }
+
+ void add_close_notification(std::function<void()> notification)
+ {
+ popup_done_notifiers.push_back(notification);
+ }
+
+ operator zxdg_popup_v6*() const {return popup;}
+
+ XdgSurfaceV6* const shell_surface;
+ zxdg_popup_v6* const popup;
+
+ std::vector<std::function<void(int32_t, int32_t, int32_t, int32_t)>> configure_notifiers;
+ std::vector<std::function<void()>> popup_done_notifiers;
+
+private:
+ static void configure_thunk(void *data, struct zxdg_popup_v6 *, int32_t x, int32_t y,
+ int32_t width, int32_t height)
+ {
+ for (auto& notifier : static_cast<XdgPopupV6*>(data)->configure_notifiers)
+ {
+ notifier(x, y, width, height);
+ }
+ }
+
+ static void popup_done_thunk(void *data, struct zxdg_popup_v6 *)
+ {
+ for (auto& notifier : static_cast<XdgPopupV6*>(data)->popup_done_notifiers)
+ {
+ notifier();
+ }
+ }
+};
+
+}
+
+#endif // WLCS_XDG_SHELL_V6_
--- /dev/null
+project: wlcs
+
+kill-timeout: 50m
+
+backends:
+ lxd:
+ systems:
+ - ubuntu-20.04
+ - ubuntu-21.04
+ - ubuntu-devel:
+ image: ubuntu-daily:devel/amd64
+ - fedora-32
+ - fedora-33
+ - fedora-34
+ - alpine-3.13
+
+suites:
+ spread/build/:
+ summary: Build wlcs
+ environment:
+ CC/gcc: gcc
+ CXX/gcc: g++
+ CC/clang: clang
+ CXX/clang: clang++
+
+path:
+ /spread/wlcs
+
+exclude:
+ - .git
--- /dev/null
+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)
+
--- /dev/null
+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)
+
--- /dev/null
+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
+ }
+
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "data_device.h"
+
+wlcs::ActiveListeners wlcs::DataDeviceListener::active_listeners;
+constexpr wl_data_device_listener wlcs::DataDeviceListener::thunks;
+
+
+void wlcs::DataDeviceListener::data_offer(void* data, struct wl_data_device* wl_data_device, struct wl_data_offer* id)
+{
+ if (active_listeners.includes(data))
+ static_cast<DataDeviceListener*>(data)->data_offer(wl_data_device, id);
+}
+
+void wlcs::DataDeviceListener::enter(
+ void* data,
+ struct wl_data_device* wl_data_device,
+ uint32_t serial,
+ struct wl_surface* surface,
+ wl_fixed_t x,
+ wl_fixed_t y,
+ struct wl_data_offer* id)
+{
+ if (active_listeners.includes(data))
+ static_cast<DataDeviceListener*>(data)->enter(wl_data_device, serial, surface, x, y, id);
+}
+
+void wlcs::DataDeviceListener::leave(void* data, struct wl_data_device* wl_data_device)
+{
+ if (active_listeners.includes(data))
+ static_cast<DataDeviceListener*>(data)->leave(wl_data_device);
+}
+
+void wlcs::DataDeviceListener::motion(
+ void* data,
+ struct wl_data_device* wl_data_device,
+ uint32_t time,
+ wl_fixed_t x,
+ wl_fixed_t y)
+{
+ if (active_listeners.includes(data))
+ static_cast<DataDeviceListener*>(data)->motion(wl_data_device, time, x, y);
+}
+
+void wlcs::DataDeviceListener::drop(void* data, struct wl_data_device* wl_data_device)
+{
+ if (active_listeners.includes(data))
+ static_cast<DataDeviceListener*>(data)->drop(wl_data_device);
+}
+
+void wlcs::DataDeviceListener::selection(
+ void* data,
+ struct wl_data_device* wl_data_device,
+ struct wl_data_offer* id)
+{
+ if (active_listeners.includes(data))
+ static_cast<DataDeviceListener*>(data)->selection(wl_data_device, wl_data_device, id);
+}
+
+void wlcs::DataDeviceListener::data_offer(struct wl_data_device* /*wl_data_device*/, struct wl_data_offer* /*id*/)
+{
+}
+
+void wlcs::DataDeviceListener::enter(
+ struct wl_data_device* /*wl_data_device*/,
+ uint32_t /*serial*/,
+ struct wl_surface* /*surface*/,
+ wl_fixed_t /*x*/,
+ wl_fixed_t /*y*/,
+ struct wl_data_offer* /*id*/)
+{
+}
+
+void wlcs::DataDeviceListener::leave(struct wl_data_device* /*wl_data_device*/)
+{
+}
+
+void wlcs::DataDeviceListener::motion(
+ struct wl_data_device* /*wl_data_device*/,
+ uint32_t /*time*/,
+ wl_fixed_t /*x*/,
+ wl_fixed_t /*y*/)
+{
+}
+
+void wlcs::DataDeviceListener::drop(struct wl_data_device* /*wl_data_device*/)
+{
+}
+
+void wlcs::DataDeviceListener::selection(
+ struct wl_data_device* /*wl_data_device*/,
+ struct wl_data_offer* /*id*/)
+{
+}
+
+
+wlcs::ActiveListeners wlcs::DataOfferListener::active_listeners;
+constexpr wl_data_offer_listener wlcs::DataOfferListener::thunks;
+
+void wlcs::DataOfferListener::offer(void* data, struct wl_data_offer* data_offer, char const* mime_type)
+{
+ if (active_listeners.includes(data))
+ static_cast<DataOfferListener*>(data)->offer(data_offer, mime_type);
+}
+
+void wlcs::DataOfferListener::source_actions(void* data, struct wl_data_offer* data_offer, uint32_t dnd_actions)
+{
+ if (active_listeners.includes(data))
+ static_cast<DataOfferListener*>(data)->source_actions(data_offer, dnd_actions);
+}
+
+void wlcs::DataOfferListener::action(void* data, struct wl_data_offer* data_offer, uint32_t dnd_action)
+{
+ if (active_listeners.includes(data))
+ static_cast<DataOfferListener*>(data)->action(data_offer, dnd_action);
+}
+
+void wlcs::DataOfferListener::offer(struct wl_data_offer* /*data_offer*/, char const* /*mime_type*/)
+{
+}
+
+void wlcs::DataOfferListener::source_actions(struct wl_data_offer* /*data_offer*/, uint32_t /*dnd_actions*/)
+{
+}
+
+void wlcs::DataOfferListener::action(struct wl_data_offer* /*data_offer*/, uint32_t /*dnd_action*/)
+{
+}
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "gtk_primary_selection.h"
+
+#include <unistd.h>
+
+wlcs::ActiveListeners wlcs::GtkPrimarySelectionDeviceListener::active_listeners;
+constexpr gtk_primary_selection_device_listener wlcs::GtkPrimarySelectionDeviceListener::thunks;
+
+wlcs::ActiveListeners wlcs::GtkPrimarySelectionOfferListener::active_listeners;
+constexpr gtk_primary_selection_offer_listener wlcs::GtkPrimarySelectionOfferListener::thunks;
+
+wlcs::ActiveListeners wlcs::GtkPrimarySelectionSourceListener::active_listeners;
+constexpr gtk_primary_selection_source_listener wlcs::GtkPrimarySelectionSourceListener::thunks;
+
+
+void wlcs::GtkPrimarySelectionDeviceListener::data_offer(
+ void* data,
+ gtk_primary_selection_device* device,
+ gtk_primary_selection_offer* offer)
+{
+ if (active_listeners.includes(data))
+ static_cast<GtkPrimarySelectionDeviceListener*>(data)->data_offer(device, offer);
+}
+
+void wlcs::GtkPrimarySelectionDeviceListener::data_offer(gtk_primary_selection_device*, gtk_primary_selection_offer*)
+{
+}
+
+void wlcs::GtkPrimarySelectionDeviceListener::selection(gtk_primary_selection_device*, gtk_primary_selection_offer*)
+{
+}
+
+void wlcs::GtkPrimarySelectionDeviceListener::selection(
+ void* data,
+ gtk_primary_selection_device* device,
+ gtk_primary_selection_offer* offer)
+{
+ if (active_listeners.includes(data))
+ static_cast<GtkPrimarySelectionDeviceListener*>(data)->selection(device, offer);
+}
+
+void wlcs::GtkPrimarySelectionOfferListener::offer(gtk_primary_selection_offer*, const char*)
+{
+}
+
+void wlcs::GtkPrimarySelectionOfferListener::offer(
+ void* data, gtk_primary_selection_offer* offer, const char* mime_type)
+{
+ if (active_listeners.includes(data))
+ static_cast<GtkPrimarySelectionOfferListener*>(data)->offer(offer, mime_type);
+}
+
+void wlcs::GtkPrimarySelectionSourceListener::send(gtk_primary_selection_source*, const char*, int32_t fd)
+{
+ close(fd);
+}
+
+void wlcs::GtkPrimarySelectionSourceListener::cancelled(gtk_primary_selection_source*)
+{
+}
+
+void wlcs::GtkPrimarySelectionSourceListener::send(
+ void* data, gtk_primary_selection_source* source, const char* mime_type, int32_t fd)
+{
+ if (active_listeners.includes(data))
+ static_cast<GtkPrimarySelectionSourceListener*>(data)->send(source, mime_type, fd);
+}
+
+void wlcs::GtkPrimarySelectionSourceListener::cancelled(void* data, gtk_primary_selection_source* source)
+{
+ if (active_listeners.includes(data))
+ static_cast<GtkPrimarySelectionSourceListener*>(data)->cancelled(source);
+}
--- /dev/null
+/*
+ * Copyright © 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2 or 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by:
+ * Alexandros Frantzis <alexandros.frantzis@canonical.com>
+ */
+
+#include "helpers.h"
+#include "shared_library.h"
+
+#include <boost/throw_exception.hpp>
+#include <system_error>
+
+#include <fcntl.h>
+#include <linux/memfd.h>
+#include <sys/syscall.h>
+
+namespace
+{
+
+bool error_indicates_tmpfile_not_supported(int error)
+{
+ return
+ error == EISDIR || // Directory exists, but no support for O_TMPFILE
+ error == ENOENT || // Directory doesn't exist, and no support for O_TMPFILE
+ error == EOPNOTSUPP || // Filesystem that directory resides on does not support O_TMPFILE
+ error == EINVAL; // There apparently exists at least one development board that has a kernel
+ // that incorrectly returns EINVAL. Yay.
+}
+
+int memfd_create(char const* name, unsigned int flags)
+{
+ return static_cast<int>(syscall(SYS_memfd_create, name, flags));
+}
+}
+
+int wlcs::helpers::create_anonymous_file(size_t size)
+{
+
+ int fd = memfd_create("wlcs-unnamed", MFD_CLOEXEC);
+ if (fd == -1 && errno == ENOSYS)
+ {
+ fd = open("/dev/shm", O_TMPFILE | O_RDWR | O_EXCL | O_CLOEXEC, S_IRWXU);
+
+ // Workaround for filesystems that don't support O_TMPFILE
+ if (fd == -1 && error_indicates_tmpfile_not_supported(errno))
+ {
+ char template_filename[] = "/dev/shm/wlcs-buffer-XXXXXX";
+ fd = mkostemp(template_filename, O_CLOEXEC);
+ if (fd != -1)
+ {
+ if (unlink(template_filename) < 0)
+ {
+ close(fd);
+ fd = -1;
+ }
+ }
+ }
+ }
+
+ if (fd == -1)
+ {
+ BOOST_THROW_EXCEPTION(
+ std::system_error(errno, std::system_category(), "Failed to open temporary file"));
+ }
+
+ if (ftruncate(fd, size) == -1)
+ {
+ close(fd);
+ BOOST_THROW_EXCEPTION(
+ std::system_error(errno, std::system_category(), "Failed to resize temporary file"));
+ }
+
+ return fd;
+}
+
+namespace
+{
+static int argc;
+static char const** argv;
+
+std::shared_ptr<WlcsServerIntegration const> entry_point;
+}
+
+void wlcs::helpers::set_command_line(int argc, char const** argv)
+{
+ ::argc = argc;
+ ::argv = argv;
+}
+
+int wlcs::helpers::get_argc()
+{
+ return ::argc;
+}
+
+char const** wlcs::helpers::get_argv()
+{
+ return ::argv;
+}
+
+void wlcs::helpers::set_entry_point(std::shared_ptr<WlcsServerIntegration const> const& entry_point)
+{
+ ::entry_point = entry_point;
+}
+
+std::shared_ptr<WlcsServerIntegration const> wlcs::helpers::get_test_hooks()
+{
+ return ::entry_point;
+}
--- /dev/null
+/*
+ * Copyright © 2017-2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include "in_process_server.h"
+#include "thread_proxy.h"
+#include "version_specifier.h"
+#include "wlcs/display_server.h"
+#include "helpers.h"
+#include "wlcs/pointer.h"
+#include "wlcs/touch.h"
+#include "xdg_shell_v6.h"
+#include "xdg_shell_stable.h"
+#include "generated/wayland-client.h"
+#include "generated/xdg-shell-unstable-v6-client.h"
+#include "generated/xdg-shell-client.h"
+
+#include "linux/input.h"
+#include <boost/throw_exception.hpp>
+#include <stdexcept>
+#include <memory>
+#include <vector>
+#include <algorithm>
+#include <experimental/optional>
+#include <map>
+#include <unordered_map>
+#include <chrono>
+#include <poll.h>
+
+using namespace std::literals::chrono_literals;
+
+class ShimNotImplemented : public std::logic_error
+{
+public:
+ ShimNotImplemented() : std::logic_error("Function not implemented in display server shim")
+ {
+ }
+
+ ShimNotImplemented(const std::string& name)
+ : std::logic_error("Function '" + name + "()' not implemented in display server shim")
+ {
+ }
+};
+
+wlcs::ExtensionExpectedlyNotSupported::ExtensionExpectedlyNotSupported(char const* extension, VersionSpecifier const& version)
+ : std::runtime_error{
+ std::string{"Extension: "} +
+ extension + " version " + version.describe() +
+ " not supported by compositor under test."}
+{
+ auto const skip_reason =
+ std::string{"Missing extension: "} + extension + version.describe();
+ ::testing::Test::RecordProperty("wlcs-skip-test", skip_reason);
+}
+
+wlcs::Timeout::Timeout(char const* message)
+ : std::runtime_error(message)
+{
+}
+
+class wlcs::Pointer::Impl
+{
+public:
+ template<typename Proxy>
+ Impl(
+ WlcsPointer* raw_device,
+ std::shared_ptr<Proxy> const& proxy,
+ std::shared_ptr<void const> const& keep_dso_loaded)
+ : keep_dso_loaded{keep_dso_loaded},
+ pointer{raw_device}
+ {
+ setup_thunks(proxy);
+ }
+
+ ~Impl()
+ {
+ destroy_thunk();
+ }
+
+ void move_to(int x, int y)
+ {
+ move_absolute_thunk(wl_fixed_from_int(x), wl_fixed_from_int(y));
+ }
+
+ void move_by(int dx, int dy)
+ {
+ move_relative_thunk(wl_fixed_from_int(dx), wl_fixed_from_int(dy));
+ }
+
+ void button_down(int button)
+ {
+ button_down_thunk(button);
+ }
+
+ void button_up(int button)
+ {
+ button_up_thunk(button);
+ }
+
+private:
+ template<typename Proxy>
+ void setup_thunks(std::shared_ptr<Proxy> const& proxy)
+ {
+ move_absolute_thunk = proxy->register_op(
+ [this](wl_fixed_t x, wl_fixed_t y)
+ {
+ pointer->move_absolute(pointer, x, y);
+ });
+ move_relative_thunk = proxy->register_op(
+ [this](wl_fixed_t dx, wl_fixed_t dy)
+ {
+ pointer->move_relative(pointer, dx, dy);
+ });
+ button_down_thunk = proxy->register_op(
+ [this](int button)
+ {
+ pointer->button_down(pointer, button);
+ });
+ button_up_thunk = proxy->register_op(
+ [this](int button)
+ {
+ pointer->button_up(pointer, button);
+ });
+ destroy_thunk = proxy->register_op(
+ [this]()
+ {
+ pointer->destroy(pointer);
+ });
+ }
+
+ std::shared_ptr<void const> const keep_dso_loaded;
+ WlcsPointer* const pointer;
+
+ std::function<void(wl_fixed_t, wl_fixed_t)> move_absolute_thunk;
+ std::function<void(wl_fixed_t, wl_fixed_t)> move_relative_thunk;
+ std::function<void(int)> button_down_thunk;
+ std::function<void(int)> button_up_thunk;
+ std::function<void()> destroy_thunk;
+};
+
+wlcs::Pointer::~Pointer() = default;
+wlcs::Pointer::Pointer(Pointer&&) = default;
+
+template<typename Proxy>
+wlcs::Pointer::Pointer(
+ WlcsPointer* raw_device,
+ std::shared_ptr<Proxy> const& proxy,
+ std::shared_ptr<void const> const& keep_dso_loaded)
+ : impl{std::make_unique<Impl>(raw_device, proxy, keep_dso_loaded)}
+{
+}
+
+void wlcs::Pointer::move_to(int x, int y)
+{
+ impl->move_to(x, y);
+}
+
+void wlcs::Pointer::move_by(int dx, int dy)
+{
+ impl->move_by(dx, dy);
+}
+
+void wlcs::Pointer::button_down(int button)
+{
+ impl->button_down(button);
+}
+
+void wlcs::Pointer::button_up(int button)
+{
+ impl->button_up(button);
+}
+
+void wlcs::Pointer::click(int button)
+{
+ button_down(button);
+ button_up(button);
+}
+
+void wlcs::Pointer::left_button_down()
+{
+ button_down(BTN_LEFT);
+}
+
+void wlcs::Pointer::left_button_up()
+{
+ button_up(BTN_LEFT);
+}
+
+void wlcs::Pointer::left_click()
+{
+ click(BTN_LEFT);
+}
+
+class wlcs::Touch::Impl
+{
+public:
+ template<typename Proxy>
+ Impl(
+ WlcsTouch* raw_device,
+ std::shared_ptr<Proxy> const& proxy,
+ std::shared_ptr<void const> const& keep_dso_loaded)
+ : keep_dso_loaded{keep_dso_loaded},
+ touch{raw_device, proxy->register_op([](WlcsTouch* raw_device) { raw_device->destroy(raw_device); })}
+ {
+ if (touch->version != WLCS_TOUCH_VERSION)
+ {
+ BOOST_THROW_EXCEPTION((
+ std::runtime_error{
+ std::string{"Unexpected WlcsTouch version. Expected: "} +
+ std::to_string(WLCS_TOUCH_VERSION) +
+ " received: " +
+ std::to_string(touch->version)}));
+ }
+ set_up_thunks(proxy);
+ }
+
+ void down_at(int x, int y)
+ {
+ touch->touch_down(touch.get(), x, y);
+ }
+
+ void move_to(int x, int y)
+ {
+ touch->touch_move(touch.get(), x, y);
+ }
+
+ void up()
+ {
+ touch->touch_up(touch.get());
+ }
+
+private:
+ template<typename Proxy>
+ void set_up_thunks(std::shared_ptr<Proxy> const& proxy)
+ {
+ touch_down_thunk = proxy->register_op(
+ [this](int x, int y)
+ {
+ touch->touch_down(touch.get(), x, y);
+ });
+ touch_move_thunk = proxy->register_op(
+ [this](int x, int y)
+ {
+ touch->touch_move(touch.get(), x, y);
+ });
+ touch_up_thunk = proxy->register_op(
+ [this]()
+ {
+ touch->touch_up(touch.get());
+ });
+ }
+
+ std::shared_ptr<void const> const keep_dso_loaded;
+ std::unique_ptr<WlcsTouch, std::function<void(WlcsTouch*)>> const touch;
+
+ std::function<void(int, int)> touch_down_thunk;
+ std::function<void(int, int)> touch_move_thunk;
+ std::function<void()> touch_up_thunk;
+};
+
+wlcs::Touch::~Touch() = default;
+wlcs::Touch::Touch(Touch&&) = default;
+
+template<typename Proxy>
+wlcs::Touch::Touch(
+ WlcsTouch* raw_device,
+ std::shared_ptr<Proxy> const& proxy,
+ std::shared_ptr<void const> const& keep_dso_loaded)
+ : impl{std::make_unique<Impl>(raw_device, proxy, keep_dso_loaded)}
+{
+}
+
+void wlcs::Touch::down_at(int x, int y)
+{
+ impl->down_at(x, y);
+}
+
+void wlcs::Touch::move_to(int x, int y)
+{
+ impl->move_to(x, y);
+}
+
+void wlcs::Touch::up()
+{
+ impl->up();
+}
+
+namespace
+{
+std::shared_ptr<std::unordered_map<std::string, uint32_t> const> extract_supported_extensions(WlcsDisplayServer* server)
+{
+ if (server->version < 2)
+ {
+ return {};
+ }
+
+ auto const descriptor = server->get_descriptor(server);
+ auto extensions = std::make_shared<std::unordered_map<std::string, uint32_t>>();
+
+ for (auto i = 0u; i < descriptor->num_extensions; ++i)
+ {
+ (*extensions)[descriptor->supported_extensions[i].name] = descriptor->supported_extensions[i].version;
+ }
+ return extensions;
+}
+
+class NullProxy
+{
+public:
+ template<typename Callable>
+ auto register_op(Callable handler)
+ {
+ return handler;
+ }
+};
+}
+
+class wlcs::Server::Impl
+{
+public:
+ Impl(std::shared_ptr<WlcsServerIntegration const> const& hooks, int argc, char const** argv)
+ : server{hooks->create_server(argc, argv), hooks->destroy_server},
+ thread_context{make_context_if_needed(*server)},
+ hooks{hooks},
+ supported_extensions_{extract_supported_extensions(server.get())}
+ {
+ if (hooks->version < 1)
+ {
+ BOOST_THROW_EXCEPTION((std::runtime_error{"Server integration too old"}));
+ }
+ if (server->version < 1)
+ {
+ BOOST_THROW_EXCEPTION((std::runtime_error{"Server integration too old"}));
+ }
+ if (!server->stop)
+ {
+ BOOST_THROW_EXCEPTION((std::logic_error{"Missing required WlcsDisplayServer.stop definition"}));
+ }
+ if (thread_context)
+ {
+ initialise_thunks(thread_context->proxy);
+ }
+ else
+ {
+ initialise_thunks(std::make_shared<NullProxy>());
+ }
+ }
+
+ void start()
+ {
+ if (thread_context)
+ {
+ thread_context->server_thread = std::thread{
+ [this]()
+ {
+ server->start_on_this_thread(server.get(), thread_context->event_loop.get());
+ }};
+ }
+ else
+ {
+ server->start(server.get());
+ }
+ }
+
+ void stop()
+ {
+ stop_thunk();
+ }
+
+ int create_client_socket()
+ {
+ auto fd = create_client_socket_thunk();
+ if (fd < 0)
+ {
+ BOOST_THROW_EXCEPTION((std::system_error{
+ errno,
+ std::system_category(),
+ "Failed to get client socket from server"}));
+ }
+ return fd;
+ }
+
+ Pointer create_pointer()
+ {
+ if (thread_context)
+ {
+ return Pointer{create_pointer_thunk(), thread_context->proxy, hooks};
+ }
+ else
+ {
+ return Pointer{create_pointer_thunk(), std::make_shared<NullProxy>(), hooks};
+ }
+ }
+
+ Touch create_touch()
+ {
+ if (thread_context)
+ {
+ return Touch{create_touch_thunk(), thread_context->proxy, hooks};
+ }
+ else
+ {
+ return Touch{create_touch_thunk(), std::make_shared<NullProxy>(), hooks};
+ }
+ }
+
+ void move_surface_to(Surface& surface, int x, int y)
+ {
+ // Ensure the server knows about the IDs we're about to send...
+ surface.owner().roundtrip();
+
+ position_window_absolute_thunk(surface.owner(), surface, x, y);
+ }
+
+ WlcsDisplayServer* wlcs_server() const
+ {
+ return server.get();
+ }
+
+ std::shared_ptr<const std::unordered_map<std::string, uint32_t>> supported_extensions() const
+ {
+ return supported_extensions_;
+ }
+
+private:
+ struct ThreadContext
+ {
+ explicit ThreadContext(std::unique_ptr<struct wl_event_loop, decltype(&wl_event_loop_destroy)> loop)
+ : event_loop{std::move(loop)},
+ proxy{std::make_shared<ThreadProxy>(event_loop.get())}
+ {
+ }
+ ThreadContext(ThreadContext&&) = default;
+
+ ~ThreadContext()
+ {
+ if (server_thread.joinable())
+ {
+ server_thread.join();
+ }
+ }
+
+ std::unique_ptr<struct wl_event_loop, void(*)(struct wl_event_loop*)> event_loop;
+ std::thread server_thread;
+ std::shared_ptr<ThreadProxy> proxy;
+ };
+
+ static std::experimental::optional<ThreadContext> make_context_if_needed(WlcsDisplayServer const& server)
+ {
+ if (server.version >= 3)
+ {
+ if (!server.start)
+ {
+ if (!server.start_on_this_thread)
+ {
+ BOOST_THROW_EXCEPTION((
+ std::runtime_error{"Server integration missing both start() and start_on_this_thread()"}));
+ }
+ auto loop = std::unique_ptr<struct wl_event_loop, decltype(&wl_event_loop_destroy)>{
+ wl_event_loop_create(),
+ &wl_event_loop_destroy
+ };
+ if (!loop)
+ {
+ BOOST_THROW_EXCEPTION((
+ std::runtime_error{"Failed to create eventloop for WLCS events"}));
+ }
+
+ return {ThreadContext{std::move(loop)}};
+ }
+ }
+ return {};
+ };
+
+ std::unique_ptr<WlcsDisplayServer, void(*)(WlcsDisplayServer*)> const server;
+ std::experimental::optional<ThreadContext> thread_context;
+ std::shared_ptr<WlcsServerIntegration const> const hooks;
+ std::shared_ptr<std::unordered_map<std::string, uint32_t> const> const supported_extensions_;
+
+ template<typename Proxy>
+ void initialise_thunks(std::shared_ptr<Proxy> proxy)
+ {
+ stop_thunk = proxy->register_op(
+ [this]()
+ {
+ server->stop(server.get());
+ });
+ create_client_socket_thunk = proxy->register_op(
+ [this]()
+ {
+ return server->create_client_socket(server.get());
+ });
+ create_pointer_thunk = proxy->register_op(
+ [this]()
+ {
+ return server->create_pointer(server.get());
+ });
+ create_touch_thunk = proxy->register_op(
+ [this]()
+ {
+ return server->create_touch(server.get());
+ });
+ position_window_absolute_thunk = proxy->register_op(
+ [this](
+ struct wl_display* client,
+ struct wl_surface* surface,
+ int x,
+ int y)
+ {
+ server->position_window_absolute(
+ server.get(),
+ client,
+ surface,
+ x,
+ y);
+ });
+ }
+ std::function<void()> stop_thunk;
+ std::function<int()> create_client_socket_thunk;
+ std::function<WlcsPointer*()> create_pointer_thunk;
+ std::function<WlcsTouch*()> create_touch_thunk;
+ std::function<void(struct wl_display*, struct wl_surface*, int, int)> position_window_absolute_thunk;
+};
+
+wlcs::Server::Server(
+ std::shared_ptr<WlcsServerIntegration const> const& hooks,
+ int argc,
+ char const** argv)
+ : impl{std::make_unique<wlcs::Server::Impl>(hooks, argc, argv)}
+{
+}
+
+wlcs::Server::~Server() = default;
+
+void wlcs::Server::start()
+{
+ impl->start();
+}
+
+void wlcs::Server::stop()
+{
+ impl->stop();
+}
+
+std::shared_ptr<const std::unordered_map<std::string, uint32_t>> wlcs::Server::supported_extensions()
+{
+ return impl->supported_extensions();
+}
+
+int wlcs::Server::create_client_socket()
+{
+ return impl->create_client_socket();
+}
+
+void wlcs::Server::move_surface_to(Surface& surface, int x, int y)
+{
+ impl->move_surface_to(surface, x, y);
+}
+
+wlcs::Pointer wlcs::Server::create_pointer()
+{
+ return impl->create_pointer();
+}
+
+wlcs::Touch wlcs::Server::create_touch()
+{
+ return impl->create_touch();
+}
+
+wlcs::InProcessServer::InProcessServer()
+ : server{helpers::get_test_hooks(), helpers::get_argc(), helpers::get_argv()}
+{
+}
+
+void wlcs::InProcessServer::SetUp()
+{
+ server.start();
+}
+
+void wlcs::InProcessServer::TearDown()
+{
+ server.stop();
+}
+
+wlcs::Server& wlcs::InProcessServer::the_server()
+{
+ return server;
+}
+
+void throw_wayland_error(wl_display* display)
+{
+ auto err = wl_display_get_error(display);
+ if (err != EPROTO)
+ {
+ BOOST_THROW_EXCEPTION((std::system_error{
+ err,
+ std::system_category(),
+ "Error while dispatching Wayland events"
+ }));
+ }
+ else
+ {
+ uint32_t object_id;
+ uint32_t protocol_error;
+ wl_interface const* interface;
+ protocol_error = wl_display_get_protocol_error(display, &interface, &object_id);
+ BOOST_THROW_EXCEPTION((wlcs::ProtocolError{interface, protocol_error}));
+ }
+}
+
+class wlcs::Client::Impl
+{
+public:
+ Impl(Server& server)
+ : supported_extensions{server.supported_extensions()}
+ {
+ try
+ {
+ display = wl_display_connect_to_fd(server.create_client_socket());
+ }
+ catch (ShimNotImplemented const&)
+ {
+ // TODO: Warn about connecting to who-knows-what
+ display = wl_display_connect(NULL);
+ }
+
+ if (!display)
+ {
+ BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to connect to Wayland socket"}));
+ }
+
+ registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, ®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 (keyboard) wl_keyboard_destroy(keyboard);
+ if (pointer) wl_pointer_destroy(pointer);
+ if (touch) wl_touch_destroy(touch);
+ if (xdg_shell_v6) zxdg_shell_v6_destroy(xdg_shell_v6);
+ if (xdg_shell_stable) xdg_wm_base_destroy(xdg_shell_stable);
+ for (auto const& output: outputs)
+ wl_output_destroy(output->current.output);
+ for (auto const& callback: destruction_callbacks)
+ callback();
+ destruction_callbacks.clear();
+ wl_display_disconnect(display);
+ }
+
+ struct wl_display* wl_display() const
+ {
+ return display;
+ }
+
+ struct wl_compositor* wl_compositor() const
+ {
+ return compositor;
+ }
+
+ struct wl_subcompositor* wl_subcompositor() const
+ {
+ return subcompositor;
+ }
+
+ struct wl_shm* wl_shm() const
+ {
+ return shm;
+ }
+
+ struct wl_seat* wl_seat() const
+ {
+ return seat;
+ }
+
+ void run_on_destruction(std::function<void()> callback)
+ {
+ destruction_callbacks.push_back(callback);
+ }
+
+ ShmBuffer const& create_buffer(Client& client, int width, int height)
+ {
+ auto buffer = std::make_shared<ShmBuffer>(client, width, height);
+ run_on_destruction([buffer]() mutable
+ {
+ buffer.reset();
+ });
+ return *buffer;
+ }
+
+ Surface create_wl_shell_surface(Client& client, int width, int height)
+ {
+ Surface surface{client};
+
+ wl_shell_surface * shell_surface = wl_shell_get_shell_surface(shell, surface);
+ surface.run_on_destruction([shell_surface]()
+ {
+ wl_shell_surface_destroy(shell_surface);
+ });
+ wl_shell_surface_set_toplevel(shell_surface);
+
+ wl_surface_commit(surface);
+
+ surface.attach_visible_buffer(width, height);
+
+ return surface;
+ }
+
+ Surface create_xdg_shell_v6_surface(Client& client, int width, int height)
+ {
+ Surface surface{client};
+
+ auto xdg_surface = std::make_shared<XdgSurfaceV6>(client, surface);
+ auto xdg_toplevel = std::make_shared<XdgToplevelV6>(*xdg_surface);
+
+ surface.run_on_destruction([xdg_surface, xdg_toplevel]() mutable
+ {
+ xdg_surface.reset();
+ xdg_toplevel.reset();
+ });
+
+ wl_surface_commit(surface);
+
+ surface.attach_visible_buffer(width, height);
+
+ return surface;
+ }
+
+ Surface create_xdg_shell_stable_surface(Client& client, int width, int height)
+ {
+ Surface surface{client};
+
+ auto xdg_surface = std::make_shared<XdgSurfaceStable>(client, surface);
+ auto xdg_toplevel = std::make_shared<XdgToplevelStable>(*xdg_surface);
+
+ surface.run_on_destruction([xdg_surface, xdg_toplevel]() mutable
+ {
+ xdg_surface.reset();
+ xdg_toplevel.reset();
+ });
+
+ wl_surface_commit(surface);
+
+ surface.attach_visible_buffer(width, height);
+
+ return surface;
+ }
+
+ wl_shell* the_shell() const
+ {
+ return shell;
+ }
+
+ zxdg_shell_v6* the_xdg_shell_v6() const
+ {
+ return xdg_shell_v6;
+ }
+
+ xdg_wm_base* the_xdg_shell_stable() const
+ {
+ return xdg_shell_stable;
+ }
+
+ wl_pointer* the_pointer() const { return pointer; }
+
+ wl_surface* keyboard_focused_window() const
+ {
+ return keyboard_focused_surface;
+ }
+
+ wl_surface* window_under_cursor() const
+ {
+ if (pointer_events_pending())
+ BOOST_THROW_EXCEPTION(std::runtime_error("Pointer events pending"));
+ if (current_pointer_location)
+ {
+ return current_pointer_location->surface;
+ }
+ return nullptr;
+ }
+
+ wl_surface* touched_window() const
+ {
+ if (touch_events_pending())
+ BOOST_THROW_EXCEPTION(std::runtime_error("Touch events pending"));
+ wl_surface* surface = nullptr;
+ for (auto const& touch : current_touches)
+ {
+ if (surface && touch.second.surface != surface)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Multiple surfaces have active touches"));
+ else
+ surface = touch.second.surface;
+ }
+ return surface;
+ }
+
+ std::pair<wl_fixed_t, wl_fixed_t> pointer_position() const
+ {
+ if (pointer_events_pending())
+ BOOST_THROW_EXCEPTION(std::runtime_error("Pointer events pending"));
+ return current_pointer_location.value().coordinates;
+ };
+
+ std::pair<wl_fixed_t, wl_fixed_t> touch_position() const
+ {
+ if (touch_events_pending())
+ BOOST_THROW_EXCEPTION(std::runtime_error("Touch events pending"));
+ if (current_touches.empty())
+ BOOST_THROW_EXCEPTION(std::runtime_error("No touches"));
+ else if (current_touches.size() == 1)
+ return current_touches.begin()->second.coordinates;
+ else
+ BOOST_THROW_EXCEPTION(std::runtime_error("More than one touches"));
+ };
+
+ std::experimental::optional<uint32_t> latest_serial() const
+ {
+ return latest_serial_;
+ }
+
+ bool pointer_events_pending() const
+ {
+ return !pending_buttons.empty() || pending_pointer_location;
+ }
+
+ bool touch_events_pending() const
+ {
+ return !pending_touches.empty() || !pending_up_touches.empty();
+ }
+
+ void add_pointer_enter_notification(PointerEnterNotifier const& on_enter)
+ {
+ enter_notifiers.push_back(on_enter);
+ }
+ void add_pointer_leave_notification(PointerLeaveNotifier const& on_leave)
+ {
+ leave_notifiers.push_back(on_leave);
+ }
+ void add_pointer_motion_notification(PointerMotionNotifier const& on_motion)
+ {
+ motion_notifiers.push_back(on_motion);
+ }
+ void add_pointer_button_notification(PointerButtonNotifier const& on_button)
+ {
+ button_notifiers.push_back(on_button);
+ }
+
+ void* bind_if_supported(wl_interface const& to_bind, VersionSpecifier const& version) const
+ {
+ std::experimental::optional<bool> expected_to_be_supported{};
+
+ if (supported_extensions)
+ {
+ expected_to_be_supported =
+ supported_extensions->count(to_bind.name) &&
+ version.select_version(supported_extensions->at(to_bind.name));
+ }
+
+ /* TODO: Mark tests using globals which exist, but are listed as unsupported as
+ * may-fail.
+ * This should help incrementally implementing a protocol, while getting test
+ * feedback.
+ */
+ auto const global = globals.find(to_bind.name);
+ if (global != globals.end())
+ {
+ auto const version_to_bind = version.select_version(global->second.version);
+ if (version_to_bind)
+ {
+ auto global_proxy = wl_registry_bind(registry, global->second.id, &to_bind, version_to_bind.value());
+ if (!global_proxy)
+ {
+ throw_wayland_error(display);
+ }
+ return global_proxy;
+ }
+ else if (!expected_to_be_supported.value_or(true))
+ {
+ // We didn't expect to find the needed version of this extension anyway…
+ BOOST_THROW_EXCEPTION((ExtensionExpectedlyNotSupported{to_bind.name, version}));
+ }
+
+ BOOST_THROW_EXCEPTION((std::runtime_error{
+ "Failed to bind to " + std::string{to_bind.name} + " version " + version.describe()}));
+ }
+ else if (!expected_to_be_supported.value_or(true))
+ {
+ // We didn't expect to find this extension anyway…
+ BOOST_THROW_EXCEPTION((ExtensionExpectedlyNotSupported{to_bind.name, version}));
+ }
+ BOOST_THROW_EXCEPTION((std::runtime_error{
+ "Failed to bind to " + std::string{to_bind.name} + " version " + version.describe()}));
+ }
+
+ void dispatch_until(
+ std::function<bool()> const& predicate, std::chrono::seconds timeout)
+ {
+ using namespace std::chrono;
+
+ auto const end_time = steady_clock::now() + timeout;
+ while (!predicate())
+ {
+ while (wl_display_prepare_read(display) != 0)
+ {
+ if (wl_display_dispatch_pending(display) < 0)
+ throw_wayland_error(display);
+ }
+ wl_display_flush(display);
+
+ auto const time_left = end_time - steady_clock::now();
+ if (time_left.count() < 0)
+ {
+ BOOST_THROW_EXCEPTION((Timeout{"Timeout waiting for condition"}));
+ }
+
+ /*
+ * TODO: We really want std::chrono::duration::ceil() here, but that's C++17
+ */
+ /*
+ * We want to wait *at least* as long as time_left. duration_cast<milliseconds>
+ * will perform integer division, so any fractional milliseconds will get dropped.
+ *
+ * Adding 1ms will ensure we wait until *after* we're meant to timeout.
+ */
+ auto const maximum_wait_ms = duration_cast<milliseconds>(time_left) + 1ms;
+ pollfd fd{
+ wl_display_get_fd(display),
+ POLLIN | POLLERR,
+ 0
+ };
+
+ auto const poll_result = poll(&fd, 1, maximum_wait_ms.count());
+ if (poll_result < 0)
+ {
+ wl_display_cancel_read(display);
+ BOOST_THROW_EXCEPTION((std::system_error{
+ errno,
+ std::system_category(),
+ "Failed to wait for Wayland event"}));
+ }
+
+ if (poll_result == 0)
+ {
+ wl_display_cancel_read(display);
+ BOOST_THROW_EXCEPTION((Timeout{"Timeout waiting for condition"}));
+ }
+
+ if (wl_display_read_events(display) < 0)
+ {
+ throw_wayland_error(display);
+ }
+
+ if (wl_display_dispatch_pending(display) < 0)
+ {
+ throw_wayland_error(display);
+ }
+ }
+ }
+
+ void server_roundtrip()
+ {
+ if (wl_display_roundtrip(display) < 0)
+ {
+ throw_wayland_error(display);
+ }
+ }
+
+ struct Output
+ {
+ OutputState current;
+ OutputState pending;
+ std::vector<std::function<void()>> done_notifiers;
+
+ Output(struct wl_output* output)
+ : current{output},
+ pending{output}
+ {
+ }
+
+ static void geometry_thunk(
+ void* ctx,
+ struct wl_output */*wl_output*/,
+ int32_t x,
+ int32_t y,
+ int32_t /*physical_width*/,
+ int32_t /*physical_height*/,
+ int32_t /*subpixel*/,
+ const char */*make*/,
+ const char */*model*/,
+ int32_t /*transform*/)
+ {
+ auto me = static_cast<Output*>(ctx);
+ me->pending.geometry_position = std::make_pair(x, y);
+ }
+
+ static void mode_thunk(
+ void* ctx,
+ struct wl_output */*wl_output*/,
+ uint32_t /*flags*/,
+ int32_t width,
+ int32_t height,
+ int32_t /*refresh*/)
+ {
+ auto me = static_cast<Output*>(ctx);
+ me->pending.mode_size = std::make_pair(width, height);
+ }
+
+ static void done_thunk(void* ctx, struct wl_output */*wl_output*/)
+ {
+ auto me = static_cast<Output*>(ctx);
+
+ if (me->pending.geometry_position)
+ me->current.geometry_position = me->pending.geometry_position;
+ if (me->pending.mode_size)
+ me->current.mode_size = me->pending.mode_size;
+ if (me->pending.scale)
+ me->current.scale = me->pending.scale;
+
+ me->pending = OutputState{me->current.output};
+
+ for (auto const& notifier : me->done_notifiers)
+ notifier();
+ }
+
+ static void scale_thunk(void* ctx, struct wl_output */*wl_output*/, int32_t factor)
+ {
+ auto me = static_cast<Output*>(ctx);
+ me->pending.scale = factor;
+ }
+
+ static constexpr wl_output_listener listener = {
+ &Impl::Output::geometry_thunk,
+ &Impl::Output::mode_thunk,
+ &Impl::Output::done_thunk,
+ &Impl::Output::scale_thunk,
+ };
+ };
+
+ std::vector<std::unique_ptr<Output>> outputs;
+
+private:
+ static void keyboard_keymap(void*, wl_keyboard*, uint32_t, int32_t fd, uint32_t)
+ {
+ close(fd);
+ }
+
+ static void keyboard_enter(
+ void* ctx,
+ wl_keyboard*,
+ uint32_t serial,
+ wl_surface *surface,
+ wl_array* /*keys*/)
+ {
+ auto me = static_cast<Impl*>(ctx);
+ me->keyboard_focused_surface = surface;
+ me->latest_serial_ = serial;
+ }
+
+ static void keyboard_leave(
+ void *ctx,
+ wl_keyboard*,
+ uint32_t serial,
+ wl_surface* surface)
+ {
+ auto me = static_cast<Impl*>(ctx);
+ if (me->keyboard_focused_surface == surface)
+ {
+ me->keyboard_focused_surface = nullptr;
+ }
+ me->latest_serial_ = serial;
+ }
+
+ static void keyboard_key(
+ void* ctx,
+ wl_keyboard*,
+ uint32_t serial,
+ uint32_t /*time*/,
+ uint32_t /*key*/,
+ uint32_t /*state*/)
+ {
+ auto me = static_cast<Impl*>(ctx);
+ me->latest_serial_ = serial;
+ }
+
+ static void keyboard_modifiers(void*, wl_keyboard*, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t)
+ {
+ }
+
+ static void keyboard_repeat_info(void*, wl_keyboard*, int32_t, int32_t)
+ {
+ }
+
+ static constexpr wl_keyboard_listener keyboard_listener = {
+ keyboard_keymap,
+ keyboard_enter,
+ keyboard_leave,
+ keyboard_key,
+ keyboard_modifiers,
+ keyboard_repeat_info,
+ };
+
+ static void pointer_enter(
+ void* ctx,
+ wl_pointer* /*pointer*/,
+ uint32_t serial,
+ wl_surface* surface,
+ wl_fixed_t x,
+ wl_fixed_t y)
+ {
+ auto me = static_cast<Impl*>(ctx);
+ me->latest_serial_ = serial;
+
+ if (me->current_pointer_location && !me->pending_pointer_leave)
+ FAIL()
+ << "Pointer tried to enter surface " << surface
+ << " without first leaving surface " << me->current_pointer_location.value().surface;
+
+ me->pending_pointer_location = SurfaceLocation{
+ surface,
+ std::make_pair(x, y)
+ };
+ }
+
+ static void pointer_leave(
+ void* ctx,
+ wl_pointer* /*pointer*/,
+ uint32_t serial,
+ wl_surface* surface)
+ {
+ auto me = static_cast<Impl*>(ctx);
+ me->latest_serial_ = serial;
+
+ if (!me->current_pointer_location)
+ FAIL() << "Got wl_pointer.leave when the pointer was not on a surface";
+
+ // the surface should never be null along the wire, but may come out as null if it's been destroyed
+ if (surface != nullptr && surface != me->current_pointer_location.value().surface)
+ FAIL()
+ << "Got wl_pointer.leave with surface " << surface
+ << " instead of " << me->current_pointer_location.value().surface;
+
+ me->pending_pointer_location = std::experimental::nullopt;
+ me->pending_pointer_leave = true;
+ }
+
+ static void pointer_motion(
+ void* ctx,
+ wl_pointer* /*pointer*/,
+ uint32_t /*time*/,
+ wl_fixed_t x,
+ wl_fixed_t y)
+ {
+ auto me = static_cast<Impl*>(ctx);
+
+ if (!me->current_pointer_location && !me->pending_pointer_location)
+ FAIL() << "Got wl_pointer.motion when the pointer was not on a surface";
+
+ if (!me->pending_pointer_location)
+ me->pending_pointer_location = me->current_pointer_location;
+ me->pending_pointer_location.value().coordinates = std::make_pair(x, y);
+ }
+
+ static void pointer_button(
+ void *ctx,
+ wl_pointer* /*wl_pointer*/,
+ uint32_t serial,
+ uint32_t /*time*/,
+ uint32_t button,
+ uint32_t state)
+ {
+ auto me = static_cast<Impl*>(ctx);
+ me->latest_serial_ = serial;
+
+ me->pending_buttons[button] = std::make_pair(serial, state == WL_POINTER_BUTTON_STATE_PRESSED);
+ }
+
+ static void pointer_frame(void* ctx, wl_pointer* /*pointer*/)
+ {
+ auto me = static_cast<Impl*>(ctx);
+
+ if (me->pending_pointer_leave)
+ {
+ if (!me->current_pointer_location)
+ FAIL() << "Pointer tried to leave when it was not on a surface";
+
+ wl_surface* old_surface = me->current_pointer_location.value().surface;
+ me->current_pointer_location = std::experimental::nullopt;
+ me->pending_pointer_leave = false;
+
+ me->notify_of_pointer_leave(old_surface);
+ }
+
+ if (me->pending_pointer_location)
+ {
+ auto const old_pointer_location = me->current_pointer_location;
+ me->current_pointer_location = me->pending_pointer_location;
+ me->pending_pointer_location = std::experimental::nullopt;
+
+ if (!old_pointer_location)
+ {
+ me->notify_of_pointer_enter(
+ me->current_pointer_location.value().surface,
+ me->current_pointer_location.value().coordinates);
+ }
+ else
+ {
+ me->notify_of_pointer_motion(me->current_pointer_location.value().coordinates);
+ }
+ }
+
+ if (!me->pending_buttons.empty())
+ {
+ me->notify_of_pointer_buttons(me->pending_buttons);
+ me->pending_buttons.clear();
+ }
+ }
+
+ void notify_of_pointer_enter(wl_surface* surface, std::pair<int, int> position)
+ {
+ std::vector<decltype(enter_notifiers)::const_iterator> to_remove;
+ for (auto notifier = enter_notifiers.begin(); notifier != enter_notifiers.end(); ++notifier)
+ {
+ if (!(*notifier)(surface, position.first, position.second))
+ {
+ to_remove.push_back(notifier);
+ }
+ }
+ for (auto removed : to_remove)
+ {
+ enter_notifiers.erase(removed);
+ }
+ }
+
+ void notify_of_pointer_leave(wl_surface* surface)
+ {
+ std::vector<decltype(leave_notifiers)::const_iterator> to_remove;
+ for (auto notifier = leave_notifiers.begin(); notifier != leave_notifiers.end(); ++notifier)
+ {
+ if (!(*notifier)(surface))
+ {
+ to_remove.push_back(notifier);
+ }
+ }
+ for (auto removed : to_remove)
+ {
+ leave_notifiers.erase(removed);
+ }
+ }
+
+ void notify_of_pointer_motion(std::pair<int, int> position)
+ {
+
+ std::vector<decltype(motion_notifiers)::const_iterator> to_remove;
+ for (auto notifier = motion_notifiers.begin(); notifier != motion_notifiers.end(); ++notifier)
+ {
+ if (!(*notifier)(position.first, position.second))
+ {
+ to_remove.push_back(notifier);
+ }
+ }
+ for (auto removed : to_remove)
+ {
+ motion_notifiers.erase(removed);
+ }
+ }
+
+ void notify_of_pointer_buttons(std::map<uint32_t, std::pair<uint32_t, bool>> const& buttons)
+ {
+ for (auto const& button : buttons)
+ {
+ std::vector<decltype(button_notifiers)::const_iterator> to_remove;
+ for (auto notifier = button_notifiers.begin(); notifier != button_notifiers.end(); ++notifier)
+ {
+ if (!(*notifier)(button.second.first, button.first, button.second.second))
+ {
+ to_remove.push_back(notifier);
+ }
+ }
+ for (auto removed : to_remove)
+ {
+ button_notifiers.erase(removed);
+ }
+ }
+ }
+
+ static constexpr wl_pointer_listener pointer_listener = {
+ &Impl::pointer_enter,
+ &Impl::pointer_leave,
+ &Impl::pointer_motion,
+ &Impl::pointer_button,
+ nullptr, // axis
+ &Impl::pointer_frame, // frame
+ nullptr, // axis_source
+ nullptr, // axis_stop
+ nullptr // axis_discrete
+ };
+
+ static void touch_down(
+ void* ctx,
+ wl_touch* /*wl_touch*/,
+ uint32_t serial,
+ uint32_t /*time*/,
+ wl_surface* surface,
+ int32_t id,
+ wl_fixed_t x,
+ wl_fixed_t y)
+ {
+ auto me = static_cast<Impl*>(ctx);
+ me->latest_serial_ = serial;
+
+ auto touch = me->current_touches.find(id);
+ if (touch != me->current_touches.end())
+ FAIL() << "Got wl_touch.down with ID " << id << " which is already down";
+
+ me->pending_touches[id] = SurfaceLocation {
+ surface,
+ std::make_pair(x,y)
+ };
+ }
+
+ static void touch_up(
+ void* ctx,
+ wl_touch* /*wl_touch*/,
+ uint32_t serial,
+ uint32_t /*time*/,
+ int32_t id)
+ {
+ auto me = static_cast<Impl*>(ctx);
+ me->latest_serial_ = serial;
+
+ auto touch = me->current_touches.find(id);
+ if (touch == me->current_touches.end())
+ FAIL() << "Got wl_touch.up with unknown ID " << id;
+
+ me->pending_up_touches.insert(id);
+ }
+
+ static void touch_motion(
+ void* ctx,
+ wl_touch* /*wl_touch*/,
+ uint32_t /*time*/,
+ int32_t id,
+ wl_fixed_t x,
+ wl_fixed_t y)
+ {
+ auto me = static_cast<Impl*>(ctx);
+
+ auto touch = me->current_touches.find(id);
+ if (touch == me->current_touches.end())
+ FAIL() << "Got wl_touch.up with unknown ID " << id;
+
+ me->pending_touches[id] = SurfaceLocation {
+ touch->second.surface,
+ std::make_pair(x,y)
+ };
+ }
+
+ static void touch_frame(void* ctx, wl_touch* /*touch*/)
+ {
+ auto me = static_cast<Impl*>(ctx);
+
+ for (auto const& id : me->pending_up_touches)
+ {
+ me->current_touches.erase(id);
+ }
+
+ for (auto const& touch : me->pending_touches)
+ {
+ me->current_touches[touch.first] = touch.second;
+ }
+
+ me->pending_up_touches.clear();
+ me->pending_touches.clear();
+ }
+
+ static constexpr wl_touch_listener touch_listener = {
+ &Impl::touch_down,
+ &Impl::touch_up,
+ &Impl::touch_motion,
+ &Impl::touch_frame,
+ nullptr, // cancel
+ nullptr, // shape
+ nullptr, // orientation
+ };
+
+ static void seat_capabilities(
+ void* ctx,
+ struct wl_seat* seat,
+ uint32_t capabilities)
+ {
+ auto me = static_cast<Impl*>(ctx);
+
+ if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD)
+ {
+ me->keyboard = wl_seat_get_keyboard(seat);
+ wl_keyboard_add_listener(me->keyboard, &keyboard_listener, me);
+ }
+
+ if (capabilities & WL_SEAT_CAPABILITY_POINTER)
+ {
+ me->pointer = wl_seat_get_pointer(seat);
+ wl_pointer_add_listener(me->pointer, &pointer_listener, me);
+ }
+
+ if (capabilities & WL_SEAT_CAPABILITY_TOUCH)
+ {
+ me->touch = wl_seat_get_touch(seat);
+ wl_touch_add_listener(me->touch, &touch_listener, me);
+ }
+ }
+
+ static void seat_name(
+ void*,
+ struct wl_seat*,
+ char const*)
+ {
+ }
+
+ static constexpr wl_seat_listener seat_listener = {
+ &Impl::seat_capabilities,
+ &Impl::seat_name
+ };
+
+ static void global_handler(
+ void* ctx,
+ wl_registry* registry,
+ uint32_t id,
+ char const* interface,
+ uint32_t version)
+ {
+ using namespace std::literals::string_literals;
+
+ auto me = static_cast<Impl*>(ctx);
+ me->global_type_names[id] = interface;
+ me->globals[interface] = Global{id, version};
+
+ if ("wl_shm"s == interface)
+ {
+ me->shm = static_cast<struct wl_shm*>(
+ wl_registry_bind(registry, id, &wl_shm_interface, version));
+ }
+ else if ("wl_compositor"s == interface)
+ {
+ me->compositor = static_cast<struct wl_compositor*>(
+ wl_registry_bind(registry, id, &wl_compositor_interface, version));
+ }
+ else if ("wl_subcompositor"s == interface)
+ {
+ me->subcompositor = static_cast<struct wl_subcompositor*>(
+ wl_registry_bind(registry, id, &wl_subcompositor_interface, version));
+ }
+ else if ("wl_shell"s == interface)
+ {
+ me->shell = static_cast<struct wl_shell*>(
+ wl_registry_bind(registry, id, &wl_shell_interface, version));
+ }
+ else if ("wl_seat"s == interface)
+ {
+ me->seat = static_cast<struct wl_seat*>(
+ wl_registry_bind(registry, id, &wl_seat_interface, version));
+
+ wl_seat_add_listener(me->seat, &seat_listener, me);
+ // Ensure we receive the initial seat events.
+ me->server_roundtrip();
+ }
+ else if ("wl_output"s == interface)
+ {
+ auto wl_output = static_cast<struct wl_output*>(
+ wl_registry_bind(registry, id, &wl_output_interface, version));
+ auto output = std::make_unique<Output>(wl_output);
+ wl_output_add_listener(wl_output, &Output::listener, output.get());
+ me->outputs.push_back(move(output));
+
+ // Ensure we receive the initial output events.
+ me->server_roundtrip();
+ }
+ else if ("zxdg_shell_v6"s == interface)
+ {
+ me->xdg_shell_v6 = static_cast<struct zxdg_shell_v6*>(
+ wl_registry_bind(registry, id, &zxdg_shell_v6_interface, version));
+ }
+ else if ("xdg_wm_base"s == interface)
+ {
+ me->xdg_shell_stable = static_cast<struct xdg_wm_base*>(
+ wl_registry_bind(registry, id, &xdg_wm_base_interface, version));
+ }
+ }
+
+ static void global_removed(void* ctx, wl_registry*, uint32_t id)
+ {
+ auto me = static_cast<Impl*>(ctx);
+ auto const name = me->global_type_names.find(id);
+ if (name != me->global_type_names.end())
+ {
+ me->globals.erase(name->second);
+ me->global_type_names.erase(name);
+ }
+ }
+
+ constexpr static wl_registry_listener registry_listener = {
+ &global_handler,
+ &global_removed
+ };
+
+ std::shared_ptr<std::unordered_map<std::string, uint32_t> const> const supported_extensions;
+
+ struct wl_display* display;
+ struct wl_registry* registry = nullptr;
+ struct wl_compositor* compositor = nullptr;
+ struct wl_subcompositor* subcompositor = nullptr;
+ struct wl_shm* shm = nullptr;
+ struct wl_shell* shell = nullptr;
+ struct wl_seat* seat = nullptr;
+ struct wl_keyboard* keyboard = nullptr;
+ struct wl_pointer* pointer = nullptr;
+ struct wl_touch* touch = nullptr;
+ struct zxdg_shell_v6* xdg_shell_v6 = nullptr;
+ std::vector<std::function<void()>> destruction_callbacks;
+ struct xdg_wm_base* xdg_shell_stable = nullptr;
+
+ struct Global
+ {
+ uint32_t id;
+ uint32_t version;
+ };
+ std::map<std::string, Global> globals;
+ std::map<uint32_t, std::string> global_type_names;
+
+ struct SurfaceLocation
+ {
+ wl_surface* surface;
+ std::pair<wl_fixed_t, wl_fixed_t> coordinates;
+ };
+ wl_surface* keyboard_focused_surface = nullptr;
+ std::experimental::optional<SurfaceLocation> current_pointer_location;
+ std::experimental::optional<SurfaceLocation> pending_pointer_location;
+ bool pending_pointer_leave{false};
+ std::map<uint32_t, std::pair<uint32_t, bool>> pending_buttons; ///< Maps button id to the serial and if it's down
+ std::map<int, SurfaceLocation> current_touches; ///< Touches that have gotten a frame event
+ std::map<int, SurfaceLocation> pending_touches; ///< Touches that have gotten down or motion events without a frame
+ std::set<int> pending_up_touches; ///< Touches that have gotten up events without a frame
+ std::experimental::optional<uint32_t> latest_serial_;
+
+ std::vector<PointerEnterNotifier> enter_notifiers;
+ std::vector<PointerLeaveNotifier> leave_notifiers;
+ std::vector<PointerMotionNotifier> motion_notifiers;
+ std::vector<PointerButtonNotifier> button_notifiers;
+};
+
+constexpr wl_keyboard_listener wlcs::Client::Impl::keyboard_listener;
+constexpr wl_pointer_listener wlcs::Client::Impl::pointer_listener;
+constexpr wl_touch_listener wlcs::Client::Impl::touch_listener;
+constexpr wl_seat_listener wlcs::Client::Impl::seat_listener;
+constexpr wl_output_listener wlcs::Client::Impl::Output::listener;
+constexpr wl_registry_listener wlcs::Client::Impl::registry_listener;
+
+wlcs::Client::Client(Server& server)
+ : impl{std::make_unique<Impl>(server)}
+{
+}
+
+wlcs::Client::~Client() = default;
+
+wlcs::Client::operator wl_display*() const
+{
+ return impl->wl_display();
+}
+
+wl_compositor* wlcs::Client::compositor() const
+{
+ return impl->wl_compositor();
+}
+
+wl_subcompositor* wlcs::Client::subcompositor() const
+{
+ return impl->wl_subcompositor();
+}
+
+wl_shm* wlcs::Client::shm() const
+{
+ return impl->wl_shm();
+}
+
+wl_seat* wlcs::Client::seat() const
+{
+ return impl->wl_seat();
+}
+
+void wlcs::Client::run_on_destruction(std::function<void()> callback)
+{
+ impl->run_on_destruction(callback);
+}
+
+wlcs::ShmBuffer const& wlcs::Client::create_buffer(int width, int height)
+{
+ return impl->create_buffer(*this, width, height);
+}
+
+wlcs::Surface wlcs::Client::create_wl_shell_surface(int width, int height)
+{
+ return impl->create_wl_shell_surface(*this, width, height);
+}
+
+wlcs::Surface wlcs::Client::create_xdg_shell_v6_surface(int width, int height)
+{
+ return impl->create_xdg_shell_v6_surface(*this, width, height);
+}
+
+wlcs::Surface wlcs::Client::create_xdg_shell_stable_surface(int width, int height)
+{
+ return impl->create_xdg_shell_stable_surface(*this, width, height);
+}
+
+wlcs::Surface wlcs::Client::create_visible_surface(int width, int height)
+{
+ return impl->create_wl_shell_surface(*this, width, height);
+}
+
+size_t wlcs::Client::output_count() const
+{
+ return impl->outputs.size();
+}
+
+wlcs::OutputState wlcs::Client::output_state(size_t index) const
+{
+ if (index > output_count())
+ throw std::out_of_range("Invalid output index");
+
+ return impl->outputs[index]->current;
+}
+
+void wlcs::Client::add_output_done_notifier(size_t index, std::function<void()> const& notifier)
+{
+ if (index > output_count())
+ throw std::runtime_error("Invalid output index");
+
+ impl->outputs[index]->done_notifiers.push_back(notifier);
+}
+
+wl_shell* wlcs::Client::shell() const
+{
+ return impl->the_shell();
+}
+
+zxdg_shell_v6* wlcs::Client::xdg_shell_v6() const
+{
+ return impl->the_xdg_shell_v6();
+}
+
+xdg_wm_base* wlcs::Client::xdg_shell_stable() const
+{
+ return impl->the_xdg_shell_stable();
+}
+
+wl_surface* wlcs::Client::keyboard_focused_window() const
+{
+ return impl->keyboard_focused_window();
+}
+
+wl_surface* wlcs::Client::window_under_cursor() const
+{
+ return impl->window_under_cursor();
+}
+
+wl_surface* wlcs::Client::touched_window() const
+{
+ return impl->touched_window();
+}
+
+std::pair<wl_fixed_t, wl_fixed_t> wlcs::Client::pointer_position() const
+{
+ return impl->pointer_position();
+}
+
+std::pair<wl_fixed_t, wl_fixed_t> wlcs::Client::touch_position() const
+{
+ return impl->touch_position();
+}
+
+std::experimental::optional<uint32_t> wlcs::Client::latest_serial() const
+{
+ return impl->latest_serial();
+}
+
+void wlcs::Client::add_pointer_enter_notification(PointerEnterNotifier const& on_enter)
+{
+ impl->add_pointer_enter_notification(on_enter);
+}
+
+void wlcs::Client::add_pointer_leave_notification(PointerLeaveNotifier const& on_leave)
+{
+ impl->add_pointer_leave_notification(on_leave);
+}
+
+void wlcs::Client::add_pointer_motion_notification(PointerMotionNotifier const& on_motion)
+{
+ impl->add_pointer_motion_notification(on_motion);
+}
+
+void wlcs::Client::add_pointer_button_notification(PointerButtonNotifier const& on_button)
+{
+ impl->add_pointer_button_notification(on_button);
+}
+
+void wlcs::Client::dispatch_until(std::function<bool()> const& predicate, std::chrono::seconds timeout)
+{
+ impl->dispatch_until(predicate, timeout);
+}
+
+void wlcs::Client::roundtrip()
+{
+ impl->server_roundtrip();
+}
+
+void* wlcs::Client::bind_if_supported(wl_interface const& interface, VersionSpecifier const& version) const
+{
+ return impl->bind_if_supported(interface, version);
+}
+
+wl_pointer* wlcs::Client::the_pointer() const
+{
+ return impl->the_pointer();
+}
+
+class wlcs::Surface::Impl
+{
+public:
+ Impl(Client& client)
+ : surface_{wl_compositor_create_surface(client.compositor())},
+ owner_{client}
+ {
+ wl_surface_add_listener(surface_, &surface_listener, this);
+ }
+
+ ~Impl()
+ {
+ for (auto i = 0u; i < pending_callbacks.size(); )
+ {
+ if (pending_callbacks[i].first == this)
+ {
+ auto pending_callback = pending_callbacks[i].second;
+ delete static_cast<std::function<void(uint32_t)>*>(wl_callback_get_user_data(pending_callback));
+
+ wl_callback_destroy(pending_callback);
+ pending_callbacks.erase(pending_callbacks.begin() + i);
+ }
+ else
+ {
+ ++i;
+ }
+ }
+
+ for (auto const& callback: destruction_callbacks)
+ callback();
+ destruction_callbacks.clear();
+
+ wl_surface_destroy(surface_);
+ }
+
+ wl_surface* surface() const
+ {
+ return surface_;
+ }
+
+ void attach_buffer(int width, int height)
+ {
+ auto& buffer = owner_.create_buffer(width, height);
+ wl_surface_attach(surface_, buffer, 0, 0);
+ }
+
+ void add_frame_callback(std::function<void(uint32_t)> const& on_frame)
+ {
+ std::unique_ptr<std::function<void(uint32_t)>> holder{
+ new std::function<void(uint32_t)>(on_frame)};
+
+ auto callback = wl_surface_frame(surface_);
+
+ pending_callbacks.push_back(std::make_pair(this, callback));
+
+ wl_callback_add_listener(callback, &frame_listener, holder.release());
+ }
+
+ void attach_visible_buffer(int width, int height)
+ {
+ attach_buffer(width, height);
+ auto surface_rendered = std::make_shared<bool>(false);
+ add_frame_callback([surface_rendered](auto) { *surface_rendered = true; });
+ wl_surface_commit(surface_);
+ owner_.dispatch_until([surface_rendered]() { return *surface_rendered; });
+ }
+
+ void run_on_destruction(std::function<void()> callback)
+ {
+ destruction_callbacks.push_back(callback);
+ }
+
+ Client& owner() const
+ {
+ return owner_;
+ }
+
+ auto current_outputs() -> std::set<wl_output*> const&
+ {
+ return outputs;
+ }
+
+private:
+
+ static std::vector<std::pair<Impl const*, wl_callback*>> pending_callbacks;
+ std::set<wl_output*> outputs;
+
+ static void frame_callback(void* ctx, wl_callback* callback, uint32_t frame_time)
+ {
+ auto us = std::find_if(
+ pending_callbacks.begin(),
+ pending_callbacks.end(),
+ [callback](auto const& elem)
+ {
+ return elem.second == callback;
+ });
+ pending_callbacks.erase(us);
+
+ auto frame_callback = static_cast<std::function<void(uint32_t)>*>(ctx);
+
+ (*frame_callback)(frame_time);
+
+ wl_callback_destroy(callback);
+ delete frame_callback;
+ }
+
+ static constexpr wl_callback_listener frame_listener = {
+ &frame_callback
+ };
+
+ static void on_enter(void* data, wl_surface* /*wl_surface*/, wl_output* output)
+ {
+ auto const self = static_cast<Impl*>(data);
+
+ auto const inserted = self->outputs.insert(output);
+
+ if (!inserted.second)
+ {
+ BOOST_THROW_EXCEPTION(std::runtime_error(
+ "Got wl_surface.enter(wl_output@" +
+ std::to_string(wl_proxy_get_id(reinterpret_cast<wl_proxy*>(output))) +
+ ") for an output the surface is already on"));
+ }
+ }
+
+ static void on_leave(void* data, wl_surface* /*wl_surface*/, wl_output* output)
+ {
+ auto const self = static_cast<Impl*>(data);
+
+ auto const erased = self->outputs.erase(output);
+
+ if (!erased)
+ {
+ BOOST_THROW_EXCEPTION(std::runtime_error(
+ "Got wl_surface.leave(wl_output@" +
+ std::to_string(wl_proxy_get_id(reinterpret_cast<wl_proxy*>(output))) +
+ ") for an output the surface is not on"));
+ }
+ }
+
+ static constexpr wl_surface_listener surface_listener{
+ &on_enter,
+ &on_leave,
+ };
+
+ struct wl_surface* const surface_;
+ Client& owner_;
+ std::vector<std::function<void()>> destruction_callbacks;
+};
+
+std::vector<std::pair<wlcs::Surface::Impl const*, wl_callback*>> wlcs::Surface::Impl::pending_callbacks;
+
+constexpr wl_callback_listener wlcs::Surface::Impl::frame_listener;
+constexpr wl_surface_listener wlcs::Surface::Impl::surface_listener;
+
+wlcs::Surface::Surface(Client& client)
+ : impl{std::make_unique<Impl>(client)}
+{
+}
+
+wlcs::Surface::~Surface() = default;
+
+wlcs::Surface::Surface(Surface&&) = default;
+
+wlcs::Surface::operator wl_surface*() const
+{
+ return impl->surface();
+}
+
+void wlcs::Surface::attach_buffer(int width, int height)
+{
+ impl->attach_buffer(width, height);
+}
+
+void wlcs::Surface::add_frame_callback(std::function<void(int)> const& on_frame)
+{
+ impl->add_frame_callback(on_frame);
+}
+
+void wlcs::Surface::attach_visible_buffer(int width, int height)
+{
+ impl->attach_visible_buffer(width, height);
+}
+
+void wlcs::Surface::run_on_destruction(std::function<void()> callback)
+{
+ impl->run_on_destruction(callback);
+}
+
+wlcs::Client& wlcs::Surface::owner() const
+{
+ return impl->owner();
+}
+
+auto wlcs::Surface:: current_outputs() -> std::set<wl_output*> const&
+{
+ return impl->current_outputs();
+}
+
+class wlcs::Subsurface::Impl
+{
+public:
+ Impl(Client& client, Surface& surface, Surface& parent)
+ : subsurface_{wl_subcompositor_get_subsurface(client.subcompositor(), surface, parent)},
+ parent_{parent}
+ {
+ }
+
+ ~Impl()
+ {
+ wl_subsurface_destroy(subsurface_);
+ }
+
+ struct wl_subsurface* const subsurface_;
+ Surface& parent_;
+};
+
+wlcs::Subsurface wlcs::Subsurface::create_visible(Surface& parent, int x, int y, int width, int height)
+{
+ wlcs::Subsurface subsurface{parent};
+ wl_subsurface_set_position(subsurface, x, y);
+
+ subsurface.attach_buffer(width, height);
+
+ bool surface_rendered{false};
+ subsurface.add_frame_callback([&surface_rendered](auto) { surface_rendered = true; });
+ wlcs::Surface* surface_ptr = &subsurface;
+ while (surface_ptr)
+ {
+ wl_surface_commit(*surface_ptr);
+ auto subsurface_ptr = dynamic_cast<wlcs::Subsurface*>(surface_ptr);
+ if (subsurface_ptr)
+ surface_ptr = &subsurface_ptr->parent();
+ else
+ surface_ptr = nullptr;
+ }
+ parent.owner().dispatch_until([&surface_rendered]() { return surface_rendered; });
+
+ return subsurface;
+}
+
+wlcs::Subsurface::Subsurface(wlcs::Surface& parent)
+ : Surface{parent.owner()},
+ impl{std::make_unique<wlcs::Subsurface::Impl>(parent.owner(), *this, parent)}
+{
+}
+
+wlcs::Subsurface::Subsurface(Subsurface &&) = default;
+
+wlcs::Subsurface::~Subsurface() = default;
+
+wlcs::Subsurface::operator wl_subsurface*() const
+{
+ return impl->subsurface_;
+}
+
+wlcs::Surface& wlcs::Subsurface::parent() const
+{
+ return impl->parent_;
+}
+
+class wlcs::ShmBuffer::Impl
+{
+public:
+ Impl(Client& client, int width, int height)
+ {
+ auto stride = width * 4;
+ auto size = stride * height;
+ auto fd = wlcs::helpers::create_anonymous_file(size);
+
+ auto pool = wl_shm_create_pool(client.shm(), fd, size);
+ buffer_ = wl_shm_pool_create_buffer(
+ pool,
+ 0,
+ width,
+ height,
+ stride,
+ WL_SHM_FORMAT_ARGB8888);
+ wl_shm_pool_destroy(pool);
+ close(fd);
+
+ wl_buffer_add_listener(buffer_, &listener, this);
+ }
+
+ ~Impl()
+ {
+ wl_buffer_destroy(buffer_);
+ }
+
+ wl_buffer* buffer() const
+ {
+ return buffer_;
+ }
+
+ void add_release_listener(std::function<bool()> const& on_release)
+ {
+ release_notifiers.push_back(on_release);
+ }
+
+private:
+ static void on_release(void* ctx, wl_buffer* /*buffer*/)
+ {
+ auto me = static_cast<Impl*>(ctx);
+
+ std::vector<decltype(me->release_notifiers.begin())> expired_notifiers;
+
+ for (auto notifier = me->release_notifiers.begin(); notifier != me->release_notifiers.end(); ++notifier)
+ {
+ if (!(*notifier)())
+ {
+ expired_notifiers.push_back(notifier);
+ }
+ }
+ for (auto const& expired : expired_notifiers)
+ me->release_notifiers.erase(expired);
+ }
+
+ static constexpr wl_buffer_listener listener {
+ &on_release
+ };
+
+ wl_buffer* buffer_;
+ std::vector<std::function<bool()>> release_notifiers;
+};
+
+constexpr wl_buffer_listener wlcs::ShmBuffer::Impl::listener;
+
+wlcs::ShmBuffer::ShmBuffer(Client &client, int width, int height)
+ : impl{std::make_unique<Impl>(client, width, height)}
+{
+}
+
+wlcs::ShmBuffer::ShmBuffer(ShmBuffer&&) = default;
+wlcs::ShmBuffer::~ShmBuffer() = default;
+
+wlcs::ShmBuffer::operator wl_buffer*() const
+{
+ return impl->buffer();
+}
+
+void wlcs::ShmBuffer::add_release_listener(std::function<bool()> const &on_release)
+{
+ impl->add_release_listener(on_release);
+}
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "input_method.h"
+#include <gmock/gmock.h>
+
+auto wlcs::InputMethod::all_input_methods() -> std::vector<std::shared_ptr<InputMethod>>
+{
+ return {
+ std::make_shared<PointerInputMethod>(),
+ std::make_shared<TouchInputMethod>()};
+}
+
+struct wlcs::PointerInputMethod::Pointer : Device
+{
+ Pointer(wlcs::Server& server)
+ : pointer{server.create_pointer()}
+ {
+ }
+
+ void down_at(std::pair<int, int> position) override
+ {
+ ASSERT_THAT(button_down, testing::Eq(false)) << "Called down_at() with pointer already down";
+ pointer.move_to(position.first, position.second);
+ pointer.left_button_down();
+ button_down = true;
+ }
+
+ void move_to(std::pair<int, int> position) override
+ {
+ ASSERT_THAT(button_down, testing::Eq(true)) << "Called move_to() with pointer up";
+ pointer.move_to(position.first, position.second);
+ }
+
+ void up() override
+ {
+ ASSERT_THAT(button_down, testing::Eq(true)) << "Called up() with pointer already up";
+ pointer.left_button_up();
+ button_down = false;
+ }
+
+ wlcs::Pointer pointer;
+ bool button_down = false;
+};
+
+auto wlcs::PointerInputMethod::create_device(wlcs::Server& server) -> std::unique_ptr<Device>
+{
+ return std::make_unique<Pointer>(server);
+}
+
+auto wlcs::PointerInputMethod::current_surface(wlcs::Client const& client) -> wl_surface*
+{
+ return client.window_under_cursor();
+}
+
+auto wlcs::PointerInputMethod::position_on_surface(wlcs::Client const& client) -> std::pair<int, int>
+{
+ auto const wl_fixed_position = client.pointer_position();
+ return {
+ wl_fixed_to_int(wl_fixed_position.first),
+ wl_fixed_to_int(wl_fixed_position.second)};
+}
+
+struct wlcs::TouchInputMethod::Touch : Device
+{
+ Touch(wlcs::Server& server)
+ : touch{server.create_touch()}
+ {
+ }
+
+ void down_at(std::pair<int, int> position) override
+ {
+ ASSERT_THAT(is_down, testing::Eq(false)) << "Called down_at() with touch already down";
+ touch.down_at(position.first, position.second);
+ is_down = true;
+ }
+
+ void move_to(std::pair<int, int> position) override
+ {
+ ASSERT_THAT(is_down, testing::Eq(true)) << "Called move_to() with touch up";
+ touch.move_to(position.first, position.second);
+ }
+
+ void up() override
+ {
+ ASSERT_THAT(is_down, testing::Eq(true)) << "Called up() with touch already up";
+ touch.up();
+ is_down = false;
+ }
+
+ wlcs::Touch touch;
+ bool is_down = false;
+};
+
+auto wlcs::TouchInputMethod::create_device(wlcs::Server& server) -> std::unique_ptr<Device>
+{
+ return std::make_unique<Touch>(server);
+}
+
+auto wlcs::TouchInputMethod::current_surface(wlcs::Client const& client) -> wl_surface*
+{
+ return client.touched_window();
+}
+
+auto wlcs::TouchInputMethod::position_on_surface(wlcs::Client const& client) -> std::pair<int, int>
+{
+ auto const wl_fixed_position = client.touch_position();
+ return {
+ wl_fixed_to_int(wl_fixed_position.first),
+ wl_fixed_to_int(wl_fixed_position.second)};
+}
+
+std::ostream& std::operator<<(std::ostream& out, std::shared_ptr<wlcs::InputMethod> const& param)
+{
+ return out << param->name;
+}
--- /dev/null
+/*
+ * Copyright © 2018-2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "layer_shell_v1.h"
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+wlcs::LayerSurfaceV1::LayerSurfaceV1(
+ wlcs::Client& client,
+ wlcs::Surface& surface,
+ zwlr_layer_shell_v1_layer layer,
+ wl_output* output,
+ const char *_namespace)
+ : client{client},
+ layer_shell{client.bind_if_supported<zwlr_layer_shell_v1>(AnyVersion)},
+ layer_surface{wrap_wl_object(
+ zwlr_layer_shell_v1_get_layer_surface(
+ layer_shell,
+ surface,
+ output,
+ layer,
+ _namespace))}
+{
+ static struct zwlr_layer_surface_v1_listener const listener {
+ [](void* data,
+ struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1,
+ uint32_t serial,
+ uint32_t width,
+ uint32_t height)
+ {
+ auto self = static_cast<LayerSurfaceV1*>(data);
+ self->last_width_ = (int)width;
+ self->last_height_ = (int)height;
+ self->configure_count++;
+ (void)zwlr_layer_surface_v1;
+ (void)serial;
+ zwlr_layer_surface_v1_ack_configure(zwlr_layer_surface_v1, serial);
+ },
+ [](void* /*data*/,
+ struct zwlr_layer_surface_v1 */*zwlr_layer_surface_v1*/)
+ {
+ }
+ };
+ zwlr_layer_surface_v1_add_listener(layer_surface, &listener, this);
+}
+
+void wlcs::LayerSurfaceV1::dispatch_until_configure()
+{
+ client.dispatch_until([prev_config_count = configure_count, ¤t_config_count = configure_count]
+ {
+ return current_config_count > prev_config_count;
+ });
+}
--- /dev/null
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include <gtest/gtest.h>
+#include <iostream>
+#include <memory>
+
+#include <dlfcn.h>
+
+#include "xfail_supporting_test_listener.h"
+#include "shared_library.h"
+#include "wlcs/display_server.h"
+
+#include "helpers.h"
+
+
+int main(int argc, char** argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+
+ if (argc < 2 || !argv[1] || std::string{"--help"} == argv[1])
+ {
+ std::cerr
+ << "WayLand Conformance Suite test runner" << std::endl
+ << "Usage: " << argv[0] << " COMPOSITOR_INTEGRATION_MODULE [GTEST OPTIONS]... [COMPOSITOR_OPTIONS]..." << std::endl;
+ return 1;
+ }
+
+ auto const integration_filename = argv[1];
+
+ // Shuffle the integration module argument out of argv
+ for (auto i = 1 ; i < (argc - 1) ; ++i)
+ {
+ argv[i] = argv[i + 1];
+ }
+ wlcs::helpers::set_command_line(argc - 1, const_cast<char const**>(argv));
+
+ std::shared_ptr<wlcs::SharedLibrary> dso;
+ try
+ {
+ dso = std::make_shared<wlcs::SharedLibrary>(integration_filename);
+ }
+ catch (std::exception const& err)
+ {
+ std::cerr
+ << "Failed to load compositor integration module " << integration_filename << ": " << err.what() << std::endl;
+ return 1;
+ }
+
+ std::shared_ptr<WlcsServerIntegration const> entry_point;
+ try
+ {
+ entry_point = std::shared_ptr<WlcsServerIntegration const>{
+ dso,
+ dso->load_function<WlcsServerIntegration const*>("wlcs_server_integration")
+ };
+ }
+ catch (std::exception const& err)
+ {
+ std::cerr
+ << "Failed to load compositor entry point: " << err.what() << std::endl;
+ return 1;
+ }
+
+ wlcs::helpers::set_entry_point(entry_point);
+
+ auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
+ auto wrapping_listener = new testing::XFailSupportingTestListenerWrapper{
+ std::unique_ptr<::testing::TestEventListener>{
+ listeners.Release(listeners.default_result_printer())}};
+ listeners.Append(wrapping_listener);
+
+ /* (void)! is apparently the magical incantation required to get GCC to
+ * *actually* silently ignore the return value of a function declared with
+ * __attribute__(("warn_unused_result"))
+ * cf: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425
+ */
+ (void)!RUN_ALL_TESTS();
+ if (wrapping_listener->failed())
+ {
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
+
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "pointer_constraints_unstable_v1.h"
+
+#include "in_process_server.h"
+#include <version_specifier.h>
+
+wlcs::ZwpPointerConstraintsV1::ZwpPointerConstraintsV1(Client& client) :
+ manager{client.bind_if_supported<zwp_pointer_constraints_v1>(AnyVersion)}
+{
+}
+
+wlcs::ZwpPointerConstraintsV1::~ZwpPointerConstraintsV1() = default;
+
+wlcs::ZwpPointerConstraintsV1::operator zwp_pointer_constraints_v1*() const
+{
+ return manager;
+}
+
+wlcs::ZwpConfinedPointerV1::ZwpConfinedPointerV1(
+ ZwpPointerConstraintsV1& manager,
+ wl_surface* surface,
+ wl_pointer* pointer,
+ wl_region* region,
+ uint32_t lifetime) :
+ relative_pointer{zwp_pointer_constraints_v1_confine_pointer(manager, surface, pointer, region, lifetime)},
+ version{zwp_confined_pointer_v1_get_version(relative_pointer)}
+{
+ zwp_confined_pointer_v1_set_user_data(relative_pointer, this);
+ zwp_confined_pointer_v1_add_listener(relative_pointer, &listener, this);
+}
+
+wlcs::ZwpConfinedPointerV1::~ZwpConfinedPointerV1()
+{
+ zwp_confined_pointer_v1_destroy(relative_pointer);
+}
+
+wlcs::ZwpConfinedPointerV1::operator zwp_confined_pointer_v1*() const
+{
+ return relative_pointer;
+}
+
+zwp_confined_pointer_v1_listener const wlcs::ZwpConfinedPointerV1::listener =
+ {
+ [](void* self, auto*) { static_cast<ZwpConfinedPointerV1*>(self)->confined(); },
+ [](void* self, auto*) { static_cast<ZwpConfinedPointerV1*>(self)->unconfined(); },
+ };
+
+
+wlcs::ZwpLockedPointerV1::ZwpLockedPointerV1(
+ ZwpPointerConstraintsV1& manager,
+ wl_surface* surface,
+ wl_pointer* pointer,
+ wl_region* region,
+ uint32_t lifetime) :
+ locked_pointer{zwp_pointer_constraints_v1_lock_pointer(manager, surface, pointer, region, lifetime)},
+ version{zwp_locked_pointer_v1_get_version(locked_pointer)}
+{
+ zwp_locked_pointer_v1_set_user_data(locked_pointer, this);
+ zwp_locked_pointer_v1_add_listener(locked_pointer, &listener, this);
+}
+
+wlcs::ZwpLockedPointerV1::~ZwpLockedPointerV1()
+{
+ zwp_locked_pointer_v1_destroy(locked_pointer);
+}
+
+wlcs::ZwpLockedPointerV1::operator zwp_locked_pointer_v1*() const
+{
+ return locked_pointer;
+}
+
+zwp_locked_pointer_v1_listener const wlcs::ZwpLockedPointerV1::listener =
+ {
+ [](void* self, auto*) { static_cast<ZwpLockedPointerV1*>(self)->locked(); },
+ [](void* self, auto*) { static_cast<ZwpLockedPointerV1*>(self)->unlocked(); },
+ };
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "primary_selection.h"
+
+#include <unistd.h>
+
+wlcs::ActiveListeners wlcs::PrimarySelectionDeviceListener::active_listeners;
+constexpr zwp_primary_selection_device_v1_listener wlcs::PrimarySelectionDeviceListener::thunks;
+
+wlcs::ActiveListeners wlcs::PrimarySelectionOfferListener::active_listeners;
+constexpr zwp_primary_selection_offer_v1_listener wlcs::PrimarySelectionOfferListener::thunks;
+
+wlcs::ActiveListeners wlcs::PrimarySelectionSourceListener::active_listeners;
+constexpr zwp_primary_selection_source_v1_listener wlcs::PrimarySelectionSourceListener::thunks;
+
+
+void wlcs::PrimarySelectionDeviceListener::data_offer(
+ void* data,
+ zwp_primary_selection_device_v1* device,
+ zwp_primary_selection_offer_v1* offer)
+{
+ if (active_listeners.includes(data))
+ static_cast<PrimarySelectionDeviceListener*>(data)->data_offer(device, offer);
+}
+
+void wlcs::PrimarySelectionDeviceListener::data_offer(zwp_primary_selection_device_v1*, zwp_primary_selection_offer_v1*)
+{
+}
+
+void wlcs::PrimarySelectionDeviceListener::selection(zwp_primary_selection_device_v1*, zwp_primary_selection_offer_v1*)
+{
+}
+
+void wlcs::PrimarySelectionDeviceListener::selection(
+ void* data,
+ zwp_primary_selection_device_v1* device,
+ zwp_primary_selection_offer_v1* offer)
+{
+ if (active_listeners.includes(data))
+ static_cast<PrimarySelectionDeviceListener*>(data)->selection(device, offer);
+}
+
+void wlcs::PrimarySelectionOfferListener::offer(zwp_primary_selection_offer_v1*, const char*)
+{
+}
+
+void wlcs::PrimarySelectionOfferListener::offer(
+ void* data, zwp_primary_selection_offer_v1* offer, const char* mime_type)
+{
+ if (active_listeners.includes(data))
+ static_cast<PrimarySelectionOfferListener*>(data)->offer(offer, mime_type);
+}
+
+void wlcs::PrimarySelectionSourceListener::send(zwp_primary_selection_source_v1*, const char*, int32_t fd)
+{
+ close(fd);
+}
+
+void wlcs::PrimarySelectionSourceListener::cancelled(zwp_primary_selection_source_v1*)
+{
+}
+
+void wlcs::PrimarySelectionSourceListener::send(
+ void* data, zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd)
+{
+ if (active_listeners.includes(data))
+ static_cast<PrimarySelectionSourceListener*>(data)->send(source, mime_type, fd);
+}
+
+void wlcs::PrimarySelectionSourceListener::cancelled(void* data, zwp_primary_selection_source_v1* source)
+{
+ if (active_listeners.includes(data))
+ static_cast<PrimarySelectionSourceListener*>(data)->cancelled(source);
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="gtk_primary_selection">
+ <copyright>
+ Copyright © 2015, 2016 Red Hat
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="Primary selection protocol">
+ This protocol provides the ability to have a primary selection device to
+ match that of the X server. This primary selection is a shortcut to the
+ common clipboard selection, where text just needs to be selected in order
+ to allow copying it elsewhere. The de facto way to perform this action
+ is the middle mouse button, although it is not limited to this one.
+
+ Clients wishing to honor primary selection should create a primary
+ selection source and set it as the selection through
+ wp_primary_selection_device.set_selection whenever the text selection
+ changes. In order to minimize calls in pointer-driven text selection,
+ it should happen only once after the operation finished. Similarly,
+ a NULL source should be set when text is unselected.
+
+ wp_primary_selection_offer objects are first announced through the
+ wp_primary_selection_device.data_offer event. Immediately after this event,
+ the primary data offer will emit wp_primary_selection_offer.offer events
+ to let know of the mime types being offered.
+
+ When the primary selection changes, the client with the keyboard focus
+ will receive wp_primary_selection_device.selection events. Only the client
+ with the keyboard focus will receive such events with a non-NULL
+ wp_primary_selection_offer. Across keyboard focus changes, previously
+ focused clients will receive wp_primary_selection_device.events with a
+ NULL wp_primary_selection_offer.
+
+ In order to request the primary selection data, the client must pass
+ a recent serial pertaining to the press event that is triggering the
+ operation, if the compositor deems the serial valid and recent, the
+ wp_primary_selection_source.send event will happen in the other end
+ to let the transfer begin. The client owning the primary selection
+ should write the requested data, and close the file descriptor
+ immediately.
+
+ If the primary selection owner client disappeared during the transfer,
+ the client reading the data will receive a
+ wp_primary_selection_device.selection event with a NULL
+ wp_primary_selection_offer, the client should take this as a hint
+ to finish the reads related to the no longer existing offer.
+
+ The primary selection owner should be checking for errors during
+ writes, merely cancelling the ongoing transfer if any happened.
+ </description>
+
+ <interface name="gtk_primary_selection_device_manager" version="1">
+ <description summary="X primary selection emulation">
+ The primary selection device manager is a singleton global object that
+ provides access to the primary selection. It allows to create
+ wp_primary_selection_source objects, as well as retrieving the per-seat
+ wp_primary_selection_device objects.
+ </description>
+
+ <request name="create_source">
+ <description summary="create a new primary selection source">
+ Create a new primary selection source.
+ </description>
+ <arg name="id" type="new_id" interface="gtk_primary_selection_source"/>
+ </request>
+
+ <request name="get_device">
+ <description summary="create a new primary selection device">
+ Create a new data device for a given seat.
+ </description>
+ <arg name="id" type="new_id" interface="gtk_primary_selection_device"/>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection device manager">
+ Destroy the primary selection device manager.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="gtk_primary_selection_device" version="1">
+ <request name="set_selection">
+ <description summary="set the primary selection">
+ Replaces the current selection. The previous owner of the primary selection
+ will receive a wp_primary_selection_source.cancelled event.
+
+ To unset the selection, set the source to NULL.
+ </description>
+ <arg name="source" type="object" interface="gtk_primary_selection_source" allow-null="true"/>
+ <arg name="serial" type="uint" summary="serial of the event that triggered this request"/>
+ </request>
+
+ <event name="data_offer">
+ <description summary="introduce a new wp_primary_selection_offer">
+ Introduces a new wp_primary_selection_offer object that may be used
+ to receive the current primary selection. Immediately following this
+ event, the new wp_primary_selection_offer object will send
+ wp_primary_selection_offer.offer events to describe the offered mime
+ types.
+ </description>
+ <arg name="offer" type="new_id" interface="gtk_primary_selection_offer"/>
+ </event>
+
+ <event name="selection">
+ <description summary="advertise a new primary selection">
+ The wp_primary_selection_device.selection event is sent to notify the
+ client of a new primary selection. This event is sent after the
+ wp_primary_selection.data_offer event introducing this object, and after
+ the offer has announced its mimetypes through
+ wp_primary_selection_offer.offer.
+
+ The data_offer is valid until a new offer or NULL is received
+ or until the client loses keyboard focus. The client must destroy the
+ previous selection data_offer, if any, upon receiving this event.
+ </description>
+ <arg name="id" type="object" interface="gtk_primary_selection_offer" allow-null="true"/>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection device">
+ Destroy the primary selection device.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="gtk_primary_selection_offer" version="1">
+ <description summary="offer to transfer primary selection contents">
+ A wp_primary_selection_offer represents an offer to transfer the contents
+ of the primary selection clipboard to the client. Similar to
+ wl_data_offer, the offer also describes the mime types that the source
+ will transferthat the
+ data can be converted to and provides the mechanisms for transferring the
+ data directly to the client.
+ </description>
+
+ <request name="receive">
+ <description summary="request that the data is transferred">
+ To transfer the contents of the primary selection clipboard, the client
+ issues this request and indicates the mime type that it wants to
+ receive. The transfer happens through the passed file descriptor
+ (typically created with the pipe system call). The source client writes
+ the data in the mime type representation requested and then closes the
+ file descriptor.
+
+ The receiving client reads from the read end of the pipe until EOF and
+ closes its end, at which point the transfer is complete.
+ </description>
+ <arg name="mime_type" type="string"/>
+ <arg name="fd" type="fd"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection offer">
+ Destroy the primary selection offer.
+ </description>
+ </request>
+
+ <event name="offer">
+ <description summary="advertise offered mime type">
+ Sent immediately after creating announcing the wp_primary_selection_offer
+ through wp_primary_selection_device.data_offer. One event is sent per
+ offered mime type.
+ </description>
+ <arg name="mime_type" type="string"/>
+ </event>
+ </interface>
+
+ <interface name="gtk_primary_selection_source" version="1">
+ <description summary="offer to replace the contents of the primary selection">
+ The source side of a wp_primary_selection_offer, it provides a way to
+ describe the offered data and respond to requests to transfer the
+ requested contents of the primary selection clipboard.
+ </description>
+
+ <request name="offer">
+ <description summary="add an offered mime type">
+ This request adds a mime type to the set of mime types advertised to
+ targets. Can be called several times to offer multiple types.
+ </description>
+ <arg name="mime_type" type="string"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection source">
+ Destroy the primary selection source.
+ </description>
+ </request>
+
+ <event name="send">
+ <description summary="send the primary selection contents">
+ Request for the current primary selection contents from the client.
+ Send the specified mime type over the passed file descriptor, then
+ close it.
+ </description>
+ <arg name="mime_type" type="string"/>
+ <arg name="fd" type="fd"/>
+ </event>
+
+ <event name="cancelled">
+ <description summary="request for primary selection contents was canceled">
+ This primary selection source is no longer valid. The client should
+ clean up and destroy this primary selection source.
+ </description>
+ </event>
+ </interface>
+</protocol>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="pointer_constraints_unstable_v1">
+
+ <copyright>
+ Copyright © 2014 Jonas Ådahl
+ Copyright © 2015 Red Hat Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="protocol for constraining pointer motions">
+ This protocol specifies a set of interfaces used for adding constraints to
+ the motion of a pointer. Possible constraints include confining pointer
+ motions to a given region, or locking it to its current position.
+
+ In order to constrain the pointer, a client must first bind the global
+ interface "wp_pointer_constraints" which, if a compositor supports pointer
+ constraints, is exposed by the registry. Using the bound global object, the
+ client uses the request that corresponds to the type of constraint it wants
+ to make. See wp_pointer_constraints for more details.
+
+ Warning! The protocol described in this file is experimental and backward
+ incompatible changes may be made. Backward compatible changes may be added
+ together with the corresponding interface version bump. Backward
+ incompatible changes are done by bumping the version number in the protocol
+ and interface names and resetting the interface version. Once the protocol
+ is to be declared stable, the 'z' prefix and the version number in the
+ protocol and interface names are removed and the interface version number is
+ reset.
+ </description>
+
+ <interface name="zwp_pointer_constraints_v1" version="1">
+ <description summary="constrain the movement of a pointer">
+ The global interface exposing pointer constraining functionality. It
+ exposes two requests: lock_pointer for locking the pointer to its
+ position, and confine_pointer for locking the pointer to a region.
+
+ The lock_pointer and confine_pointer requests create the objects
+ wp_locked_pointer and wp_confined_pointer respectively, and the client can
+ use these objects to interact with the lock.
+
+ For any surface, only one lock or confinement may be active across all
+ wl_pointer objects of the same seat. If a lock or confinement is requested
+ when another lock or confinement is active or requested on the same surface
+ and with any of the wl_pointer objects of the same seat, an
+ 'already_constrained' error will be raised.
+ </description>
+
+ <enum name="error">
+ <description summary="wp_pointer_constraints error values">
+ These errors can be emitted in response to wp_pointer_constraints
+ requests.
+ </description>
+ <entry name="already_constrained" value="1"
+ summary="pointer constraint already requested on that surface"/>
+ </enum>
+
+ <enum name="lifetime">
+ <description summary="constraint lifetime">
+ These values represent different lifetime semantics. They are passed
+ as arguments to the factory requests to specify how the constraint
+ lifetimes should be managed.
+ </description>
+ <entry name="oneshot" value="1">
+ <description summary="the pointer constraint is defunct once deactivated">
+ A oneshot pointer constraint will never reactivate once it has been
+ deactivated. See the corresponding deactivation event
+ (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for
+ details.
+ </description>
+ </entry>
+ <entry name="persistent" value="2">
+ <description summary="the pointer constraint may reactivate">
+ A persistent pointer constraint may again reactivate once it has
+ been deactivated. See the corresponding deactivation event
+ (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for
+ details.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the pointer constraints manager object">
+ Used by the client to notify the server that it will no longer use this
+ pointer constraints object.
+ </description>
+ </request>
+
+ <request name="lock_pointer">
+ <description summary="lock pointer to a position">
+ The lock_pointer request lets the client request to disable movements of
+ the virtual pointer (i.e. the cursor), effectively locking the pointer
+ to a position. This request may not take effect immediately; in the
+ future, when the compositor deems implementation-specific constraints
+ are satisfied, the pointer lock will be activated and the compositor
+ sends a locked event.
+
+ The protocol provides no guarantee that the constraints are ever
+ satisfied, and does not require the compositor to send an error if the
+ constraints cannot ever be satisfied. It is thus possible to request a
+ lock that will never activate.
+
+ There may not be another pointer constraint of any kind requested or
+ active on the surface for any of the wl_pointer objects of the seat of
+ the passed pointer when requesting a lock. If there is, an error will be
+ raised. See general pointer lock documentation for more details.
+
+ The intersection of the region passed with this request and the input
+ region of the surface is used to determine where the pointer must be
+ in order for the lock to activate. It is up to the compositor whether to
+ warp the pointer or require some kind of user interaction for the lock
+ to activate. If the region is null the surface input region is used.
+
+ A surface may receive pointer focus without the lock being activated.
+
+ The request creates a new object wp_locked_pointer which is used to
+ interact with the lock as well as receive updates about its state. See
+ the the description of wp_locked_pointer for further information.
+
+ Note that while a pointer is locked, the wl_pointer objects of the
+ corresponding seat will not emit any wl_pointer.motion events, but
+ relative motion events will still be emitted via wp_relative_pointer
+ objects of the same seat. wl_pointer.axis and wl_pointer.button events
+ are unaffected.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_locked_pointer_v1"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="surface to lock pointer to"/>
+ <arg name="pointer" type="object" interface="wl_pointer"
+ summary="the pointer that should be locked"/>
+ <arg name="region" type="object" interface="wl_region" allow-null="true"
+ summary="region of surface"/>
+ <arg name="lifetime" type="uint" summary="lock lifetime"/>
+ </request>
+
+ <request name="confine_pointer">
+ <description summary="confine pointer to a region">
+ The confine_pointer request lets the client request to confine the
+ pointer cursor to a given region. This request may not take effect
+ immediately; in the future, when the compositor deems implementation-
+ specific constraints are satisfied, the pointer confinement will be
+ activated and the compositor sends a confined event.
+
+ The intersection of the region passed with this request and the input
+ region of the surface is used to determine where the pointer must be
+ in order for the confinement to activate. It is up to the compositor
+ whether to warp the pointer or require some kind of user interaction for
+ the confinement to activate. If the region is null the surface input
+ region is used.
+
+ The request will create a new object wp_confined_pointer which is used
+ to interact with the confinement as well as receive updates about its
+ state. See the the description of wp_confined_pointer for further
+ information.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_confined_pointer_v1"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="surface to lock pointer to"/>
+ <arg name="pointer" type="object" interface="wl_pointer"
+ summary="the pointer that should be confined"/>
+ <arg name="region" type="object" interface="wl_region" allow-null="true"
+ summary="region of surface"/>
+ <arg name="lifetime" type="uint" summary="confinement lifetime"/>
+ </request>
+ </interface>
+
+ <interface name="zwp_locked_pointer_v1" version="1">
+ <description summary="receive relative pointer motion events">
+ The wp_locked_pointer interface represents a locked pointer state.
+
+ While the lock of this object is active, the wl_pointer objects of the
+ associated seat will not emit any wl_pointer.motion events.
+
+ This object will send the event 'locked' when the lock is activated.
+ Whenever the lock is activated, it is guaranteed that the locked surface
+ will already have received pointer focus and that the pointer will be
+ within the region passed to the request creating this object.
+
+ To unlock the pointer, send the destroy request. This will also destroy
+ the wp_locked_pointer object.
+
+ If the compositor decides to unlock the pointer the unlocked event is
+ sent. See wp_locked_pointer.unlock for details.
+
+ When unlocking, the compositor may warp the cursor position to the set
+ cursor position hint. If it does, it will not result in any relative
+ motion events emitted via wp_relative_pointer.
+
+ If the surface the lock was requested on is destroyed and the lock is not
+ yet activated, the wp_locked_pointer object is now defunct and must be
+ destroyed.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the locked pointer object">
+ Destroy the locked pointer object. If applicable, the compositor will
+ unlock the pointer.
+ </description>
+ </request>
+
+ <request name="set_cursor_position_hint">
+ <description summary="set the pointer cursor position hint">
+ Set the cursor position hint relative to the top left corner of the
+ surface.
+
+ If the client is drawing its own cursor, it should update the position
+ hint to the position of its own cursor. A compositor may use this
+ information to warp the pointer upon unlock in order to avoid pointer
+ jumps.
+
+ The cursor position hint is double buffered. The new hint will only take
+ effect when the associated surface gets it pending state applied. See
+ wl_surface.commit for details.
+ </description>
+ <arg name="surface_x" type="fixed"
+ summary="surface-local x coordinate"/>
+ <arg name="surface_y" type="fixed"
+ summary="surface-local y coordinate"/>
+ </request>
+
+ <request name="set_region">
+ <description summary="set a new lock region">
+ Set a new region used to lock the pointer.
+
+ The new lock region is double-buffered. The new lock region will
+ only take effect when the associated surface gets its pending state
+ applied. See wl_surface.commit for details.
+
+ For details about the lock region, see wp_locked_pointer.
+ </description>
+ <arg name="region" type="object" interface="wl_region" allow-null="true"
+ summary="region of surface"/>
+ </request>
+
+ <event name="locked">
+ <description summary="lock activation event">
+ Notification that the pointer lock of the seat's pointer is activated.
+ </description>
+ </event>
+
+ <event name="unlocked">
+ <description summary="lock deactivation event">
+ Notification that the pointer lock of the seat's pointer is no longer
+ active. If this is a oneshot pointer lock (see
+ wp_pointer_constraints.lifetime) this object is now defunct and should
+ be destroyed. If this is a persistent pointer lock (see
+ wp_pointer_constraints.lifetime) this pointer lock may again
+ reactivate in the future.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zwp_confined_pointer_v1" version="1">
+ <description summary="confined pointer object">
+ The wp_confined_pointer interface represents a confined pointer state.
+
+ This object will send the event 'confined' when the confinement is
+ activated. Whenever the confinement is activated, it is guaranteed that
+ the surface the pointer is confined to will already have received pointer
+ focus and that the pointer will be within the region passed to the request
+ creating this object. It is up to the compositor to decide whether this
+ requires some user interaction and if the pointer will warp to within the
+ passed region if outside.
+
+ To unconfine the pointer, send the destroy request. This will also destroy
+ the wp_confined_pointer object.
+
+ If the compositor decides to unconfine the pointer the unconfined event is
+ sent. The wp_confined_pointer object is at this point defunct and should
+ be destroyed.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the confined pointer object">
+ Destroy the confined pointer object. If applicable, the compositor will
+ unconfine the pointer.
+ </description>
+ </request>
+
+ <request name="set_region">
+ <description summary="set a new confine region">
+ Set a new region used to confine the pointer.
+
+ The new confine region is double-buffered. The new confine region will
+ only take effect when the associated surface gets its pending state
+ applied. See wl_surface.commit for details.
+
+ If the confinement is active when the new confinement region is applied
+ and the pointer ends up outside of newly applied region, the pointer may
+ warped to a position within the new confinement region. If warped, a
+ wl_pointer.motion event will be emitted, but no
+ wp_relative_pointer.relative_motion event.
+
+ The compositor may also, instead of using the new region, unconfine the
+ pointer.
+
+ For details about the confine region, see wp_confined_pointer.
+ </description>
+ <arg name="region" type="object" interface="wl_region" allow-null="true"
+ summary="region of surface"/>
+ </request>
+
+ <event name="confined">
+ <description summary="pointer confined">
+ Notification that the pointer confinement of the seat's pointer is
+ activated.
+ </description>
+ </event>
+
+ <event name="unconfined">
+ <description summary="pointer unconfined">
+ Notification that the pointer confinement of the seat's pointer is no
+ longer active. If this is a oneshot pointer confinement (see
+ wp_pointer_constraints.lifetime) this object is now defunct and should
+ be destroyed. If this is a persistent pointer confinement (see
+ wp_pointer_constraints.lifetime) this pointer confinement may again
+ reactivate in the future.
+ </description>
+ </event>
+ </interface>
+
+</protocol>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wp_primary_selection_unstable_v1">
+ <copyright>
+ Copyright © 2015, 2016 Red Hat
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="Primary selection protocol">
+ This protocol provides the ability to have a primary selection device to
+ match that of the X server. This primary selection is a shortcut to the
+ common clipboard selection, where text just needs to be selected in order
+ to allow copying it elsewhere. The de facto way to perform this action
+ is the middle mouse button, although it is not limited to this one.
+
+ Clients wishing to honor primary selection should create a primary
+ selection source and set it as the selection through
+ wp_primary_selection_device.set_selection whenever the text selection
+ changes. In order to minimize calls in pointer-driven text selection,
+ it should happen only once after the operation finished. Similarly,
+ a NULL source should be set when text is unselected.
+
+ wp_primary_selection_offer objects are first announced through the
+ wp_primary_selection_device.data_offer event. Immediately after this event,
+ the primary data offer will emit wp_primary_selection_offer.offer events
+ to let know of the mime types being offered.
+
+ When the primary selection changes, the client with the keyboard focus
+ will receive wp_primary_selection_device.selection events. Only the client
+ with the keyboard focus will receive such events with a non-NULL
+ wp_primary_selection_offer. Across keyboard focus changes, previously
+ focused clients will receive wp_primary_selection_device.events with a
+ NULL wp_primary_selection_offer.
+
+ In order to request the primary selection data, the client must pass
+ a recent serial pertaining to the press event that is triggering the
+ operation, if the compositor deems the serial valid and recent, the
+ wp_primary_selection_source.send event will happen in the other end
+ to let the transfer begin. The client owning the primary selection
+ should write the requested data, and close the file descriptor
+ immediately.
+
+ If the primary selection owner client disappeared during the transfer,
+ the client reading the data will receive a
+ wp_primary_selection_device.selection event with a NULL
+ wp_primary_selection_offer, the client should take this as a hint
+ to finish the reads related to the no longer existing offer.
+
+ The primary selection owner should be checking for errors during
+ writes, merely cancelling the ongoing transfer if any happened.
+ </description>
+
+ <interface name="zwp_primary_selection_device_manager_v1" version="1">
+ <description summary="X primary selection emulation">
+ The primary selection device manager is a singleton global object that
+ provides access to the primary selection. It allows to create
+ wp_primary_selection_source objects, as well as retrieving the per-seat
+ wp_primary_selection_device objects.
+ </description>
+
+ <request name="create_source">
+ <description summary="create a new primary selection source">
+ Create a new primary selection source.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_primary_selection_source_v1"/>
+ </request>
+
+ <request name="get_device">
+ <description summary="create a new primary selection device">
+ Create a new data device for a given seat.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_primary_selection_device_v1"/>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection device manager">
+ Destroy the primary selection device manager.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwp_primary_selection_device_v1" version="1">
+ <request name="set_selection">
+ <description summary="set the primary selection">
+ Replaces the current selection. The previous owner of the primary
+ selection will receive a wp_primary_selection_source.cancelled event.
+
+ To unset the selection, set the source to NULL.
+ </description>
+ <arg name="source" type="object" interface="zwp_primary_selection_source_v1" allow-null="true"/>
+ <arg name="serial" type="uint" summary="serial of the event that triggered this request"/>
+ </request>
+
+ <event name="data_offer">
+ <description summary="introduce a new wp_primary_selection_offer">
+ Introduces a new wp_primary_selection_offer object that may be used
+ to receive the current primary selection. Immediately following this
+ event, the new wp_primary_selection_offer object will send
+ wp_primary_selection_offer.offer events to describe the offered mime
+ types.
+ </description>
+ <arg name="offer" type="new_id" interface="zwp_primary_selection_offer_v1"/>
+ </event>
+
+ <event name="selection">
+ <description summary="advertise a new primary selection">
+ The wp_primary_selection_device.selection event is sent to notify the
+ client of a new primary selection. This event is sent after the
+ wp_primary_selection.data_offer event introducing this object, and after
+ the offer has announced its mimetypes through
+ wp_primary_selection_offer.offer.
+
+ The data_offer is valid until a new offer or NULL is received
+ or until the client loses keyboard focus. The client must destroy the
+ previous selection data_offer, if any, upon receiving this event.
+ </description>
+ <arg name="id" type="object" interface="zwp_primary_selection_offer_v1" allow-null="true"/>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection device">
+ Destroy the primary selection device.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwp_primary_selection_offer_v1" version="1">
+ <description summary="offer to transfer primary selection contents">
+ A wp_primary_selection_offer represents an offer to transfer the contents
+ of the primary selection clipboard to the client. Similar to
+ wl_data_offer, the offer also describes the mime types that the data can
+ be converted to and provides the mechanisms for transferring the data
+ directly to the client.
+ </description>
+
+ <request name="receive">
+ <description summary="request that the data is transferred">
+ To transfer the contents of the primary selection clipboard, the client
+ issues this request and indicates the mime type that it wants to
+ receive. The transfer happens through the passed file descriptor
+ (typically created with the pipe system call). The source client writes
+ the data in the mime type representation requested and then closes the
+ file descriptor.
+
+ The receiving client reads from the read end of the pipe until EOF and
+ closes its end, at which point the transfer is complete.
+ </description>
+ <arg name="mime_type" type="string"/>
+ <arg name="fd" type="fd"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection offer">
+ Destroy the primary selection offer.
+ </description>
+ </request>
+
+ <event name="offer">
+ <description summary="advertise offered mime type">
+ Sent immediately after creating announcing the
+ wp_primary_selection_offer through
+ wp_primary_selection_device.data_offer. One event is sent per offered
+ mime type.
+ </description>
+ <arg name="mime_type" type="string"/>
+ </event>
+ </interface>
+
+ <interface name="zwp_primary_selection_source_v1" version="1">
+ <description summary="offer to replace the contents of the primary selection">
+ The source side of a wp_primary_selection_offer, it provides a way to
+ describe the offered data and respond to requests to transfer the
+ requested contents of the primary selection clipboard.
+ </description>
+
+ <request name="offer">
+ <description summary="add an offered mime type">
+ This request adds a mime type to the set of mime types advertised to
+ targets. Can be called several times to offer multiple types.
+ </description>
+ <arg name="mime_type" type="string"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection source">
+ Destroy the primary selection source.
+ </description>
+ </request>
+
+ <event name="send">
+ <description summary="send the primary selection contents">
+ Request for the current primary selection contents from the client.
+ Send the specified mime type over the passed file descriptor, then
+ close it.
+ </description>
+ <arg name="mime_type" type="string"/>
+ <arg name="fd" type="fd"/>
+ </event>
+
+ <event name="cancelled">
+ <description summary="request for primary selection contents was canceled">
+ This primary selection source is no longer valid. The client should
+ clean up and destroy this primary selection source.
+ </description>
+ </event>
+ </interface>
+</protocol>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="relative_pointer_unstable_v1">
+
+ <copyright>
+ Copyright © 2014 Jonas Ådahl
+ Copyright © 2015 Red Hat Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="protocol for relative pointer motion events">
+ This protocol specifies a set of interfaces used for making clients able to
+ receive relative pointer events not obstructed by barriers (such as the
+ monitor edge or other pointer barriers).
+
+ To start receiving relative pointer events, a client must first bind the
+ global interface "wp_relative_pointer_manager" which, if a compositor
+ supports relative pointer motion events, is exposed by the registry. After
+ having created the relative pointer manager proxy object, the client uses
+ it to create the actual relative pointer object using the
+ "get_relative_pointer" request given a wl_pointer. The relative pointer
+ motion events will then, when applicable, be transmitted via the proxy of
+ the newly created relative pointer object. See the documentation of the
+ relative pointer interface for more details.
+
+ Warning! The protocol described in this file is experimental and backward
+ incompatible changes may be made. Backward compatible changes may be added
+ together with the corresponding interface version bump. Backward
+ incompatible changes are done by bumping the version number in the protocol
+ and interface names and resetting the interface version. Once the protocol
+ is to be declared stable, the 'z' prefix and the version number in the
+ protocol and interface names are removed and the interface version number is
+ reset.
+ </description>
+
+ <interface name="zwp_relative_pointer_manager_v1" version="1">
+ <description summary="get relative pointer objects">
+ A global interface used for getting the relative pointer object for a
+ given pointer.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the relative pointer manager object">
+ Used by the client to notify the server that it will no longer use this
+ relative pointer manager object.
+ </description>
+ </request>
+
+ <request name="get_relative_pointer">
+ <description summary="get a relative pointer object">
+ Create a relative pointer interface given a wl_pointer object. See the
+ wp_relative_pointer interface for more details.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_relative_pointer_v1"/>
+ <arg name="pointer" type="object" interface="wl_pointer"/>
+ </request>
+ </interface>
+
+ <interface name="zwp_relative_pointer_v1" version="1">
+ <description summary="relative pointer object">
+ A wp_relative_pointer object is an extension to the wl_pointer interface
+ used for emitting relative pointer events. It shares the same focus as
+ wl_pointer objects of the same seat and will only emit events when it has
+ focus.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="release the relative pointer object"/>
+ </request>
+
+ <event name="relative_motion">
+ <description summary="relative pointer motion">
+ Relative x/y pointer motion from the pointer of the seat associated with
+ this object.
+
+ A relative motion is in the same dimension as regular wl_pointer motion
+ events, except they do not represent an absolute position. For example,
+ moving a pointer from (x, y) to (x', y') would have the equivalent
+ relative motion (x' - x, y' - y). If a pointer motion caused the
+ absolute pointer position to be clipped by for example the edge of the
+ monitor, the relative motion is unaffected by the clipping and will
+ represent the unclipped motion.
+
+ This event also contains non-accelerated motion deltas. The
+ non-accelerated delta is, when applicable, the regular pointer motion
+ delta as it was before having applied motion acceleration and other
+ transformations such as normalization.
+
+ Note that the non-accelerated delta does not represent 'raw' events as
+ they were read from some device. Pointer motion acceleration is device-
+ and configuration-specific and non-accelerated deltas and accelerated
+ deltas may have the same value on some devices.
+
+ Relative motions are not coupled to wl_pointer.motion events, and can be
+ sent in combination with such events, but also independently. There may
+ also be scenarios where wl_pointer.motion is sent, but there is no
+ relative motion. The order of an absolute and relative motion event
+ originating from the same physical motion is not guaranteed.
+
+ If the client needs button events or focus state, it can receive them
+ from a wl_pointer object of the same seat that the wp_relative_pointer
+ object is associated with.
+ </description>
+ <arg name="utime_hi" type="uint"
+ summary="high 32 bits of a 64 bit timestamp with microsecond granularity"/>
+ <arg name="utime_lo" type="uint"
+ summary="low 32 bits of a 64 bit timestamp with microsecond granularity"/>
+ <arg name="dx" type="fixed"
+ summary="the x component of the motion vector"/>
+ <arg name="dy" type="fixed"
+ summary="the y component of the motion vector"/>
+ <arg name="dx_unaccel" type="fixed"
+ summary="the x component of the unaccelerated motion vector"/>
+ <arg name="dy_unaccel" type="fixed"
+ summary="the y component of the unaccelerated motion vector"/>
+ </event>
+ </interface>
+
+</protocol>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wayland">
+
+ <copyright>
+ Copyright © 2008-2011 Kristian Høgsberg
+ Copyright © 2010-2011 Intel Corporation
+ Copyright © 2012-2013 Collabora, Ltd.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the
+ next paragraph) shall be included in all copies or substantial
+ portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ </copyright>
+
+ <interface name="wl_display" version="1">
+ <description summary="core global object">
+ The core global object. This is a special singleton object. It
+ is used for internal Wayland protocol features.
+ </description>
+
+ <request name="sync">
+ <description summary="asynchronous roundtrip">
+ The sync request asks the server to emit the 'done' event
+ on the returned wl_callback object. Since requests are
+ handled in-order and events are delivered in-order, this can
+ be used as a barrier to ensure all previous requests and the
+ resulting events have been handled.
+
+ The object returned by this request will be destroyed by the
+ compositor after the callback is fired and as such the client must not
+ attempt to use it after that point.
+
+ The callback_data passed in the callback is the event serial.
+ </description>
+ <arg name="callback" type="new_id" interface="wl_callback"
+ summary="callback object for the sync request"/>
+ </request>
+
+ <request name="get_registry">
+ <description summary="get global registry object">
+ This request creates a registry object that allows the client
+ to list and bind the global objects available from the
+ compositor.
+ </description>
+ <arg name="registry" type="new_id" interface="wl_registry"
+ summary="global registry object"/>
+ </request>
+
+ <event name="error">
+ <description summary="fatal error event">
+ The error event is sent out when a fatal (non-recoverable)
+ error has occurred. The object_id argument is the object
+ where the error occurred, most often in response to a request
+ to that object. The code identifies the error and is defined
+ by the object interface. As such, each interface defines its
+ own set of error codes. The message is a brief description
+ of the error, for (debugging) convenience.
+ </description>
+ <arg name="object_id" type="object" summary="object where the error occurred"/>
+ <arg name="code" type="uint" summary="error code"/>
+ <arg name="message" type="string" summary="error description"/>
+ </event>
+
+ <enum name="error">
+ <description summary="global error values">
+ These errors are global and can be emitted in response to any
+ server request.
+ </description>
+ <entry name="invalid_object" value="0"
+ summary="server couldn't find object"/>
+ <entry name="invalid_method" value="1"
+ summary="method doesn't exist on the specified interface"/>
+ <entry name="no_memory" value="2"
+ summary="server is out of memory"/>
+ </enum>
+
+ <event name="delete_id">
+ <description summary="acknowledge object ID deletion">
+ This event is used internally by the object ID management
+ logic. When a client deletes an object, the server will send
+ this event to acknowledge that it has seen the delete request.
+ When the client receives this event, it will know that it can
+ safely reuse the object ID.
+ </description>
+ <arg name="id" type="uint" summary="deleted object ID"/>
+ </event>
+ </interface>
+
+ <interface name="wl_registry" version="1">
+ <description summary="global registry object">
+ The singleton global registry object. The server has a number of
+ global objects that are available to all clients. These objects
+ typically represent an actual object in the server (for example,
+ an input device) or they are singleton objects that provide
+ extension functionality.
+
+ When a client creates a registry object, the registry object
+ will emit a global event for each global currently in the
+ registry. Globals come and go as a result of device or
+ monitor hotplugs, reconfiguration or other events, and the
+ registry will send out global and global_remove events to
+ keep the client up to date with the changes. To mark the end
+ of the initial burst of events, the client can use the
+ wl_display.sync request immediately after calling
+ wl_display.get_registry.
+
+ A client can bind to a global object by using the bind
+ request. This creates a client-side handle that lets the object
+ emit events to the client and lets the client invoke requests on
+ the object.
+ </description>
+
+ <request name="bind">
+ <description summary="bind an object to the display">
+ Binds a new, client-created object to the server using the
+ specified name as the identifier.
+ </description>
+ <arg name="name" type="uint" summary="unique numeric name of the object"/>
+ <arg name="id" type="new_id" summary="bounded object"/>
+ </request>
+
+ <event name="global">
+ <description summary="announce global object">
+ Notify the client of global objects.
+
+ The event notifies the client that a global object with
+ the given name is now available, and it implements the
+ given version of the given interface.
+ </description>
+ <arg name="name" type="uint" summary="numeric name of the global object"/>
+ <arg name="interface" type="string" summary="interface implemented by the object"/>
+ <arg name="version" type="uint" summary="interface version"/>
+ </event>
+
+ <event name="global_remove">
+ <description summary="announce removal of global object">
+ Notify the client of removed global objects.
+
+ This event notifies the client that the global identified
+ by name is no longer available. If the client bound to
+ the global using the bind request, the client should now
+ destroy that object.
+
+ The object remains valid and requests to the object will be
+ ignored until the client destroys it, to avoid races between
+ the global going away and a client sending a request to it.
+ </description>
+ <arg name="name" type="uint" summary="numeric name of the global object"/>
+ </event>
+ </interface>
+
+ <interface name="wl_callback" version="1">
+ <description summary="callback object">
+ Clients can handle the 'done' event to get notified when
+ the related request is done.
+ </description>
+
+ <event name="done">
+ <description summary="done event">
+ Notify the client when the related request is done.
+ </description>
+ <arg name="callback_data" type="uint" summary="request-specific data for the callback"/>
+ </event>
+ </interface>
+
+ <interface name="wl_compositor" version="4">
+ <description summary="the compositor singleton">
+ A compositor. This object is a singleton global. The
+ compositor is in charge of combining the contents of multiple
+ surfaces into one displayable output.
+ </description>
+
+ <request name="create_surface">
+ <description summary="create new surface">
+ Ask the compositor to create a new surface.
+ </description>
+ <arg name="id" type="new_id" interface="wl_surface" summary="the new surface"/>
+ </request>
+
+ <request name="create_region">
+ <description summary="create new region">
+ Ask the compositor to create a new region.
+ </description>
+ <arg name="id" type="new_id" interface="wl_region" summary="the new region"/>
+ </request>
+ </interface>
+
+ <interface name="wl_shm_pool" version="1">
+ <description summary="a shared memory pool">
+ The wl_shm_pool object encapsulates a piece of memory shared
+ between the compositor and client. Through the wl_shm_pool
+ object, the client can allocate shared memory wl_buffer objects.
+ All objects created through the same pool share the same
+ underlying mapped memory. Reusing the mapped memory avoids the
+ setup/teardown overhead and is useful when interactively resizing
+ a surface or for many small buffers.
+ </description>
+
+ <request name="create_buffer">
+ <description summary="create a buffer from the pool">
+ Create a wl_buffer object from the pool.
+
+ The buffer is created offset bytes into the pool and has
+ width and height as specified. The stride argument specifies
+ the number of bytes from the beginning of one row to the beginning
+ of the next. The format is the pixel format of the buffer and
+ must be one of those advertised through the wl_shm.format event.
+
+ A buffer will keep a reference to the pool it was created from
+ so it is valid to destroy the pool immediately after creating
+ a buffer from it.
+ </description>
+ <arg name="id" type="new_id" interface="wl_buffer" summary="buffer to create"/>
+ <arg name="offset" type="int" summary="buffer byte offset within the pool"/>
+ <arg name="width" type="int" summary="buffer width, in pixels"/>
+ <arg name="height" type="int" summary="buffer height, in pixels"/>
+ <arg name="stride" type="int" summary="number of bytes from the beginning of one row to the beginning of the next row"/>
+ <arg name="format" type="uint" enum="wl_shm.format" summary="buffer pixel format"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the pool">
+ Destroy the shared memory pool.
+
+ The mmapped memory will be released when all
+ buffers that have been created from this pool
+ are gone.
+ </description>
+ </request>
+
+ <request name="resize">
+ <description summary="change the size of the pool mapping">
+ This request will cause the server to remap the backing memory
+ for the pool from the file descriptor passed when the pool was
+ created, but using the new size. This request can only be
+ used to make the pool bigger.
+ </description>
+ <arg name="size" type="int" summary="new size of the pool, in bytes"/>
+ </request>
+ </interface>
+
+ <interface name="wl_shm" version="1">
+ <description summary="shared memory support">
+ A singleton global object that provides support for shared
+ memory.
+
+ Clients can create wl_shm_pool objects using the create_pool
+ request.
+
+ At connection setup time, the wl_shm object emits one or more
+ format events to inform clients about the valid pixel formats
+ that can be used for buffers.
+ </description>
+
+ <enum name="error">
+ <description summary="wl_shm error values">
+ These errors can be emitted in response to wl_shm requests.
+ </description>
+ <entry name="invalid_format" value="0" summary="buffer format is not known"/>
+ <entry name="invalid_stride" value="1" summary="invalid size or stride during pool or buffer creation"/>
+ <entry name="invalid_fd" value="2" summary="mmapping the file descriptor failed"/>
+ </enum>
+
+ <enum name="format">
+ <description summary="pixel formats">
+ This describes the memory layout of an individual pixel.
+
+ All renderers should support argb8888 and xrgb8888 but any other
+ formats are optional and may not be supported by the particular
+ renderer in use.
+
+ The drm format codes match the macros defined in drm_fourcc.h.
+ The formats actually supported by the compositor will be
+ reported by the format event.
+ </description>
+ <entry name="argb8888" value="0" summary="32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian"/>
+ <entry name="xrgb8888" value="1" summary="32-bit RGB format, [31:0] x:R:G:B 8:8:8:8 little endian"/>
+ <entry name="c8" value="0x20203843" summary="8-bit color index format, [7:0] C"/>
+ <entry name="rgb332" value="0x38424752" summary="8-bit RGB format, [7:0] R:G:B 3:3:2"/>
+ <entry name="bgr233" value="0x38524742" summary="8-bit BGR format, [7:0] B:G:R 2:3:3"/>
+ <entry name="xrgb4444" value="0x32315258" summary="16-bit xRGB format, [15:0] x:R:G:B 4:4:4:4 little endian"/>
+ <entry name="xbgr4444" value="0x32314258" summary="16-bit xBGR format, [15:0] x:B:G:R 4:4:4:4 little endian"/>
+ <entry name="rgbx4444" value="0x32315852" summary="16-bit RGBx format, [15:0] R:G:B:x 4:4:4:4 little endian"/>
+ <entry name="bgrx4444" value="0x32315842" summary="16-bit BGRx format, [15:0] B:G:R:x 4:4:4:4 little endian"/>
+ <entry name="argb4444" value="0x32315241" summary="16-bit ARGB format, [15:0] A:R:G:B 4:4:4:4 little endian"/>
+ <entry name="abgr4444" value="0x32314241" summary="16-bit ABGR format, [15:0] A:B:G:R 4:4:4:4 little endian"/>
+ <entry name="rgba4444" value="0x32314152" summary="16-bit RBGA format, [15:0] R:G:B:A 4:4:4:4 little endian"/>
+ <entry name="bgra4444" value="0x32314142" summary="16-bit BGRA format, [15:0] B:G:R:A 4:4:4:4 little endian"/>
+ <entry name="xrgb1555" value="0x35315258" summary="16-bit xRGB format, [15:0] x:R:G:B 1:5:5:5 little endian"/>
+ <entry name="xbgr1555" value="0x35314258" summary="16-bit xBGR 1555 format, [15:0] x:B:G:R 1:5:5:5 little endian"/>
+ <entry name="rgbx5551" value="0x35315852" summary="16-bit RGBx 5551 format, [15:0] R:G:B:x 5:5:5:1 little endian"/>
+ <entry name="bgrx5551" value="0x35315842" summary="16-bit BGRx 5551 format, [15:0] B:G:R:x 5:5:5:1 little endian"/>
+ <entry name="argb1555" value="0x35315241" summary="16-bit ARGB 1555 format, [15:0] A:R:G:B 1:5:5:5 little endian"/>
+ <entry name="abgr1555" value="0x35314241" summary="16-bit ABGR 1555 format, [15:0] A:B:G:R 1:5:5:5 little endian"/>
+ <entry name="rgba5551" value="0x35314152" summary="16-bit RGBA 5551 format, [15:0] R:G:B:A 5:5:5:1 little endian"/>
+ <entry name="bgra5551" value="0x35314142" summary="16-bit BGRA 5551 format, [15:0] B:G:R:A 5:5:5:1 little endian"/>
+ <entry name="rgb565" value="0x36314752" summary="16-bit RGB 565 format, [15:0] R:G:B 5:6:5 little endian"/>
+ <entry name="bgr565" value="0x36314742" summary="16-bit BGR 565 format, [15:0] B:G:R 5:6:5 little endian"/>
+ <entry name="rgb888" value="0x34324752" summary="24-bit RGB format, [23:0] R:G:B little endian"/>
+ <entry name="bgr888" value="0x34324742" summary="24-bit BGR format, [23:0] B:G:R little endian"/>
+ <entry name="xbgr8888" value="0x34324258" summary="32-bit xBGR format, [31:0] x:B:G:R 8:8:8:8 little endian"/>
+ <entry name="rgbx8888" value="0x34325852" summary="32-bit RGBx format, [31:0] R:G:B:x 8:8:8:8 little endian"/>
+ <entry name="bgrx8888" value="0x34325842" summary="32-bit BGRx format, [31:0] B:G:R:x 8:8:8:8 little endian"/>
+ <entry name="abgr8888" value="0x34324241" summary="32-bit ABGR format, [31:0] A:B:G:R 8:8:8:8 little endian"/>
+ <entry name="rgba8888" value="0x34324152" summary="32-bit RGBA format, [31:0] R:G:B:A 8:8:8:8 little endian"/>
+ <entry name="bgra8888" value="0x34324142" summary="32-bit BGRA format, [31:0] B:G:R:A 8:8:8:8 little endian"/>
+ <entry name="xrgb2101010" value="0x30335258" summary="32-bit xRGB format, [31:0] x:R:G:B 2:10:10:10 little endian"/>
+ <entry name="xbgr2101010" value="0x30334258" summary="32-bit xBGR format, [31:0] x:B:G:R 2:10:10:10 little endian"/>
+ <entry name="rgbx1010102" value="0x30335852" summary="32-bit RGBx format, [31:0] R:G:B:x 10:10:10:2 little endian"/>
+ <entry name="bgrx1010102" value="0x30335842" summary="32-bit BGRx format, [31:0] B:G:R:x 10:10:10:2 little endian"/>
+ <entry name="argb2101010" value="0x30335241" summary="32-bit ARGB format, [31:0] A:R:G:B 2:10:10:10 little endian"/>
+ <entry name="abgr2101010" value="0x30334241" summary="32-bit ABGR format, [31:0] A:B:G:R 2:10:10:10 little endian"/>
+ <entry name="rgba1010102" value="0x30334152" summary="32-bit RGBA format, [31:0] R:G:B:A 10:10:10:2 little endian"/>
+ <entry name="bgra1010102" value="0x30334142" summary="32-bit BGRA format, [31:0] B:G:R:A 10:10:10:2 little endian"/>
+ <entry name="yuyv" value="0x56595559" summary="packed YCbCr format, [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian"/>
+ <entry name="yvyu" value="0x55595659" summary="packed YCbCr format, [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian"/>
+ <entry name="uyvy" value="0x59565955" summary="packed YCbCr format, [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian"/>
+ <entry name="vyuy" value="0x59555956" summary="packed YCbCr format, [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian"/>
+ <entry name="ayuv" value="0x56555941" summary="packed AYCbCr format, [31:0] A:Y:Cb:Cr 8:8:8:8 little endian"/>
+ <entry name="nv12" value="0x3231564e" summary="2 plane YCbCr Cr:Cb format, 2x2 subsampled Cr:Cb plane"/>
+ <entry name="nv21" value="0x3132564e" summary="2 plane YCbCr Cb:Cr format, 2x2 subsampled Cb:Cr plane"/>
+ <entry name="nv16" value="0x3631564e" summary="2 plane YCbCr Cr:Cb format, 2x1 subsampled Cr:Cb plane"/>
+ <entry name="nv61" value="0x3136564e" summary="2 plane YCbCr Cb:Cr format, 2x1 subsampled Cb:Cr plane"/>
+ <entry name="yuv410" value="0x39565559" summary="3 plane YCbCr format, 4x4 subsampled Cb (1) and Cr (2) planes"/>
+ <entry name="yvu410" value="0x39555659" summary="3 plane YCbCr format, 4x4 subsampled Cr (1) and Cb (2) planes"/>
+ <entry name="yuv411" value="0x31315559" summary="3 plane YCbCr format, 4x1 subsampled Cb (1) and Cr (2) planes"/>
+ <entry name="yvu411" value="0x31315659" summary="3 plane YCbCr format, 4x1 subsampled Cr (1) and Cb (2) planes"/>
+ <entry name="yuv420" value="0x32315559" summary="3 plane YCbCr format, 2x2 subsampled Cb (1) and Cr (2) planes"/>
+ <entry name="yvu420" value="0x32315659" summary="3 plane YCbCr format, 2x2 subsampled Cr (1) and Cb (2) planes"/>
+ <entry name="yuv422" value="0x36315559" summary="3 plane YCbCr format, 2x1 subsampled Cb (1) and Cr (2) planes"/>
+ <entry name="yvu422" value="0x36315659" summary="3 plane YCbCr format, 2x1 subsampled Cr (1) and Cb (2) planes"/>
+ <entry name="yuv444" value="0x34325559" summary="3 plane YCbCr format, non-subsampled Cb (1) and Cr (2) planes"/>
+ <entry name="yvu444" value="0x34325659" summary="3 plane YCbCr format, non-subsampled Cr (1) and Cb (2) planes"/>
+ </enum>
+
+ <request name="create_pool">
+ <description summary="create a shm pool">
+ Create a new wl_shm_pool object.
+
+ The pool can be used to create shared memory based buffer
+ objects. The server will mmap size bytes of the passed file
+ descriptor, to use as backing memory for the pool.
+ </description>
+ <arg name="id" type="new_id" interface="wl_shm_pool" summary="pool to create"/>
+ <arg name="fd" type="fd" summary="file descriptor for the pool"/>
+ <arg name="size" type="int" summary="pool size, in bytes"/>
+ </request>
+
+ <event name="format">
+ <description summary="pixel format description">
+ Informs the client about a valid pixel format that
+ can be used for buffers. Known formats include
+ argb8888 and xrgb8888.
+ </description>
+ <arg name="format" type="uint" enum="format" summary="buffer pixel format"/>
+ </event>
+ </interface>
+
+ <interface name="wl_buffer" version="1">
+ <description summary="content for a wl_surface">
+ A buffer provides the content for a wl_surface. Buffers are
+ created through factory interfaces such as wl_drm, wl_shm or
+ similar. It has a width and a height and can be attached to a
+ wl_surface, but the mechanism by which a client provides and
+ updates the contents is defined by the buffer factory interface.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy a buffer">
+ Destroy a buffer. If and how you need to release the backing
+ storage is defined by the buffer factory interface.
+
+ For possible side-effects to a surface, see wl_surface.attach.
+ </description>
+ </request>
+
+ <event name="release">
+ <description summary="compositor releases buffer">
+ Sent when this wl_buffer is no longer used by the compositor.
+ The client is now free to reuse or destroy this buffer and its
+ backing storage.
+
+ If a client receives a release event before the frame callback
+ requested in the same wl_surface.commit that attaches this
+ wl_buffer to a surface, then the client is immediately free to
+ reuse the buffer and its backing storage, and does not need a
+ second buffer for the next surface content update. Typically
+ this is possible, when the compositor maintains a copy of the
+ wl_surface contents, e.g. as a GL texture. This is an important
+ optimization for GL(ES) compositors with wl_shm clients.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="wl_data_offer" version="3">
+ <description summary="offer to transfer data">
+ A wl_data_offer represents a piece of data offered for transfer
+ by another client (the source client). It is used by the
+ copy-and-paste and drag-and-drop mechanisms. The offer
+ describes the different mime types that the data can be
+ converted to and provides the mechanism for transferring the
+ data directly from the source client.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_finish" value="0"
+ summary="finish request was called untimely"/>
+ <entry name="invalid_action_mask" value="1"
+ summary="action mask contains invalid values"/>
+ <entry name="invalid_action" value="2"
+ summary="action argument has an invalid value"/>
+ <entry name="invalid_offer" value="3"
+ summary="offer doesn't accept this request"/>
+ </enum>
+
+ <request name="accept">
+ <description summary="accept one of the offered mime types">
+ Indicate that the client can accept the given mime type, or
+ NULL for not accepted.
+
+ For objects of version 2 or older, this request is used by the
+ client to give feedback whether the client can receive the given
+ mime type, or NULL if none is accepted; the feedback does not
+ determine whether the drag-and-drop operation succeeds or not.
+
+ For objects of version 3 or newer, this request determines the
+ final result of the drag-and-drop operation. If the end result
+ is that no mime types were accepted, the drag-and-drop operation
+ will be cancelled and the corresponding drag source will receive
+ wl_data_source.cancelled. Clients may still use this event in
+ conjunction with wl_data_source.action for feedback.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the accept request"/>
+ <arg name="mime_type" type="string" allow-null="true" summary="mime type accepted by the client"/>
+ </request>
+
+ <request name="receive">
+ <description summary="request that the data is transferred">
+ To transfer the offered data, the client issues this request
+ and indicates the mime type it wants to receive. The transfer
+ happens through the passed file descriptor (typically created
+ with the pipe system call). The source client writes the data
+ in the mime type representation requested and then closes the
+ file descriptor.
+
+ The receiving client reads from the read end of the pipe until
+ EOF and then closes its end, at which point the transfer is
+ complete.
+
+ This request may happen multiple times for different mime types,
+ both before and after wl_data_device.drop. Drag-and-drop destination
+ clients may preemptively fetch data or examine it more closely to
+ determine acceptance.
+ </description>
+ <arg name="mime_type" type="string" summary="mime type desired by receiver"/>
+ <arg name="fd" type="fd" summary="file descriptor for data transfer"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy data offer">
+ Destroy the data offer.
+ </description>
+ </request>
+
+ <event name="offer">
+ <description summary="advertise offered mime type">
+ Sent immediately after creating the wl_data_offer object. One
+ event per offered mime type.
+ </description>
+ <arg name="mime_type" type="string" summary="offered mime type"/>
+ </event>
+
+ <!-- Version 3 additions -->
+
+ <request name="finish" since="3">
+ <description summary="the offer will no longer be used">
+ Notifies the compositor that the drag destination successfully
+ finished the drag-and-drop operation.
+
+ Upon receiving this request, the compositor will emit
+ wl_data_source.dnd_finished on the drag source client.
+
+ It is a client error to perform other requests than
+ wl_data_offer.destroy after this one. It is also an error to perform
+ this request after a NULL mime type has been set in
+ wl_data_offer.accept or no action was received through
+ wl_data_offer.action.
+ </description>
+ </request>
+
+ <request name="set_actions" since="3">
+ <description summary="set the available/preferred drag-and-drop actions">
+ Sets the actions that the destination side client supports for
+ this operation. This request may trigger the emission of
+ wl_data_source.action and wl_data_offer.action events if the compositor
+ needs to change the selected action.
+
+ This request can be called multiple times throughout the
+ drag-and-drop operation, typically in response to wl_data_device.enter
+ or wl_data_device.motion events.
+
+ This request determines the final result of the drag-and-drop
+ operation. If the end result is that no action is accepted,
+ the drag source will receive wl_drag_source.cancelled.
+
+ The dnd_actions argument must contain only values expressed in the
+ wl_data_device_manager.dnd_actions enum, and the preferred_action
+ argument must only contain one of those values set, otherwise it
+ will result in a protocol error.
+
+ While managing an "ask" action, the destination drag-and-drop client
+ may perform further wl_data_offer.receive requests, and is expected
+ to perform one last wl_data_offer.set_actions request with a preferred
+ action other than "ask" (and optionally wl_data_offer.accept) before
+ requesting wl_data_offer.finish, in order to convey the action selected
+ by the user. If the preferred action is not in the
+ wl_data_offer.source_actions mask, an error will be raised.
+
+ If the "ask" action is dismissed (e.g. user cancellation), the client
+ is expected to perform wl_data_offer.destroy right away.
+
+ This request can only be made on drag-and-drop offers, a protocol error
+ will be raised otherwise.
+ </description>
+ <arg name="dnd_actions" type="uint" summary="actions supported by the destination client"/>
+ <arg name="preferred_action" type="uint" summary="action preferred by the destination client"/>
+ </request>
+
+ <event name="source_actions" since="3">
+ <description summary="notify the source-side available actions">
+ This event indicates the actions offered by the data source. It
+ will be sent right after wl_data_device.enter, or anytime the source
+ side changes its offered actions through wl_data_source.set_actions.
+ </description>
+ <arg name="source_actions" type="uint" summary="actions offered by the data source"/>
+ </event>
+
+ <event name="action" since="3">
+ <description summary="notify the selected action">
+ This event indicates the action selected by the compositor after
+ matching the source/destination side actions. Only one action (or
+ none) will be offered here.
+
+ This event can be emitted multiple times during the drag-and-drop
+ operation in response to destination side action changes through
+ wl_data_offer.set_actions.
+
+ This event will no longer be emitted after wl_data_device.drop
+ happened on the drag-and-drop destination, the client must
+ honor the last action received, or the last preferred one set
+ through wl_data_offer.set_actions when handling an "ask" action.
+
+ Compositors may also change the selected action on the fly, mainly
+ in response to keyboard modifier changes during the drag-and-drop
+ operation.
+
+ The most recent action received is always the valid one. Prior to
+ receiving wl_data_device.drop, the chosen action may change (e.g.
+ due to keyboard modifiers being pressed). At the time of receiving
+ wl_data_device.drop the drag-and-drop destination must honor the
+ last action received.
+
+ Action changes may still happen after wl_data_device.drop,
+ especially on "ask" actions, where the drag-and-drop destination
+ may choose another action afterwards. Action changes happening
+ at this stage are always the result of inter-client negotiation, the
+ compositor shall no longer be able to induce a different action.
+
+ Upon "ask" actions, it is expected that the drag-and-drop destination
+ may potentially choose a different action and/or mime type,
+ based on wl_data_offer.source_actions and finally chosen by the
+ user (e.g. popping up a menu with the available options). The
+ final wl_data_offer.set_actions and wl_data_offer.accept requests
+ must happen before the call to wl_data_offer.finish.
+ </description>
+ <arg name="dnd_action" type="uint" summary="action selected by the compositor"/>
+ </event>
+ </interface>
+
+ <interface name="wl_data_source" version="3">
+ <description summary="offer to transfer data">
+ The wl_data_source object is the source side of a wl_data_offer.
+ It is created by the source client in a data transfer and
+ provides a way to describe the offered data and a way to respond
+ to requests to transfer the data.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_action_mask" value="0"
+ summary="action mask contains invalid values"/>
+ <entry name="invalid_source" value="1"
+ summary="source doesn't accept this request"/>
+ </enum>
+
+ <request name="offer">
+ <description summary="add an offered mime type">
+ This request adds a mime type to the set of mime types
+ advertised to targets. Can be called several times to offer
+ multiple types.
+ </description>
+ <arg name="mime_type" type="string" summary="mime type offered by the data source"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the data source">
+ Destroy the data source.
+ </description>
+ </request>
+
+ <event name="target">
+ <description summary="a target accepts an offered mime type">
+ Sent when a target accepts pointer_focus or motion events. If
+ a target does not accept any of the offered types, type is NULL.
+
+ Used for feedback during drag-and-drop.
+ </description>
+ <arg name="mime_type" type="string" allow-null="true" summary="mime type accepted by the target"/>
+ </event>
+
+ <event name="send">
+ <description summary="send the data">
+ Request for data from the client. Send the data as the
+ specified mime type over the passed file descriptor, then
+ close it.
+ </description>
+ <arg name="mime_type" type="string" summary="mime type for the data"/>
+ <arg name="fd" type="fd" summary="file descriptor for the data"/>
+ </event>
+
+ <event name="cancelled">
+ <description summary="selection was cancelled">
+ This data source is no longer valid. There are several reasons why
+ this could happen:
+
+ - The data source has been replaced by another data source.
+ - The drag-and-drop operation was performed, but the drop destination
+ did not accept any of the mime types offered through
+ wl_data_source.target.
+ - The drag-and-drop operation was performed, but the drop destination
+ did not select any of the actions present in the mask offered through
+ wl_data_source.action.
+ - The drag-and-drop operation was performed but didn't happen over a
+ surface.
+ - The compositor cancelled the drag-and-drop operation (e.g. compositor
+ dependent timeouts to avoid stale drag-and-drop transfers).
+
+ The client should clean up and destroy this data source.
+
+ For objects of version 2 or older, wl_data_source.cancelled will
+ only be emitted if the data source was replaced by another data
+ source.
+ </description>
+ </event>
+
+ <!-- Version 3 additions -->
+
+ <request name="set_actions" since="3">
+ <description summary="set the available drag-and-drop actions">
+ Sets the actions that the source side client supports for this
+ operation. This request may trigger wl_data_source.action and
+ wl_data_offer.action events if the compositor needs to change the
+ selected action.
+
+ The dnd_actions argument must contain only values expressed in the
+ wl_data_device_manager.dnd_actions enum, otherwise it will result
+ in a protocol error.
+
+ This request must be made once only, and can only be made on sources
+ used in drag-and-drop, so it must be performed before
+ wl_data_device.start_drag. Attempting to use the source other than
+ for drag-and-drop will raise a protocol error.
+ </description>
+ <arg name="dnd_actions" type="uint" summary="actions supported by the data source"/>
+ </request>
+
+ <event name="dnd_drop_performed" since="3">
+ <description summary="the drag-and-drop operation physically finished">
+ The user performed the drop action. This event does not indicate
+ acceptance, wl_data_source.cancelled may still be emitted afterwards
+ if the drop destination does not accept any mime type.
+
+ However, this event might however not be received if the compositor
+ cancelled the drag-and-drop operation before this event could happen.
+
+ Note that the data_source may still be used in the future and should
+ not be destroyed here.
+ </description>
+ </event>
+
+ <event name="dnd_finished" since="3">
+ <description summary="the drag-and-drop operation concluded">
+ The drop destination finished interoperating with this data
+ source, so the client is now free to destroy this data source and
+ free all associated data.
+
+ If the action used to perform the operation was "move", the
+ source can now delete the transferred data.
+ </description>
+ </event>
+
+ <event name="action" since="3">
+ <description summary="notify the selected action">
+ This event indicates the action selected by the compositor after
+ matching the source/destination side actions. Only one action (or
+ none) will be offered here.
+
+ This event can be emitted multiple times during the drag-and-drop
+ operation, mainly in response to destination side changes through
+ wl_data_offer.set_actions, and as the data device enters/leaves
+ surfaces.
+
+ It is only possible to receive this event after
+ wl_data_source.dnd_drop_performed if the drag-and-drop operation
+ ended in an "ask" action, in which case the final wl_data_source.action
+ event will happen immediately before wl_data_source.dnd_finished.
+
+ Compositors may also change the selected action on the fly, mainly
+ in response to keyboard modifier changes during the drag-and-drop
+ operation.
+
+ The most recent action received is always the valid one. The chosen
+ action may change alongside negotiation (e.g. an "ask" action can turn
+ into a "move" operation), so the effects of the final action must
+ always be applied in wl_data_offer.dnd_finished.
+
+ Clients can trigger cursor surface changes from this point, so
+ they reflect the current action.
+ </description>
+ <arg name="dnd_action" type="uint" summary="action selected by the compositor"/>
+ </event>
+ </interface>
+
+ <interface name="wl_data_device" version="3">
+ <description summary="data transfer device">
+ There is one wl_data_device per seat which can be obtained
+ from the global wl_data_device_manager singleton.
+
+ A wl_data_device provides access to inter-client data transfer
+ mechanisms such as copy-and-paste and drag-and-drop.
+ </description>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ </enum>
+
+ <request name="start_drag">
+ <description summary="start drag-and-drop operation">
+ This request asks the compositor to start a drag-and-drop
+ operation on behalf of the client.
+
+ The source argument is the data source that provides the data
+ for the eventual data transfer. If source is NULL, enter, leave
+ and motion events are sent only to the client that initiated the
+ drag and the client is expected to handle the data passing
+ internally.
+
+ The origin surface is the surface where the drag originates and
+ the client must have an active implicit grab that matches the
+ serial.
+
+ The icon surface is an optional (can be NULL) surface that
+ provides an icon to be moved around with the cursor. Initially,
+ the top-left corner of the icon surface is placed at the cursor
+ hotspot, but subsequent wl_surface.attach request can move the
+ relative position. Attach requests must be confirmed with
+ wl_surface.commit as usual. The icon surface is given the role of
+ a drag-and-drop icon. If the icon surface already has another role,
+ it raises a protocol error.
+
+ The current and pending input regions of the icon wl_surface are
+ cleared, and wl_surface.set_input_region is ignored until the
+ wl_surface is no longer used as the icon surface. When the use
+ as an icon ends, the current and pending input regions become
+ undefined, and the wl_surface is unmapped.
+ </description>
+ <arg name="source" type="object" interface="wl_data_source" allow-null="true" summary="data source for the eventual transfer"/>
+ <arg name="origin" type="object" interface="wl_surface" summary="surface where the drag originates"/>
+ <arg name="icon" type="object" interface="wl_surface" allow-null="true" summary="drag-and-drop icon surface"/>
+ <arg name="serial" type="uint" summary="serial number of the implicit grab on the origin"/>
+ </request>
+
+ <request name="set_selection">
+ <description summary="copy data to the selection">
+ This request asks the compositor to set the selection
+ to the data from the source on behalf of the client.
+
+ To unset the selection, set the source to NULL.
+ </description>
+ <arg name="source" type="object" interface="wl_data_source" allow-null="true" summary="data source for the selection"/>
+ <arg name="serial" type="uint" summary="serial number of the event that triggered this request"/>
+ </request>
+
+ <event name="data_offer">
+ <description summary="introduce a new wl_data_offer">
+ The data_offer event introduces a new wl_data_offer object,
+ which will subsequently be used in either the
+ data_device.enter event (for drag-and-drop) or the
+ data_device.selection event (for selections). Immediately
+ following the data_device_data_offer event, the new data_offer
+ object will send out data_offer.offer events to describe the
+ mime types it offers.
+ </description>
+ <arg name="id" type="new_id" interface="wl_data_offer" summary="the new data_offer object"/>
+ </event>
+
+ <event name="enter">
+ <description summary="initiate drag-and-drop session">
+ This event is sent when an active drag-and-drop pointer enters
+ a surface owned by the client. The position of the pointer at
+ enter time is provided by the x and y arguments, in surface-local
+ coordinates.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the enter event"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="client surface entered"/>
+ <arg name="x" type="fixed" summary="surface-local x coordinate"/>
+ <arg name="y" type="fixed" summary="surface-local y coordinate"/>
+ <arg name="id" type="object" interface="wl_data_offer" allow-null="true"
+ summary="source data_offer object"/>
+ </event>
+
+ <event name="leave">
+ <description summary="end drag-and-drop session">
+ This event is sent when the drag-and-drop pointer leaves the
+ surface and the session ends. The client must destroy the
+ wl_data_offer introduced at enter time at this point.
+ </description>
+ </event>
+
+ <event name="motion">
+ <description summary="drag-and-drop session motion">
+ This event is sent when the drag-and-drop pointer moves within
+ the currently focused surface. The new position of the pointer
+ is provided by the x and y arguments, in surface-local
+ coordinates.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="x" type="fixed" summary="surface-local x coordinate"/>
+ <arg name="y" type="fixed" summary="surface-local y coordinate"/>
+ </event>
+
+ <event name="drop">
+ <description summary="end drag-and-drop session successfully">
+ The event is sent when a drag-and-drop operation is ended
+ because the implicit grab is removed.
+
+ The drag-and-drop destination is expected to honor the last action
+ received through wl_data_offer.action, if the resulting action is
+ "copy" or "move", the destination can still perform
+ wl_data_offer.receive requests, and is expected to end all
+ transfers with a wl_data_offer.finish request.
+
+ If the resulting action is "ask", the action will not be considered
+ final. The drag-and-drop destination is expected to perform one last
+ wl_data_offer.set_actions request, or wl_data_offer.destroy in order
+ to cancel the operation.
+ </description>
+ </event>
+
+ <event name="selection">
+ <description summary="advertise new selection">
+ The selection event is sent out to notify the client of a new
+ wl_data_offer for the selection for this device. The
+ data_device.data_offer and the data_offer.offer events are
+ sent out immediately before this event to introduce the data
+ offer object. The selection event is sent to a client
+ immediately before receiving keyboard focus and when a new
+ selection is set while the client has keyboard focus. The
+ data_offer is valid until a new data_offer or NULL is received
+ or until the client loses keyboard focus. The client must
+ destroy the previous selection data_offer, if any, upon receiving
+ this event.
+ </description>
+ <arg name="id" type="object" interface="wl_data_offer" allow-null="true"
+ summary="selection data_offer object"/>
+ </event>
+
+ <!-- Version 2 additions -->
+
+ <request name="release" type="destructor" since="2">
+ <description summary="destroy data device">
+ This request destroys the data device.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="wl_data_device_manager" version="3">
+ <description summary="data transfer interface">
+ The wl_data_device_manager is a singleton global object that
+ provides access to inter-client data transfer mechanisms such as
+ copy-and-paste and drag-and-drop. These mechanisms are tied to
+ a wl_seat and this interface lets a client get a wl_data_device
+ corresponding to a wl_seat.
+
+ Depending on the version bound, the objects created from the bound
+ wl_data_device_manager object will have different requirements for
+ functioning properly. See wl_data_source.set_actions,
+ wl_data_offer.accept and wl_data_offer.finish for details.
+ </description>
+
+ <request name="create_data_source">
+ <description summary="create a new data source">
+ Create a new data source.
+ </description>
+ <arg name="id" type="new_id" interface="wl_data_source" summary="data source to create"/>
+ </request>
+
+ <request name="get_data_device">
+ <description summary="create a new data device">
+ Create a new data device for a given seat.
+ </description>
+ <arg name="id" type="new_id" interface="wl_data_device" summary="data device to create"/>
+ <arg name="seat" type="object" interface="wl_seat" summary="seat associated with the data device"/>
+ </request>
+
+ <!-- Version 3 additions -->
+
+ <enum name="dnd_action" bitfield="true" since="3">
+ <description summary="drag and drop actions">
+ This is a bitmask of the available/preferred actions in a
+ drag-and-drop operation.
+
+ In the compositor, the selected action is a result of matching the
+ actions offered by the source and destination sides. "action" events
+ with a "none" action will be sent to both source and destination if
+ there is no match. All further checks will effectively happen on
+ (source actions ∩ destination actions).
+
+ In addition, compositors may also pick different actions in
+ reaction to key modifiers being pressed. One common design that
+ is used in major toolkits (and the behavior recommended for
+ compositors) is:
+
+ - If no modifiers are pressed, the first match (in bit order)
+ will be used.
+ - Pressing Shift selects "move", if enabled in the mask.
+ - Pressing Control selects "copy", if enabled in the mask.
+
+ Behavior beyond that is considered implementation-dependent.
+ Compositors may for example bind other modifiers (like Alt/Meta)
+ or drags initiated with other buttons than BTN_LEFT to specific
+ actions (e.g. "ask").
+ </description>
+ <entry name="none" value="0" summary="no action"/>
+ <entry name="copy" value="1" summary="copy action"/>
+ <entry name="move" value="2" summary="move action"/>
+ <entry name="ask" value="4" summary="ask action"/>
+ </enum>
+ </interface>
+
+ <interface name="wl_shell" version="1">
+ <description summary="create desktop-style surfaces">
+ This interface is implemented by servers that provide
+ desktop-style user interfaces.
+
+ It allows clients to associate a wl_shell_surface with
+ a basic surface.
+ </description>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ </enum>
+
+ <request name="get_shell_surface">
+ <description summary="create a shell surface from a surface">
+ Create a shell surface for an existing surface. This gives
+ the wl_surface the role of a shell surface. If the wl_surface
+ already has another role, it raises a protocol error.
+
+ Only one shell surface can be associated with a given surface.
+ </description>
+ <arg name="id" type="new_id" interface="wl_shell_surface" summary="shell surface to create"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="surface to be given the shell surface role"/>
+ </request>
+ </interface>
+
+ <interface name="wl_shell_surface" version="1">
+ <description summary="desktop-style metadata interface">
+ An interface that may be implemented by a wl_surface, for
+ implementations that provide a desktop-style user interface.
+
+ It provides requests to treat surfaces like toplevel, fullscreen
+ or popup windows, move, resize or maximize them, associate
+ metadata like title and class, etc.
+
+ On the server side the object is automatically destroyed when
+ the related wl_surface is destroyed. On the client side,
+ wl_shell_surface_destroy() must be called before destroying
+ the wl_surface object.
+ </description>
+
+ <request name="pong">
+ <description summary="respond to a ping event">
+ A client must respond to a ping event with a pong request or
+ the client may be deemed unresponsive.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the ping event"/>
+ </request>
+
+ <request name="move">
+ <description summary="start an interactive move">
+ Start a pointer-driven move of the surface.
+
+ This request must be used in response to a button press event.
+ The server may ignore move requests depending on the state of
+ the surface (e.g. fullscreen or maximized).
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="seat whose pointer is used"/>
+ <arg name="serial" type="uint" summary="serial number of the implicit grab on the pointer"/>
+ </request>
+
+ <enum name="resize" bitfield="true">
+ <description summary="edge values for resizing">
+ These values are used to indicate which edge of a surface
+ is being dragged in a resize operation. The server may
+ use this information to adapt its behavior, e.g. choose
+ an appropriate cursor image.
+ </description>
+ <entry name="none" value="0" summary="no edge"/>
+ <entry name="top" value="1" summary="top edge"/>
+ <entry name="bottom" value="2" summary="bottom edge"/>
+ <entry name="left" value="4" summary="left edge"/>
+ <entry name="top_left" value="5" summary="top and left edges"/>
+ <entry name="bottom_left" value="6" summary="bottom and left edges"/>
+ <entry name="right" value="8" summary="right edge"/>
+ <entry name="top_right" value="9" summary="top and right edges"/>
+ <entry name="bottom_right" value="10" summary="bottom and right edges"/>
+ </enum>
+
+ <request name="resize">
+ <description summary="start an interactive resize">
+ Start a pointer-driven resizing of the surface.
+
+ This request must be used in response to a button press event.
+ The server may ignore resize requests depending on the state of
+ the surface (e.g. fullscreen or maximized).
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="seat whose pointer is used"/>
+ <arg name="serial" type="uint" summary="serial number of the implicit grab on the pointer"/>
+ <arg name="edges" type="uint" enum="resize" summary="which edge or corner is being dragged"/>
+ </request>
+
+ <request name="set_toplevel">
+ <description summary="make the surface a toplevel surface">
+ Map the surface as a toplevel surface.
+
+ A toplevel surface is not fullscreen, maximized or transient.
+ </description>
+ </request>
+
+ <enum name="transient" bitfield="true">
+ <description summary="details of transient behaviour">
+ These flags specify details of the expected behaviour
+ of transient surfaces. Used in the set_transient request.
+ </description>
+ <entry name="inactive" value="0x1" summary="do not set keyboard focus"/>
+ </enum>
+
+ <request name="set_transient">
+ <description summary="make the surface a transient surface">
+ Map the surface relative to an existing surface.
+
+ The x and y arguments specify the location of the upper left
+ corner of the surface relative to the upper left corner of the
+ parent surface, in surface-local coordinates.
+
+ The flags argument controls details of the transient behaviour.
+ </description>
+ <arg name="parent" type="object" interface="wl_surface" summary="parent surface"/>
+ <arg name="x" type="int" summary="surface-local x coordinate"/>
+ <arg name="y" type="int" summary="surface-local y coordinate"/>
+ <arg name="flags" type="uint" enum="transient" summary="transient surface behavior"/>
+ </request>
+
+ <enum name="fullscreen_method">
+ <description summary="different method to set the surface fullscreen">
+ Hints to indicate to the compositor how to deal with a conflict
+ between the dimensions of the surface and the dimensions of the
+ output. The compositor is free to ignore this parameter.
+ </description>
+ <entry name="default" value="0" summary="no preference, apply default policy"/>
+ <entry name="scale" value="1" summary="scale, preserve the surface's aspect ratio and center on output"/>
+ <entry name="driver" value="2" summary="switch output mode to the smallest mode that can fit the surface, add black borders to compensate size mismatch"/>
+ <entry name="fill" value="3" summary="no upscaling, center on output and add black borders to compensate size mismatch"/>
+ </enum>
+
+ <request name="set_fullscreen">
+ <description summary="make the surface a fullscreen surface">
+ Map the surface as a fullscreen surface.
+
+ If an output parameter is given then the surface will be made
+ fullscreen on that output. If the client does not specify the
+ output then the compositor will apply its policy - usually
+ choosing the output on which the surface has the biggest surface
+ area.
+
+ The client may specify a method to resolve a size conflict
+ between the output size and the surface size - this is provided
+ through the method parameter.
+
+ The framerate parameter is used only when the method is set
+ to "driver", to indicate the preferred framerate. A value of 0
+ indicates that the client does not care about framerate. The
+ framerate is specified in mHz, that is framerate of 60000 is 60Hz.
+
+ A method of "scale" or "driver" implies a scaling operation of
+ the surface, either via a direct scaling operation or a change of
+ the output mode. This will override any kind of output scaling, so
+ that mapping a surface with a buffer size equal to the mode can
+ fill the screen independent of buffer_scale.
+
+ A method of "fill" means we don't scale up the buffer, however
+ any output scale is applied. This means that you may run into
+ an edge case where the application maps a buffer with the same
+ size of the output mode but buffer_scale 1 (thus making a
+ surface larger than the output). In this case it is allowed to
+ downscale the results to fit the screen.
+
+ The compositor must reply to this request with a configure event
+ with the dimensions for the output on which the surface will
+ be made fullscreen.
+ </description>
+ <arg name="method" type="uint" enum="fullscreen_method" summary="method for resolving size conflict"/>
+ <arg name="framerate" type="uint" summary="framerate in mHz"/>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"
+ summary="output on which the surface is to be fullscreen"/>
+ </request>
+
+ <request name="set_popup">
+ <description summary="make the surface a popup surface">
+ Map the surface as a popup.
+
+ A popup surface is a transient surface with an added pointer
+ grab.
+
+ An existing implicit grab will be changed to owner-events mode,
+ and the popup grab will continue after the implicit grab ends
+ (i.e. releasing the mouse button does not cause the popup to
+ be unmapped).
+
+ The popup grab continues until the window is destroyed or a
+ mouse button is pressed in any other client's window. A click
+ in any of the client's surfaces is reported as normal, however,
+ clicks in other clients' surfaces will be discarded and trigger
+ the callback.
+
+ The x and y arguments specify the location of the upper left
+ corner of the surface relative to the upper left corner of the
+ parent surface, in surface-local coordinates.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="seat whose pointer is used"/>
+ <arg name="serial" type="uint" summary="serial number of the implicit grab on the pointer"/>
+ <arg name="parent" type="object" interface="wl_surface" summary="parent surface"/>
+ <arg name="x" type="int" summary="surface-local x coordinate"/>
+ <arg name="y" type="int" summary="surface-local y coordinate"/>
+ <arg name="flags" type="uint" enum="transient" summary="transient surface behavior"/>
+ </request>
+
+ <request name="set_maximized">
+ <description summary="make the surface a maximized surface">
+ Map the surface as a maximized surface.
+
+ If an output parameter is given then the surface will be
+ maximized on that output. If the client does not specify the
+ output then the compositor will apply its policy - usually
+ choosing the output on which the surface has the biggest surface
+ area.
+
+ The compositor will reply with a configure event telling
+ the expected new surface size. The operation is completed
+ on the next buffer attach to this surface.
+
+ A maximized surface typically fills the entire output it is
+ bound to, except for desktop elements such as panels. This is
+ the main difference between a maximized shell surface and a
+ fullscreen shell surface.
+
+ The details depend on the compositor implementation.
+ </description>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"
+ summary="output on which the surface is to be maximized"/>
+ </request>
+
+ <request name="set_title">
+ <description summary="set surface title">
+ Set a short title for the surface.
+
+ This string may be used to identify the surface in a task bar,
+ window list, or other user interface elements provided by the
+ compositor.
+
+ The string must be encoded in UTF-8.
+ </description>
+ <arg name="title" type="string" summary="surface title"/>
+ </request>
+
+ <request name="set_class">
+ <description summary="set surface class">
+ Set a class for the surface.
+
+ The surface class identifies the general class of applications
+ to which the surface belongs. A common convention is to use the
+ file name (or the full path if it is a non-standard location) of
+ the application's .desktop file as the class.
+ </description>
+ <arg name="class_" type="string" summary="surface class"/>
+ </request>
+
+ <event name="ping">
+ <description summary="ping client">
+ Ping a client to check if it is receiving events and sending
+ requests. A client is expected to reply with a pong request.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the ping"/>
+ </event>
+
+ <event name="configure">
+ <description summary="suggest resize">
+ The configure event asks the client to resize its surface.
+
+ The size is a hint, in the sense that the client is free to
+ ignore it if it doesn't resize, pick a smaller size (to
+ satisfy aspect ratio or resize in steps of NxM pixels).
+
+ The edges parameter provides a hint about how the surface
+ was resized. The client may use this information to decide
+ how to adjust its content to the new size (e.g. a scrolling
+ area might adjust its content position to leave the viewable
+ content unmoved).
+
+ The client is free to dismiss all but the last configure
+ event it received.
+
+ The width and height arguments specify the size of the window
+ in surface-local coordinates.
+ </description>
+ <arg name="edges" type="uint" enum="resize" summary="how the surface was resized"/>
+ <arg name="width" type="int" summary="new width of the surface"/>
+ <arg name="height" type="int" summary="new height of the surface"/>
+ </event>
+
+ <event name="popup_done">
+ <description summary="popup interaction is done">
+ The popup_done event is sent out when a popup grab is broken,
+ that is, when the user clicks a surface that doesn't belong
+ to the client owning the popup surface.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="wl_surface" version="4">
+ <description summary="an onscreen surface">
+ A surface is a rectangular area that is displayed on the screen.
+ It has a location, size and pixel contents.
+
+ The size of a surface (and relative positions on it) is described
+ in surface-local coordinates, which may differ from the buffer
+ coordinates of the pixel content, in case a buffer_transform
+ or a buffer_scale is used.
+
+ A surface without a "role" is fairly useless: a compositor does
+ not know where, when or how to present it. The role is the
+ purpose of a wl_surface. Examples of roles are a cursor for a
+ pointer (as set by wl_pointer.set_cursor), a drag icon
+ (wl_data_device.start_drag), a sub-surface
+ (wl_subcompositor.get_subsurface), and a window as defined by a
+ shell protocol (e.g. wl_shell.get_shell_surface).
+
+ A surface can have only one role at a time. Initially a
+ wl_surface does not have a role. Once a wl_surface is given a
+ role, it is set permanently for the whole lifetime of the
+ wl_surface object. Giving the current role again is allowed,
+ unless explicitly forbidden by the relevant interface
+ specification.
+
+ Surface roles are given by requests in other interfaces such as
+ wl_pointer.set_cursor. The request should explicitly mention
+ that this request gives a role to a wl_surface. Often, this
+ request also creates a new protocol object that represents the
+ role and adds additional functionality to wl_surface. When a
+ client wants to destroy a wl_surface, they must destroy this 'role
+ object' before the wl_surface.
+
+ Destroying the role object does not remove the role from the
+ wl_surface, but it may stop the wl_surface from "playing the role".
+ For instance, if a wl_subsurface object is destroyed, the wl_surface
+ it was created for will be unmapped and forget its position and
+ z-order. It is allowed to create a wl_subsurface for the same
+ wl_surface again, but it is not allowed to use the wl_surface as
+ a cursor (cursor is a different role than sub-surface, and role
+ switching is not allowed).
+ </description>
+
+ <enum name="error">
+ <description summary="wl_surface error values">
+ These errors can be emitted in response to wl_surface requests.
+ </description>
+ <entry name="invalid_scale" value="0" summary="buffer scale value is invalid"/>
+ <entry name="invalid_transform" value="1" summary="buffer transform value is invalid"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="delete surface">
+ Deletes the surface and invalidates its object ID.
+ </description>
+ </request>
+
+ <request name="attach">
+ <description summary="set the surface contents">
+ Set a buffer as the content of this surface.
+
+ The new size of the surface is calculated based on the buffer
+ size transformed by the inverse buffer_transform and the
+ inverse buffer_scale. This means that the supplied buffer
+ must be an integer multiple of the buffer_scale.
+
+ The x and y arguments specify the location of the new pending
+ buffer's upper left corner, relative to the current buffer's upper
+ left corner, in surface-local coordinates. In other words, the
+ x and y, combined with the new surface size define in which
+ directions the surface's size changes.
+
+ Surface contents are double-buffered state, see wl_surface.commit.
+
+ The initial surface contents are void; there is no content.
+ wl_surface.attach assigns the given wl_buffer as the pending
+ wl_buffer. wl_surface.commit makes the pending wl_buffer the new
+ surface contents, and the size of the surface becomes the size
+ calculated from the wl_buffer, as described above. After commit,
+ there is no pending buffer until the next attach.
+
+ Committing a pending wl_buffer allows the compositor to read the
+ pixels in the wl_buffer. The compositor may access the pixels at
+ any time after the wl_surface.commit request. When the compositor
+ will not access the pixels anymore, it will send the
+ wl_buffer.release event. Only after receiving wl_buffer.release,
+ the client may reuse the wl_buffer. A wl_buffer that has been
+ attached and then replaced by another attach instead of committed
+ will not receive a release event, and is not used by the
+ compositor.
+
+ Destroying the wl_buffer after wl_buffer.release does not change
+ the surface contents. However, if the client destroys the
+ wl_buffer before receiving the wl_buffer.release event, the surface
+ contents become undefined immediately.
+
+ If wl_surface.attach is sent with a NULL wl_buffer, the
+ following wl_surface.commit will remove the surface content.
+ </description>
+ <arg name="buffer" type="object" interface="wl_buffer" allow-null="true"
+ summary="buffer of surface contents"/>
+ <arg name="x" type="int" summary="surface-local x coordinate"/>
+ <arg name="y" type="int" summary="surface-local y coordinate"/>
+ </request>
+
+ <request name="damage">
+ <description summary="mark part of the surface damaged">
+ This request is used to describe the regions where the pending
+ buffer is different from the current surface contents, and where
+ the surface therefore needs to be repainted. The compositor
+ ignores the parts of the damage that fall outside of the surface.
+
+ Damage is double-buffered state, see wl_surface.commit.
+
+ The damage rectangle is specified in surface-local coordinates,
+ where x and y specify the upper left corner of the damage rectangle.
+
+ The initial value for pending damage is empty: no damage.
+ wl_surface.damage adds pending damage: the new pending damage
+ is the union of old pending damage and the given rectangle.
+
+ wl_surface.commit assigns pending damage as the current damage,
+ and clears pending damage. The server will clear the current
+ damage as it repaints the surface.
+
+ Alternatively, damage can be posted with wl_surface.damage_buffer
+ which uses buffer coordinates instead of surface coordinates,
+ and is probably the preferred and intuitive way of doing this.
+ </description>
+ <arg name="x" type="int" summary="surface-local x coordinate"/>
+ <arg name="y" type="int" summary="surface-local y coordinate"/>
+ <arg name="width" type="int" summary="width of damage rectangle"/>
+ <arg name="height" type="int" summary="height of damage rectangle"/>
+ </request>
+
+ <request name="frame">
+ <description summary="request a frame throttling hint">
+ Request a notification when it is a good time to start drawing a new
+ frame, by creating a frame callback. This is useful for throttling
+ redrawing operations, and driving animations.
+
+ When a client is animating on a wl_surface, it can use the 'frame'
+ request to get notified when it is a good time to draw and commit the
+ next frame of animation. If the client commits an update earlier than
+ that, it is likely that some updates will not make it to the display,
+ and the client is wasting resources by drawing too often.
+
+ The frame request will take effect on the next wl_surface.commit.
+ The notification will only be posted for one frame unless
+ requested again. For a wl_surface, the notifications are posted in
+ the order the frame requests were committed.
+
+ The server must send the notifications so that a client
+ will not send excessive updates, while still allowing
+ the highest possible update rate for clients that wait for the reply
+ before drawing again. The server should give some time for the client
+ to draw and commit after sending the frame callback events to let it
+ hit the next output refresh.
+
+ A server should avoid signaling the frame callbacks if the
+ surface is not visible in any way, e.g. the surface is off-screen,
+ or completely obscured by other opaque surfaces.
+
+ The object returned by this request will be destroyed by the
+ compositor after the callback is fired and as such the client must not
+ attempt to use it after that point.
+
+ The callback_data passed in the callback is the current time, in
+ milliseconds, with an undefined base.
+ </description>
+ <arg name="callback" type="new_id" interface="wl_callback" summary="callback object for the frame request"/>
+ </request>
+
+ <request name="set_opaque_region">
+ <description summary="set opaque region">
+ This request sets the region of the surface that contains
+ opaque content.
+
+ The opaque region is an optimization hint for the compositor
+ that lets it optimize the redrawing of content behind opaque
+ regions. Setting an opaque region is not required for correct
+ behaviour, but marking transparent content as opaque will result
+ in repaint artifacts.
+
+ The opaque region is specified in surface-local coordinates.
+
+ The compositor ignores the parts of the opaque region that fall
+ outside of the surface.
+
+ Opaque region is double-buffered state, see wl_surface.commit.
+
+ wl_surface.set_opaque_region changes the pending opaque region.
+ wl_surface.commit copies the pending region to the current region.
+ Otherwise, the pending and current regions are never changed.
+
+ The initial value for an opaque region is empty. Setting the pending
+ opaque region has copy semantics, and the wl_region object can be
+ destroyed immediately. A NULL wl_region causes the pending opaque
+ region to be set to empty.
+ </description>
+ <arg name="region" type="object" interface="wl_region" allow-null="true"
+ summary="opaque region of the surface"/>
+ </request>
+
+ <request name="set_input_region">
+ <description summary="set input region">
+ This request sets the region of the surface that can receive
+ pointer and touch events.
+
+ Input events happening outside of this region will try the next
+ surface in the server surface stack. The compositor ignores the
+ parts of the input region that fall outside of the surface.
+
+ The input region is specified in surface-local coordinates.
+
+ Input region is double-buffered state, see wl_surface.commit.
+
+ wl_surface.set_input_region changes the pending input region.
+ wl_surface.commit copies the pending region to the current region.
+ Otherwise the pending and current regions are never changed,
+ except cursor and icon surfaces are special cases, see
+ wl_pointer.set_cursor and wl_data_device.start_drag.
+
+ The initial value for an input region is infinite. That means the
+ whole surface will accept input. Setting the pending input region
+ has copy semantics, and the wl_region object can be destroyed
+ immediately. A NULL wl_region causes the input region to be set
+ to infinite.
+ </description>
+ <arg name="region" type="object" interface="wl_region" allow-null="true"
+ summary="input region of the surface"/>
+ </request>
+
+ <request name="commit">
+ <description summary="commit pending surface state">
+ Surface state (input, opaque, and damage regions, attached buffers,
+ etc.) is double-buffered. Protocol requests modify the pending state,
+ as opposed to the current state in use by the compositor. A commit
+ request atomically applies all pending state, replacing the current
+ state. After commit, the new pending state is as documented for each
+ related request.
+
+ On commit, a pending wl_buffer is applied first, and all other state
+ second. This means that all coordinates in double-buffered state are
+ relative to the new wl_buffer coming into use, except for
+ wl_surface.attach itself. If there is no pending wl_buffer, the
+ coordinates are relative to the current surface contents.
+
+ All requests that need a commit to become effective are documented
+ to affect double-buffered state.
+
+ Other interfaces may add further double-buffered surface state.
+ </description>
+ </request>
+
+ <event name="enter">
+ <description summary="surface enters an output">
+ This is emitted whenever a surface's creation, movement, or resizing
+ results in some part of it being within the scanout region of an
+ output.
+
+ Note that a surface may be overlapping with zero or more outputs.
+ </description>
+ <arg name="output" type="object" interface="wl_output" summary="output entered by the surface"/>
+ </event>
+
+ <event name="leave">
+ <description summary="surface leaves an output">
+ This is emitted whenever a surface's creation, movement, or resizing
+ results in it no longer having any part of it within the scanout region
+ of an output.
+ </description>
+ <arg name="output" type="object" interface="wl_output" summary="output left by the surface"/>
+ </event>
+
+ <!-- Version 2 additions -->
+
+ <request name="set_buffer_transform" since="2">
+ <description summary="sets the buffer transformation">
+ This request sets an optional transformation on how the compositor
+ interprets the contents of the buffer attached to the surface. The
+ accepted values for the transform parameter are the values for
+ wl_output.transform.
+
+ Buffer transform is double-buffered state, see wl_surface.commit.
+
+ A newly created surface has its buffer transformation set to normal.
+
+ wl_surface.set_buffer_transform changes the pending buffer
+ transformation. wl_surface.commit copies the pending buffer
+ transformation to the current one. Otherwise, the pending and current
+ values are never changed.
+
+ The purpose of this request is to allow clients to render content
+ according to the output transform, thus permitting the compositor to
+ use certain optimizations even if the display is rotated. Using
+ hardware overlays and scanning out a client buffer for fullscreen
+ surfaces are examples of such optimizations. Those optimizations are
+ highly dependent on the compositor implementation, so the use of this
+ request should be considered on a case-by-case basis.
+
+ Note that if the transform value includes 90 or 270 degree rotation,
+ the width of the buffer will become the surface height and the height
+ of the buffer will become the surface width.
+
+ If transform is not one of the values from the
+ wl_output.transform enum the invalid_transform protocol error
+ is raised.
+ </description>
+ <arg name="transform" type="int" enum="wl_output.transform"
+ summary="transform for interpreting buffer contents"/>
+ </request>
+
+ <!-- Version 3 additions -->
+
+ <request name="set_buffer_scale" since="3">
+ <description summary="sets the buffer scaling factor">
+ This request sets an optional scaling factor on how the compositor
+ interprets the contents of the buffer attached to the window.
+
+ Buffer scale is double-buffered state, see wl_surface.commit.
+
+ A newly created surface has its buffer scale set to 1.
+
+ wl_surface.set_buffer_scale changes the pending buffer scale.
+ wl_surface.commit copies the pending buffer scale to the current one.
+ Otherwise, the pending and current values are never changed.
+
+ The purpose of this request is to allow clients to supply higher
+ resolution buffer data for use on high resolution outputs. It is
+ intended that you pick the same buffer scale as the scale of the
+ output that the surface is displayed on. This means the compositor
+ can avoid scaling when rendering the surface on that output.
+
+ Note that if the scale is larger than 1, then you have to attach
+ a buffer that is larger (by a factor of scale in each dimension)
+ than the desired surface size.
+
+ If scale is not positive the invalid_scale protocol error is
+ raised.
+ </description>
+ <arg name="scale" type="int"
+ summary="positive scale for interpreting buffer contents"/>
+ </request>
+
+ <!-- Version 4 additions -->
+ <request name="damage_buffer" since="4">
+ <description summary="mark part of the surface damaged using buffer coordinates">
+ This request is used to describe the regions where the pending
+ buffer is different from the current surface contents, and where
+ the surface therefore needs to be repainted. The compositor
+ ignores the parts of the damage that fall outside of the surface.
+
+ Damage is double-buffered state, see wl_surface.commit.
+
+ The damage rectangle is specified in buffer coordinates,
+ where x and y specify the upper left corner of the damage rectangle.
+
+ The initial value for pending damage is empty: no damage.
+ wl_surface.damage_buffer adds pending damage: the new pending
+ damage is the union of old pending damage and the given rectangle.
+
+ wl_surface.commit assigns pending damage as the current damage,
+ and clears pending damage. The server will clear the current
+ damage as it repaints the surface.
+
+ This request differs from wl_surface.damage in only one way - it
+ takes damage in buffer coordinates instead of surface-local
+ coordinates. While this generally is more intuitive than surface
+ coordinates, it is especially desirable when using wp_viewport
+ or when a drawing library (like EGL) is unaware of buffer scale
+ and buffer transform.
+
+ Note: Because buffer transformation changes and damage requests may
+ be interleaved in the protocol stream, it is impossible to determine
+ the actual mapping between surface and buffer damage until
+ wl_surface.commit time. Therefore, compositors wishing to take both
+ kinds of damage into account will have to accumulate damage from the
+ two requests separately and only transform from one to the other
+ after receiving the wl_surface.commit.
+ </description>
+ <arg name="x" type="int" summary="buffer-local x coordinate"/>
+ <arg name="y" type="int" summary="buffer-local y coordinate"/>
+ <arg name="width" type="int" summary="width of damage rectangle"/>
+ <arg name="height" type="int" summary="height of damage rectangle"/>
+ </request>
+ </interface>
+
+ <interface name="wl_seat" version="6">
+ <description summary="group of input devices">
+ A seat is a group of keyboards, pointer and touch devices. This
+ object is published as a global during start up, or when such a
+ device is hot plugged. A seat typically has a pointer and
+ maintains a keyboard focus and a pointer focus.
+ </description>
+
+ <enum name="capability" bitfield="true">
+ <description summary="seat capability bitmask">
+ This is a bitmask of capabilities this seat has; if a member is
+ set, then it is present on the seat.
+ </description>
+ <entry name="pointer" value="1" summary="the seat has pointer devices"/>
+ <entry name="keyboard" value="2" summary="the seat has one or more keyboards"/>
+ <entry name="touch" value="4" summary="the seat has touch devices"/>
+ </enum>
+
+ <event name="capabilities">
+ <description summary="seat capabilities changed">
+ This is emitted whenever a seat gains or loses the pointer,
+ keyboard or touch capabilities. The argument is a capability
+ enum containing the complete set of capabilities this seat has.
+
+ When the pointer capability is added, a client may create a
+ wl_pointer object using the wl_seat.get_pointer request. This object
+ will receive pointer events until the capability is removed in the
+ future.
+
+ When the pointer capability is removed, a client should destroy the
+ wl_pointer objects associated with the seat where the capability was
+ removed, using the wl_pointer.release request. No further pointer
+ events will be received on these objects.
+
+ In some compositors, if a seat regains the pointer capability and a
+ client has a previously obtained wl_pointer object of version 4 or
+ less, that object may start sending pointer events again. This
+ behavior is considered a misinterpretation of the intended behavior
+ and must not be relied upon by the client. wl_pointer objects of
+ version 5 or later must not send events if created before the most
+ recent event notifying the client of an added pointer capability.
+
+ The above behavior also applies to wl_keyboard and wl_touch with the
+ keyboard and touch capabilities, respectively.
+ </description>
+ <arg name="capabilities" type="uint" enum="capability" summary="capabilities of the seat"/>
+ </event>
+
+ <request name="get_pointer">
+ <description summary="return pointer object">
+ The ID provided will be initialized to the wl_pointer interface
+ for this seat.
+
+ This request only takes effect if the seat has the pointer
+ capability, or has had the pointer capability in the past.
+ It is a protocol violation to issue this request on a seat that has
+ never had the pointer capability.
+ </description>
+ <arg name="id" type="new_id" interface="wl_pointer" summary="seat pointer"/>
+ </request>
+
+ <request name="get_keyboard">
+ <description summary="return keyboard object">
+ The ID provided will be initialized to the wl_keyboard interface
+ for this seat.
+
+ This request only takes effect if the seat has the keyboard
+ capability, or has had the keyboard capability in the past.
+ It is a protocol violation to issue this request on a seat that has
+ never had the keyboard capability.
+ </description>
+ <arg name="id" type="new_id" interface="wl_keyboard" summary="seat keyboard"/>
+ </request>
+
+ <request name="get_touch">
+ <description summary="return touch object">
+ The ID provided will be initialized to the wl_touch interface
+ for this seat.
+
+ This request only takes effect if the seat has the touch
+ capability, or has had the touch capability in the past.
+ It is a protocol violation to issue this request on a seat that has
+ never had the touch capability.
+ </description>
+ <arg name="id" type="new_id" interface="wl_touch" summary="seat touch interface"/>
+ </request>
+
+ <!-- Version 2 additions -->
+
+ <event name="name" since="2">
+ <description summary="unique identifier for this seat">
+ In a multiseat configuration this can be used by the client to help
+ identify which physical devices the seat represents. Based on
+ the seat configuration used by the compositor.
+ </description>
+ <arg name="name" type="string" summary="seat identifier"/>
+ </event>
+
+ <!-- Version 5 additions -->
+
+ <request name="release" type="destructor" since="5">
+ <description summary="release the seat object">
+ Using this request a client can tell the server that it is not going to
+ use the seat object anymore.
+ </description>
+ </request>
+
+ </interface>
+
+ <interface name="wl_pointer" version="6">
+ <description summary="pointer input device">
+ The wl_pointer interface represents one or more input devices,
+ such as mice, which control the pointer location and pointer_focus
+ of a seat.
+
+ The wl_pointer interface generates motion, enter and leave
+ events for the surfaces that the pointer is located over,
+ and button and axis events for button presses, button releases
+ and scrolling.
+ </description>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ </enum>
+
+ <request name="set_cursor">
+ <description summary="set the pointer surface">
+ Set the pointer surface, i.e., the surface that contains the
+ pointer image (cursor). This request gives the surface the role
+ of a cursor. If the surface already has another role, it raises
+ a protocol error.
+
+ The cursor actually changes only if the pointer
+ focus for this device is one of the requesting client's surfaces
+ or the surface parameter is the current pointer surface. If
+ there was a previous surface set with this request it is
+ replaced. If surface is NULL, the pointer image is hidden.
+
+ The parameters hotspot_x and hotspot_y define the position of
+ the pointer surface relative to the pointer location. Its
+ top-left corner is always at (x, y) - (hotspot_x, hotspot_y),
+ where (x, y) are the coordinates of the pointer location, in
+ surface-local coordinates.
+
+ On surface.attach requests to the pointer surface, hotspot_x
+ and hotspot_y are decremented by the x and y parameters
+ passed to the request. Attach must be confirmed by
+ wl_surface.commit as usual.
+
+ The hotspot can also be updated by passing the currently set
+ pointer surface to this request with new values for hotspot_x
+ and hotspot_y.
+
+ The current and pending input regions of the wl_surface are
+ cleared, and wl_surface.set_input_region is ignored until the
+ wl_surface is no longer used as the cursor. When the use as a
+ cursor ends, the current and pending input regions become
+ undefined, and the wl_surface is unmapped.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the enter event"/>
+ <arg name="surface" type="object" interface="wl_surface" allow-null="true"
+ summary="pointer surface"/>
+ <arg name="hotspot_x" type="int" summary="surface-local x coordinate"/>
+ <arg name="hotspot_y" type="int" summary="surface-local y coordinate"/>
+ </request>
+
+ <event name="enter">
+ <description summary="enter event">
+ Notification that this seat's pointer is focused on a certain
+ surface.
+
+ When a seat's focus enters a surface, the pointer image
+ is undefined and a client should respond to this event by setting
+ an appropriate pointer image with the set_cursor request.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the enter event"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="surface entered by the pointer"/>
+ <arg name="surface_x" type="fixed" summary="surface-local x coordinate"/>
+ <arg name="surface_y" type="fixed" summary="surface-local y coordinate"/>
+ </event>
+
+ <event name="leave">
+ <description summary="leave event">
+ Notification that this seat's pointer is no longer focused on
+ a certain surface.
+
+ The leave notification is sent before the enter notification
+ for the new focus.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the leave event"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="surface left by the pointer"/>
+ </event>
+
+ <event name="motion">
+ <description summary="pointer motion event">
+ Notification of pointer location change. The arguments
+ surface_x and surface_y are the location relative to the
+ focused surface.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="surface_x" type="fixed" summary="surface-local x coordinate"/>
+ <arg name="surface_y" type="fixed" summary="surface-local y coordinate"/>
+ </event>
+
+ <enum name="button_state">
+ <description summary="physical button state">
+ Describes the physical state of a button that produced the button
+ event.
+ </description>
+ <entry name="released" value="0" summary="the button is not pressed"/>
+ <entry name="pressed" value="1" summary="the button is pressed"/>
+ </enum>
+
+ <event name="button">
+ <description summary="pointer button event">
+ Mouse button click and release notifications.
+
+ The location of the click is given by the last motion or
+ enter event.
+ The time argument is a timestamp with millisecond
+ granularity, with an undefined base.
+
+ The button is a button code as defined in the Linux kernel's
+ linux/input-event-codes.h header file, e.g. BTN_LEFT.
+
+ Any 16-bit button code value is reserved for future additions to the
+ kernel's event code list. All other button codes above 0xFFFF are
+ currently undefined but may be used in future versions of this
+ protocol.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the button event"/>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="button" type="uint" summary="button that produced the event"/>
+ <arg name="state" type="uint" enum="button_state" summary="physical state of the button"/>
+ </event>
+
+ <enum name="axis">
+ <description summary="axis types">
+ Describes the axis types of scroll events.
+ </description>
+ <entry name="vertical_scroll" value="0" summary="vertical axis"/>
+ <entry name="horizontal_scroll" value="1" summary="horizontal axis"/>
+ </enum>
+
+ <event name="axis">
+ <description summary="axis event">
+ Scroll and other axis notifications.
+
+ For scroll events (vertical and horizontal scroll axes), the
+ value parameter is the length of a vector along the specified
+ axis in a coordinate space identical to those of motion events,
+ representing a relative movement along the specified axis.
+
+ For devices that support movements non-parallel to axes multiple
+ axis events will be emitted.
+
+ When applicable, for example for touch pads, the server can
+ choose to emit scroll events where the motion vector is
+ equivalent to a motion event vector.
+
+ When applicable, a client can transform its content relative to the
+ scroll distance.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="axis" type="uint" enum="axis" summary="axis type"/>
+ <arg name="value" type="fixed" summary="length of vector in surface-local coordinate space"/>
+ </event>
+
+ <!-- Version 3 additions -->
+
+ <request name="release" type="destructor" since="3">
+ <description summary="release the pointer object">
+ Using this request a client can tell the server that it is not going to
+ use the pointer object anymore.
+
+ This request destroys the pointer proxy object, so clients must not call
+ wl_pointer_destroy() after using this request.
+ </description>
+ </request>
+
+ <!-- Version 5 additions -->
+
+ <event name="frame" since="5">
+ <description summary="end of a pointer event sequence">
+ Indicates the end of a set of events that logically belong together.
+ A client is expected to accumulate the data in all events within the
+ frame before proceeding.
+
+ All wl_pointer events before a wl_pointer.frame event belong
+ logically together. For example, in a diagonal scroll motion the
+ compositor will send an optional wl_pointer.axis_source event, two
+ wl_pointer.axis events (horizontal and vertical) and finally a
+ wl_pointer.frame event. The client may use this information to
+ calculate a diagonal vector for scrolling.
+
+ When multiple wl_pointer.axis events occur within the same frame,
+ the motion vector is the combined motion of all events.
+ When a wl_pointer.axis and a wl_pointer.axis_stop event occur within
+ the same frame, this indicates that axis movement in one axis has
+ stopped but continues in the other axis.
+ When multiple wl_pointer.axis_stop events occur within the same
+ frame, this indicates that these axes stopped in the same instance.
+
+ A wl_pointer.frame event is sent for every logical event group,
+ even if the group only contains a single wl_pointer event.
+ Specifically, a client may get a sequence: motion, frame, button,
+ frame, axis, frame, axis_stop, frame.
+
+ The wl_pointer.enter and wl_pointer.leave events are logical events
+ generated by the compositor and not the hardware. These events are
+ also grouped by a wl_pointer.frame. When a pointer moves from one
+ surface to another, a compositor should group the
+ wl_pointer.leave event within the same wl_pointer.frame.
+ However, a client must not rely on wl_pointer.leave and
+ wl_pointer.enter being in the same wl_pointer.frame.
+ Compositor-specific policies may require the wl_pointer.leave and
+ wl_pointer.enter event being split across multiple wl_pointer.frame
+ groups.
+ </description>
+ </event>
+
+ <enum name="axis_source">
+ <description summary="axis source types">
+ Describes the source types for axis events. This indicates to the
+ client how an axis event was physically generated; a client may
+ adjust the user interface accordingly. For example, scroll events
+ from a "finger" source may be in a smooth coordinate space with
+ kinetic scrolling whereas a "wheel" source may be in discrete steps
+ of a number of lines.
+
+ The "continuous" axis source is a device generating events in a
+ continuous coordinate space, but using something other than a
+ finger. One example for this source is button-based scrolling where
+ the vertical motion of a device is converted to scroll events while
+ a button is held down.
+
+ The "wheel tilt" axis source indicates that the actual device is a
+ wheel but the scroll event is not caused by a rotation but a
+ (usually sideways) tilt of the wheel.
+ </description>
+ <entry name="wheel" value="0" summary="a physical wheel rotation" />
+ <entry name="finger" value="1" summary="finger on a touch surface" />
+ <entry name="continuous" value="2" summary="continuous coordinate space"/>
+ <entry name="wheel_tilt" value="3" summary="a physical wheel tilt" since="6"/>
+ </enum>
+
+ <event name="axis_source" since="5">
+ <description summary="axis source event">
+ Source information for scroll and other axes.
+
+ This event does not occur on its own. It is sent before a
+ wl_pointer.frame event and carries the source information for
+ all events within that frame.
+
+ The source specifies how this event was generated. If the source is
+ wl_pointer.axis_source.finger, a wl_pointer.axis_stop event will be
+ sent when the user lifts the finger off the device.
+
+ If the source is wl_pointer.axis_source.wheel,
+ wl_pointer.axis_source.wheel_tilt or
+ wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event may
+ or may not be sent. Whether a compositor sends an axis_stop event
+ for these sources is hardware-specific and implementation-dependent;
+ clients must not rely on receiving an axis_stop event for these
+ scroll sources and should treat scroll sequences from these scroll
+ sources as unterminated by default.
+
+ This event is optional. If the source is unknown for a particular
+ axis event sequence, no event is sent.
+ Only one wl_pointer.axis_source event is permitted per frame.
+
+ The order of wl_pointer.axis_discrete and wl_pointer.axis_source is
+ not guaranteed.
+ </description>
+ <arg name="axis_source" type="uint" enum="axis_source" summary="source of the axis event"/>
+ </event>
+
+ <event name="axis_stop" since="5">
+ <description summary="axis stop event">
+ Stop notification for scroll and other axes.
+
+ For some wl_pointer.axis_source types, a wl_pointer.axis_stop event
+ is sent to notify a client that the axis sequence has terminated.
+ This enables the client to implement kinetic scrolling.
+ See the wl_pointer.axis_source documentation for information on when
+ this event may be generated.
+
+ Any wl_pointer.axis events with the same axis_source after this
+ event should be considered as the start of a new axis motion.
+
+ The timestamp is to be interpreted identical to the timestamp in the
+ wl_pointer.axis event. The timestamp value may be the same as a
+ preceding wl_pointer.axis event.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="axis" type="uint" enum="axis" summary="the axis stopped with this event"/>
+ </event>
+
+ <event name="axis_discrete" since="5">
+ <description summary="axis click event">
+ Discrete step information for scroll and other axes.
+
+ This event carries the axis value of the wl_pointer.axis event in
+ discrete steps (e.g. mouse wheel clicks).
+
+ This event does not occur on its own, it is coupled with a
+ wl_pointer.axis event that represents this axis value on a
+ continuous scale. The protocol guarantees that each axis_discrete
+ event is always followed by exactly one axis event with the same
+ axis number within the same wl_pointer.frame. Note that the protocol
+ allows for other events to occur between the axis_discrete and
+ its coupled axis event, including other axis_discrete or axis
+ events.
+
+ This event is optional; continuous scrolling devices
+ like two-finger scrolling on touchpads do not have discrete
+ steps and do not generate this event.
+
+ The discrete value carries the directional information. e.g. a value
+ of -2 is two steps towards the negative direction of this axis.
+
+ The axis number is identical to the axis number in the associated
+ axis event.
+
+ The order of wl_pointer.axis_discrete and wl_pointer.axis_source is
+ not guaranteed.
+ </description>
+ <arg name="axis" type="uint" enum="axis" summary="axis type"/>
+ <arg name="discrete" type="int" summary="number of steps"/>
+ </event>
+ </interface>
+
+ <interface name="wl_keyboard" version="6">
+ <description summary="keyboard input device">
+ The wl_keyboard interface represents one or more keyboards
+ associated with a seat.
+ </description>
+
+ <enum name="keymap_format">
+ <description summary="keyboard mapping format">
+ This specifies the format of the keymap provided to the
+ client with the wl_keyboard.keymap event.
+ </description>
+ <entry name="no_keymap" value="0"
+ summary="no keymap; client must understand how to interpret the raw keycode"/>
+ <entry name="xkb_v1" value="1"
+ summary="libxkbcommon compatible; to determine the xkb keycode, clients must add 8 to the key event keycode"/>
+ </enum>
+
+ <event name="keymap">
+ <description summary="keyboard mapping">
+ This event provides a file descriptor to the client which can be
+ memory-mapped to provide a keyboard mapping description.
+ </description>
+ <arg name="format" type="uint" enum="keymap_format" summary="keymap format"/>
+ <arg name="fd" type="fd" summary="keymap file descriptor"/>
+ <arg name="size" type="uint" summary="keymap size, in bytes"/>
+ </event>
+
+ <event name="enter">
+ <description summary="enter event">
+ Notification that this seat's keyboard focus is on a certain
+ surface.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the enter event"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="surface gaining keyboard focus"/>
+ <arg name="keys" type="array" summary="the currently pressed keys"/>
+ </event>
+
+ <event name="leave">
+ <description summary="leave event">
+ Notification that this seat's keyboard focus is no longer on
+ a certain surface.
+
+ The leave notification is sent before the enter notification
+ for the new focus.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the leave event"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="surface that lost keyboard focus"/>
+ </event>
+
+ <enum name="key_state">
+ <description summary="physical key state">
+ Describes the physical state of a key that produced the key event.
+ </description>
+ <entry name="released" value="0" summary="key is not pressed"/>
+ <entry name="pressed" value="1" summary="key is pressed"/>
+ </enum>
+
+ <event name="key">
+ <description summary="key event">
+ A key was pressed or released.
+ The time argument is a timestamp with millisecond
+ granularity, with an undefined base.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the key event"/>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="key" type="uint" summary="key that produced the event"/>
+ <arg name="state" type="uint" enum="key_state" summary="physical state of the key"/>
+ </event>
+
+ <event name="modifiers">
+ <description summary="modifier and group state">
+ Notifies clients that the modifier and/or group state has
+ changed, and it should update its local state.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the modifiers event"/>
+ <arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
+ <arg name="mods_latched" type="uint" summary="latched modifiers"/>
+ <arg name="mods_locked" type="uint" summary="locked modifiers"/>
+ <arg name="group" type="uint" summary="keyboard layout"/>
+ </event>
+
+ <!-- Version 3 additions -->
+
+ <request name="release" type="destructor" since="3">
+ <description summary="release the keyboard object"/>
+ </request>
+
+ <!-- Version 4 additions -->
+
+ <event name="repeat_info" since="4">
+ <description summary="repeat rate and delay">
+ Informs the client about the keyboard's repeat rate and delay.
+
+ This event is sent as soon as the wl_keyboard object has been created,
+ and is guaranteed to be received by the client before any key press
+ event.
+
+ Negative values for either rate or delay are illegal. A rate of zero
+ will disable any repeating (regardless of the value of delay).
+
+ This event can be sent later on as well with a new value if necessary,
+ so clients should continue listening for the event past the creation
+ of wl_keyboard.
+ </description>
+ <arg name="rate" type="int"
+ summary="the rate of repeating keys in characters per second"/>
+ <arg name="delay" type="int"
+ summary="delay in milliseconds since key down until repeating starts"/>
+ </event>
+ </interface>
+
+ <interface name="wl_touch" version="6">
+ <description summary="touchscreen input device">
+ The wl_touch interface represents a touchscreen
+ associated with a seat.
+
+ Touch interactions can consist of one or more contacts.
+ For each contact, a series of events is generated, starting
+ with a down event, followed by zero or more motion events,
+ and ending with an up event. Events relating to the same
+ contact point can be identified by the ID of the sequence.
+ </description>
+
+ <event name="down">
+ <description summary="touch down event and beginning of a touch sequence">
+ A new touch point has appeared on the surface. This touch point is
+ assigned a unique ID. Future events from this touch point reference
+ this ID. The ID ceases to be valid after a touch up event and may be
+ reused in the future.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the touch down event"/>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="surface touched"/>
+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
+ <arg name="x" type="fixed" summary="surface-local x coordinate"/>
+ <arg name="y" type="fixed" summary="surface-local y coordinate"/>
+ </event>
+
+ <event name="up">
+ <description summary="end of a touch event sequence">
+ The touch point has disappeared. No further events will be sent for
+ this touch point and the touch point's ID is released and may be
+ reused in a future touch down event.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the touch up event"/>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
+ </event>
+
+ <event name="motion">
+ <description summary="update of touch point coordinates">
+ A touch point has changed coordinates.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
+ <arg name="x" type="fixed" summary="surface-local x coordinate"/>
+ <arg name="y" type="fixed" summary="surface-local y coordinate"/>
+ </event>
+
+ <event name="frame">
+ <description summary="end of touch frame event">
+ Indicates the end of a set of events that logically belong together.
+ A client is expected to accumulate the data in all events within the
+ frame before proceeding.
+
+ A wl_touch.frame terminates at least one event but otherwise no
+ guarantee is provided about the set of events within a frame. A client
+ must assume that any state not updated in a frame is unchanged from the
+ previously known state.
+ </description>
+ </event>
+
+ <event name="cancel">
+ <description summary="touch session cancelled">
+ Sent if the compositor decides the touch stream is a global
+ gesture. No further events are sent to the clients from that
+ particular gesture. Touch cancellation applies to all touch points
+ currently active on this client's surface. The client is
+ responsible for finalizing the touch points, future touch points on
+ this surface may reuse the touch point ID.
+ </description>
+ </event>
+
+ <!-- Version 3 additions -->
+
+ <request name="release" type="destructor" since="3">
+ <description summary="release the touch object"/>
+ </request>
+
+ <!-- Version 6 additions -->
+
+ <event name="shape" since="6">
+ <description summary="update shape of touch point">
+ Sent when a touchpoint has changed its shape.
+
+ This event does not occur on its own. It is sent before a
+ wl_touch.frame event and carries the new shape information for
+ any previously reported, or new touch points of that frame.
+
+ Other events describing the touch point such as wl_touch.down,
+ wl_touch.motion or wl_touch.orientation may be sent within the
+ same wl_touch.frame. A client should treat these events as a single
+ logical touch point update. The order of wl_touch.shape,
+ wl_touch.orientation and wl_touch.motion is not guaranteed.
+ A wl_touch.down event is guaranteed to occur before the first
+ wl_touch.shape event for this touch ID but both events may occur within
+ the same wl_touch.frame.
+
+ A touchpoint shape is approximated by an ellipse through the major and
+ minor axis length. The major axis length describes the longer diameter
+ of the ellipse, while the minor axis length describes the shorter
+ diameter. Major and minor are orthogonal and both are specified in
+ surface-local coordinates. The center of the ellipse is always at the
+ touchpoint location as reported by wl_touch.down or wl_touch.move.
+
+ This event is only sent by the compositor if the touch device supports
+ shape reports. The client has to make reasonable assumptions about the
+ shape if it did not receive this event.
+ </description>
+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
+ <arg name="major" type="fixed" summary="length of the major axis in surface-local coordinates"/>
+ <arg name="minor" type="fixed" summary="length of the minor axis in surface-local coordinates"/>
+ </event>
+
+ <event name="orientation" since="6">
+ <description summary="update orientation of touch point">
+ Sent when a touchpoint has changed its orientation.
+
+ This event does not occur on its own. It is sent before a
+ wl_touch.frame event and carries the new shape information for
+ any previously reported, or new touch points of that frame.
+
+ Other events describing the touch point such as wl_touch.down,
+ wl_touch.motion or wl_touch.shape may be sent within the
+ same wl_touch.frame. A client should treat these events as a single
+ logical touch point update. The order of wl_touch.shape,
+ wl_touch.orientation and wl_touch.motion is not guaranteed.
+ A wl_touch.down event is guaranteed to occur before the first
+ wl_touch.orientation event for this touch ID but both events may occur
+ within the same wl_touch.frame.
+
+ The orientation describes the clockwise angle of a touchpoint's major
+ axis to the positive surface y-axis and is normalized to the -180 to
+ +180 degree range. The granularity of orientation depends on the touch
+ device, some devices only support binary rotation values between 0 and
+ 90 degrees.
+
+ This event is only sent by the compositor if the touch device supports
+ orientation reports.
+ </description>
+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
+ <arg name="orientation" type="fixed" summary="angle between major axis and positive surface y-axis in degrees"/>
+ </event>
+ </interface>
+
+ <interface name="wl_output" version="3">
+ <description summary="compositor output region">
+ An output describes part of the compositor geometry. The
+ compositor works in the 'compositor coordinate system' and an
+ output corresponds to a rectangular area in that space that is
+ actually visible. This typically corresponds to a monitor that
+ displays part of the compositor space. This object is published
+ as global during start up, or when a monitor is hotplugged.
+ </description>
+
+ <enum name="subpixel">
+ <description summary="subpixel geometry information">
+ This enumeration describes how the physical
+ pixels on an output are laid out.
+ </description>
+ <entry name="unknown" value="0" summary="unknown geometry"/>
+ <entry name="none" value="1" summary="no geometry"/>
+ <entry name="horizontal_rgb" value="2" summary="horizontal RGB"/>
+ <entry name="horizontal_bgr" value="3" summary="horizontal BGR"/>
+ <entry name="vertical_rgb" value="4" summary="vertical RGB"/>
+ <entry name="vertical_bgr" value="5" summary="vertical BGR"/>
+ </enum>
+
+ <enum name="transform">
+ <description summary="transform from framebuffer to output">
+ This describes the transform that a compositor will apply to a
+ surface to compensate for the rotation or mirroring of an
+ output device.
+
+ The flipped values correspond to an initial flip around a
+ vertical axis followed by rotation.
+
+ The purpose is mainly to allow clients to render accordingly and
+ tell the compositor, so that for fullscreen surfaces, the
+ compositor will still be able to scan out directly from client
+ surfaces.
+ </description>
+ <entry name="normal" value="0" summary="no transform"/>
+ <entry name="90" value="1" summary="90 degrees counter-clockwise"/>
+ <entry name="180" value="2" summary="180 degrees counter-clockwise"/>
+ <entry name="270" value="3" summary="270 degrees counter-clockwise"/>
+ <entry name="flipped" value="4" summary="180 degree flip around a vertical axis"/>
+ <entry name="flipped_90" value="5" summary="flip and rotate 90 degrees counter-clockwise"/>
+ <entry name="flipped_180" value="6" summary="flip and rotate 180 degrees counter-clockwise"/>
+ <entry name="flipped_270" value="7" summary="flip and rotate 270 degrees counter-clockwise"/>
+ </enum>
+
+ <event name="geometry">
+ <description summary="properties of the output">
+ The geometry event describes geometric properties of the output.
+ The event is sent when binding to the output object and whenever
+ any of the properties change.
+ </description>
+ <arg name="x" type="int"
+ summary="x position within the global compositor space"/>
+ <arg name="y" type="int"
+ summary="y position within the global compositor space"/>
+ <arg name="physical_width" type="int"
+ summary="width in millimeters of the output"/>
+ <arg name="physical_height" type="int"
+ summary="height in millimeters of the output"/>
+ <arg name="subpixel" type="int" enum="subpixel"
+ summary="subpixel orientation of the output"/>
+ <arg name="make" type="string"
+ summary="textual description of the manufacturer"/>
+ <arg name="model" type="string"
+ summary="textual description of the model"/>
+ <arg name="transform" type="int" enum="transform"
+ summary="transform that maps framebuffer to output"/>
+ </event>
+
+ <enum name="mode" bitfield="true">
+ <description summary="mode information">
+ These flags describe properties of an output mode.
+ They are used in the flags bitfield of the mode event.
+ </description>
+ <entry name="current" value="0x1"
+ summary="indicates this is the current mode"/>
+ <entry name="preferred" value="0x2"
+ summary="indicates this is the preferred mode"/>
+ </enum>
+
+ <event name="mode">
+ <description summary="advertise available modes for the output">
+ The mode event describes an available mode for the output.
+
+ The event is sent when binding to the output object and there
+ will always be one mode, the current mode. The event is sent
+ again if an output changes mode, for the mode that is now
+ current. In other words, the current mode is always the last
+ mode that was received with the current flag set.
+
+ The size of a mode is given in physical hardware units of
+ the output device. This is not necessarily the same as
+ the output size in the global compositor space. For instance,
+ the output may be scaled, as described in wl_output.scale,
+ or transformed, as described in wl_output.transform.
+ </description>
+ <arg name="flags" type="uint" enum="mode" summary="bitfield of mode flags"/>
+ <arg name="width" type="int" summary="width of the mode in hardware units"/>
+ <arg name="height" type="int" summary="height of the mode in hardware units"/>
+ <arg name="refresh" type="int" summary="vertical refresh rate in mHz"/>
+ </event>
+
+ <!-- Version 2 additions -->
+
+ <event name="done" since="2">
+ <description summary="sent all information about output">
+ This event is sent after all other properties have been
+ sent after binding to the output object and after any
+ other property changes done after that. This allows
+ changes to the output properties to be seen as
+ atomic, even if they happen via multiple events.
+ </description>
+ </event>
+
+ <event name="scale" since="2">
+ <description summary="output scaling properties">
+ This event contains scaling geometry information
+ that is not in the geometry event. It may be sent after
+ binding the output object or if the output scale changes
+ later. If it is not sent, the client should assume a
+ scale of 1.
+
+ A scale larger than 1 means that the compositor will
+ automatically scale surface buffers by this amount
+ when rendering. This is used for very high resolution
+ displays where applications rendering at the native
+ resolution would be too small to be legible.
+
+ It is intended that scaling aware clients track the
+ current output of a surface, and if it is on a scaled
+ output it should use wl_surface.set_buffer_scale with
+ the scale of the output. That way the compositor can
+ avoid scaling the surface, and the client can supply
+ a higher detail image.
+ </description>
+ <arg name="factor" type="int" summary="scaling factor of output"/>
+ </event>
+
+ <!-- Version 3 additions -->
+
+ <request name="release" type="destructor" since="3">
+ <description summary="release the output object">
+ Using this request a client can tell the server that it is not going to
+ use the output object anymore.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="wl_region" version="1">
+ <description summary="region interface">
+ A region object describes an area.
+
+ Region objects are used to describe the opaque and input
+ regions of a surface.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy region">
+ Destroy the region. This will invalidate the object ID.
+ </description>
+ </request>
+
+ <request name="add">
+ <description summary="add rectangle to region">
+ Add the specified rectangle to the region.
+ </description>
+ <arg name="x" type="int" summary="region-local x coordinate"/>
+ <arg name="y" type="int" summary="region-local y coordinate"/>
+ <arg name="width" type="int" summary="rectangle width"/>
+ <arg name="height" type="int" summary="rectangle height"/>
+ </request>
+
+ <request name="subtract">
+ <description summary="subtract rectangle from region">
+ Subtract the specified rectangle from the region.
+ </description>
+ <arg name="x" type="int" summary="region-local x coordinate"/>
+ <arg name="y" type="int" summary="region-local y coordinate"/>
+ <arg name="width" type="int" summary="rectangle width"/>
+ <arg name="height" type="int" summary="rectangle height"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subcompositor" version="1">
+ <description summary="sub-surface compositing">
+ The global interface exposing sub-surface compositing capabilities.
+ A wl_surface, that has sub-surfaces associated, is called the
+ parent surface. Sub-surfaces can be arbitrarily nested and create
+ a tree of sub-surfaces.
+
+ The root surface in a tree of sub-surfaces is the main
+ surface. The main surface cannot be a sub-surface, because
+ sub-surfaces must always have a parent.
+
+ A main surface with its sub-surfaces forms a (compound) window.
+ For window management purposes, this set of wl_surface objects is
+ to be considered as a single window, and it should also behave as
+ such.
+
+ The aim of sub-surfaces is to offload some of the compositing work
+ within a window from clients to the compositor. A prime example is
+ a video player with decorations and video in separate wl_surface
+ objects. This should allow the compositor to pass YUV video buffer
+ processing to dedicated overlay hardware when possible.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the subcompositor interface">
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other
+ objects, wl_subsurface objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="the to-be sub-surface is invalid"/>
+ </enum>
+
+ <request name="get_subsurface">
+ <description summary="give a surface the role sub-surface">
+ Create a sub-surface interface for the given surface, and
+ associate it with the given parent surface. This turns a
+ plain wl_surface into a sub-surface.
+
+ The to-be sub-surface must not already have another role, and it
+ must not have an existing wl_subsurface object. Otherwise a protocol
+ error is raised.
+ </description>
+ <arg name="id" type="new_id" interface="wl_subsurface"
+ summary="the new sub-surface object ID"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface to be turned into a sub-surface"/>
+ <arg name="parent" type="object" interface="wl_surface"
+ summary="the parent surface"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subsurface" version="1">
+ <description summary="sub-surface interface to a wl_surface">
+ An additional interface to a wl_surface object, which has been
+ made a sub-surface. A sub-surface has one parent surface. A
+ sub-surface's size and position are not limited to that of the parent.
+ Particularly, a sub-surface is not automatically clipped to its
+ parent's area.
+
+ A sub-surface becomes mapped, when a non-NULL wl_buffer is applied
+ and the parent surface is mapped. The order of which one happens
+ first is irrelevant. A sub-surface is hidden if the parent becomes
+ hidden, or if a NULL wl_buffer is applied. These rules apply
+ recursively through the tree of surfaces.
+
+ The behaviour of a wl_surface.commit request on a sub-surface
+ depends on the sub-surface's mode. The possible modes are
+ synchronized and desynchronized, see methods
+ wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized
+ mode caches the wl_surface state to be applied when the parent's
+ state gets applied, and desynchronized mode applies the pending
+ wl_surface state directly. A sub-surface is initially in the
+ synchronized mode.
+
+ Sub-surfaces have also other kind of state, which is managed by
+ wl_subsurface requests, as opposed to wl_surface requests. This
+ state includes the sub-surface position relative to the parent
+ surface (wl_subsurface.set_position), and the stacking order of
+ the parent and its sub-surfaces (wl_subsurface.place_above and
+ .place_below). This state is applied when the parent surface's
+ wl_surface state is applied, regardless of the sub-surface's mode.
+ As the exception, set_sync and set_desync are effective immediately.
+
+ The main surface can be thought to be always in desynchronized mode,
+ since it does not have a parent in the sub-surfaces sense.
+
+ Even if a sub-surface is in desynchronized mode, it will behave as
+ in synchronized mode, if its parent surface behaves as in
+ synchronized mode. This rule is applied recursively throughout the
+ tree of surfaces. This means, that one can set a sub-surface into
+ synchronized mode, and then assume that all its child and grand-child
+ sub-surfaces are synchronized, too, without explicitly setting them.
+
+ If the wl_surface associated with the wl_subsurface is destroyed, the
+ wl_subsurface object becomes inert. Note, that destroying either object
+ takes effect immediately. If you need to synchronize the removal
+ of a sub-surface to the parent surface update, unmap the sub-surface
+ first by attaching a NULL wl_buffer, update parent, and then destroy
+ the sub-surface.
+
+ If the parent wl_surface object is destroyed, the sub-surface is
+ unmapped.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove sub-surface interface">
+ The sub-surface interface is removed from the wl_surface object
+ that was turned into a sub-surface with a
+ wl_subcompositor.get_subsurface request. The wl_surface's association
+ to the parent is deleted, and the wl_surface loses its role as
+ a sub-surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="wl_surface is not a sibling or the parent"/>
+ </enum>
+
+ <request name="set_position">
+ <description summary="reposition the sub-surface">
+ This schedules a sub-surface position change.
+ The sub-surface will be moved so that its origin (top left
+ corner pixel) will be at the location x, y of the parent surface
+ coordinate system. The coordinates are not restricted to the parent
+ surface area. Negative values are allowed.
+
+ The scheduled coordinates will take effect whenever the state of the
+ parent surface is applied. When this happens depends on whether the
+ parent surface is in synchronized mode or not. See
+ wl_subsurface.set_sync and wl_subsurface.set_desync for details.
+
+ If more than one set_position request is invoked by the client before
+ the commit of the parent surface, the position of a new request always
+ replaces the scheduled position from any previous request.
+
+ The initial position is 0, 0.
+ </description>
+ <arg name="x" type="int" summary="x coordinate in the parent surface"/>
+ <arg name="y" type="int" summary="y coordinate in the parent surface"/>
+ </request>
+
+ <request name="place_above">
+ <description summary="restack the sub-surface">
+ This sub-surface is taken from the stack, and put back just
+ above the reference surface, changing the z-order of the sub-surfaces.
+ The reference surface must be one of the sibling surfaces, or the
+ parent surface. Using any other surface, including this sub-surface,
+ will cause a protocol error.
+
+ The z-order is double-buffered. Requests are handled in order and
+ applied immediately to a pending state. The final pending state is
+ copied to the active state the next time the state of the parent
+ surface is applied. When this happens depends on whether the parent
+ surface is in synchronized mode or not. See wl_subsurface.set_sync and
+ wl_subsurface.set_desync for details.
+
+ A new sub-surface is initially added as the top-most in the stack
+ of its siblings and parent.
+ </description>
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="place_below">
+ <description summary="restack the sub-surface">
+ The sub-surface is placed just below the reference surface.
+ See wl_subsurface.place_above.
+ </description>
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="set_sync">
+ <description summary="set sub-surface to synchronized mode">
+ Change the commit behaviour of the sub-surface to synchronized
+ mode, also described as the parent dependent mode.
+
+ In synchronized mode, wl_surface.commit on a sub-surface will
+ accumulate the committed state in a cache, but the state will
+ not be applied and hence will not change the compositor output.
+ The cached state is applied to the sub-surface immediately after
+ the parent surface's state is applied. This ensures atomic
+ updates of the parent and all its synchronized sub-surfaces.
+ Applying the cached state will invalidate the cache, so further
+ parent surface commits do not (re-)apply old state.
+
+ See wl_subsurface for the recursive effect of this mode.
+ </description>
+ </request>
+
+ <request name="set_desync">
+ <description summary="set sub-surface to desynchronized mode">
+ Change the commit behaviour of the sub-surface to desynchronized
+ mode, also described as independent or freely running mode.
+
+ In desynchronized mode, wl_surface.commit on a sub-surface will
+ apply the pending state directly, without caching, as happens
+ normally with a wl_surface. Calling wl_surface.commit on the
+ parent surface has no effect on the sub-surface's wl_surface
+ state. This mode allows a sub-surface to be updated on its own.
+
+ If cached state exists when wl_surface.commit is called in
+ desynchronized mode, the pending state is added to the cached
+ state, and applied as a whole. This invalidates the cache.
+
+ Note: even if a sub-surface is set to desynchronized, a parent
+ sub-surface may override it to behave as synchronized. For details,
+ see wl_subsurface.
+
+ If a surface's parent surface behaves as desynchronized, then
+ the cached state is applied on set_desync.
+ </description>
+ </request>
+ </interface>
+
+</protocol>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_foreign_toplevel_management_unstable_v1">
+ <copyright>
+ Copyright © 2018 Ilia Bozhinov
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="zwlr_foreign_toplevel_manager_v1" version="2">
+ <description summary="list and control opened apps">
+ The purpose of this protocol is to enable the creation of taskbars
+ and docks by providing them with a list of opened applications and
+ letting them request certain actions on them, like maximizing, etc.
+
+ After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
+ toplevel window will be sent via the toplevel event
+ </description>
+
+ <event name="toplevel">
+ <description summary="a toplevel has been created">
+ This event is emitted whenever a new toplevel window is created. It
+ is emitted for all toplevels, regardless of the app that has created
+ them.
+
+ All initial details of the toplevel(title, app_id, states, etc.) will
+ be sent immediately after this event via the corresponding events in
+ zwlr_foreign_toplevel_handle_v1.
+ </description>
+ <arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
+ </event>
+
+ <request name="stop">
+ <description summary="stop sending events">
+ Indicates the client no longer wishes to receive events for new toplevels.
+ However the compositor may emit further toplevel_created events, until
+ the finished event is emitted.
+
+ The client must not send any more requests after this one.
+ </description>
+ </request>
+
+ <event name="finished">
+ <description summary="the compositor has finished with the toplevel manager">
+ This event indicates that the compositor is done sending events to the
+ zwlr_foreign_toplevel_manager_v1. The server will destroy the object
+ immediately after sending this request, so it will become invalid and
+ the client should free any resources associated with it.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zwlr_foreign_toplevel_handle_v1" version="2">
+ <description summary="an opened toplevel">
+ A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
+ window. Each app may have multiple opened toplevels.
+
+ Each toplevel has a list of outputs it is visible on, conveyed to the
+ client with the output_enter and output_leave events.
+ </description>
+
+ <event name="title">
+ <description summary="title change">
+ This event is emitted whenever the title of the toplevel changes.
+ </description>
+ <arg name="title" type="string"/>
+ </event>
+
+ <event name="app_id">
+ <description summary="app-id change">
+ This event is emitted whenever the app-id of the toplevel changes.
+ </description>
+ <arg name="app_id" type="string"/>
+ </event>
+
+ <event name="output_enter">
+ <description summary="toplevel entered an output">
+ This event is emitted whenever the toplevel becomes visible on
+ the given output. A toplevel may be visible on multiple outputs.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <event name="output_leave">
+ <description summary="toplevel left an output">
+ This event is emitted whenever the toplevel stops being visible on
+ the given output. It is guaranteed that an entered-output event
+ with the same output has been emitted before this event.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <request name="set_maximized">
+ <description summary="requests that the toplevel be maximized">
+ Requests that the toplevel be maximized. If the maximized state actually
+ changes, this will be indicated by the state event.
+ </description>
+ </request>
+
+ <request name="unset_maximized">
+ <description summary="requests that the toplevel be unmaximized">
+ Requests that the toplevel be unmaximized. If the maximized state actually
+ changes, this will be indicated by the state event.
+ </description>
+ </request>
+
+ <request name="set_minimized">
+ <description summary="requests that the toplevel be minimized">
+ Requests that the toplevel be minimized. If the minimized state actually
+ changes, this will be indicated by the state event.
+ </description>
+ </request>
+
+ <request name="unset_minimized">
+ <description summary="requests that the toplevel be unminimized">
+ Requests that the toplevel be unminimized. If the minimized state actually
+ changes, this will be indicated by the state event.
+ </description>
+ </request>
+
+ <request name="activate">
+ <description summary="activate the toplevel">
+ Request that this toplevel be activated on the given seat.
+ There is no guarantee the toplevel will be actually activated.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ </request>
+
+ <enum name="state">
+ <description summary="types of states on the toplevel">
+ The different states that a toplevel can have. These have the same meaning
+ as the states with the same names defined in xdg-toplevel
+ </description>
+
+ <entry name="maximized" value="0" summary="the toplevel is maximized"/>
+ <entry name="minimized" value="1" summary="the toplevel is minimized"/>
+ <entry name="activated" value="2" summary="the toplevel is active"/>
+ <entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
+ </enum>
+
+ <event name="state">
+ <description summary="the toplevel state changed">
+ This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
+ is created and each time the toplevel state changes, either because of a
+ compositor action or because of a request in this protocol.
+ </description>
+
+ <arg name="state" type="array"/>
+ </event>
+
+ <event name="done">
+ <description summary="all information about the toplevel has been sent">
+ This event is sent after all changes in the toplevel state have been
+ sent.
+
+ This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
+ to be seen as atomic, even if they happen via multiple events.
+ </description>
+ </event>
+
+ <request name="close">
+ <description summary="request that the toplevel be closed">
+ Send a request to the toplevel to close itself. The compositor would
+ typically use a shell-specific method to carry out this request, for
+ example by sending the xdg_toplevel.close event. However, this gives
+ no guarantees the toplevel will actually be destroyed. If and when
+ this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
+ be emitted.
+ </description>
+ </request>
+
+ <request name="set_rectangle">
+ <description summary="the rectangle which represents the toplevel">
+ The rectangle of the surface specified in this request corresponds to
+ the place where the app using this protocol represents the given toplevel.
+ It can be used by the compositor as a hint for some operations, e.g
+ minimizing. The client is however not required to set this, in which
+ case the compositor is free to decide some default value.
+
+ If the client specifies more than one rectangle, only the last one is
+ considered.
+
+ The dimensions are given in surface-local coordinates.
+ Setting width=height=0 removes the already-set rectangle.
+ </description>
+
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <enum name="error">
+ <entry name="invalid_rectangle" value="0"
+ summary="the provided rectangle is invalid"/>
+ </enum>
+
+ <event name="closed">
+ <description summary="this toplevel has been destroyed">
+ This event means the toplevel has been destroyed. It is guaranteed there
+ won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
+ toplevel itself becomes inert so any requests will be ignored except the
+ destroy request.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
+ Destroys the zwlr_foreign_toplevel_handle_v1 object.
+
+ This request should be called either when the client does not want to
+ use the toplevel anymore or after the closed event to finalize the
+ destruction of the object.
+ </description>
+ </request>
+
+ <!-- Version 2 additions -->
+
+ <request name="set_fullscreen" since="2">
+ <description summary="request that the toplevel be fullscreened">
+ Requests that the toplevel be fullscreened on the given output. If the
+ fullscreen state and/or the outputs the toplevel is visible on actually
+ change, this will be indicated by the state and output_enter/leave
+ events.
+
+ The output parameter is only a hint to the compositor. Also, if output
+ is NULL, the compositor should decide which output the toplevel will be
+ fullscreened on, if at all.
+ </description>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ </request>
+
+ <request name="unset_fullscreen" since="2">
+ <description summary="request that the toplevel be unfullscreened">
+ Requests that the toplevel be unfullscreened. If the fullscreen state
+ actually changes, this will be indicated by the state event.
+ </description>
+ </request>
+ </interface>
+</protocol>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_layer_shell_unstable_v1">
+ <copyright>
+ Copyright © 2017 Drew DeVault
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="zwlr_layer_shell_v1" version="3">
+ <description summary="create surfaces that are layers of the desktop">
+ Clients can use this interface to assign the surface_layer role to
+ wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+ rendered with a defined z-depth respective to each other. They may also be
+ anchored to the edges and corners of a screen and specify input handling
+ semantics. This interface should be suitable for the implementation of
+ many desktop shell components, and a broad number of other applications
+ that interact with the desktop.
+ </description>
+
+ <request name="get_layer_surface">
+ <description summary="create a layer_surface from a surface">
+ Create a layer surface for an existing surface. This assigns the role of
+ layer_surface, or raises a protocol error if another role is already
+ assigned.
+
+ Creating a layer surface from a wl_surface which has a buffer attached
+ or committed is a client error, and any attempts by a client to attach
+ or manipulate a buffer prior to the first layer_surface.configure call
+ must also be treated as errors.
+
+ You may pass NULL for output to allow the compositor to decide which
+ output to use. Generally this will be the one that the user most
+ recently interacted with.
+
+ Clients can specify a namespace that defines the purpose of the layer
+ surface.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
+ <arg name="namespace" type="string" summary="namespace for the layer surface"/>
+ </request>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="wl_surface has another role"/>
+ <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
+ <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
+ </enum>
+
+ <enum name="layer">
+ <description summary="available layers for surfaces">
+ These values indicate which layers a surface can be rendered in. They
+ are ordered by z depth, bottom-most first. Traditional shell surfaces
+ will typically be rendered between the bottom and top layers.
+ Fullscreen shell surfaces are typically rendered at the top layer.
+ Multiple surfaces can share a single layer, and ordering within a
+ single layer is undefined.
+ </description>
+
+ <entry name="background" value="0"/>
+ <entry name="bottom" value="1"/>
+ <entry name="top" value="2"/>
+ <entry name="overlay" value="3"/>
+ </enum>
+
+ <!-- Version 3 additions -->
+
+ <request name="destroy" type="destructor" since="3">
+ <description summary="destroy the layer_shell object">
+ This request indicates that the client will not use the layer_shell
+ object any more. Objects that have been created through this instance
+ are not affected.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_layer_surface_v1" version="3">
+ <description summary="layer metadata interface">
+ An interface that may be implemented by a wl_surface, for surfaces that
+ are designed to be rendered as a layer of a stacked desktop-like
+ environment.
+
+ Layer surface state (layer, size, anchor, exclusive zone,
+ margin, interactivity) is double-buffered, and will be applied at the
+ time wl_surface.commit of the corresponding wl_surface is called.
+ </description>
+
+ <request name="set_size">
+ <description summary="sets the size of the surface">
+ Sets the size of the surface in surface-local coordinates. The
+ compositor will display the surface centered with respect to its
+ anchors.
+
+ If you pass 0 for either value, the compositor will assign it and
+ inform you of the assignment in the configure event. You must set your
+ anchor to opposite edges in the dimensions you omit; not doing so is a
+ protocol error. Both values are 0 by default.
+
+ Size is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </request>
+
+ <request name="set_anchor">
+ <description summary="configures the anchor point of the surface">
+ Requests that the compositor anchor the surface to the specified edges
+ and corners. If two 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 corner of the output); otherwise the anchor point
+ will be centered on that edge, or in the center if none is specified.
+
+ Anchor is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="anchor" type="uint" enum="anchor"/>
+ </request>
+
+ <request name="set_exclusive_zone">
+ <description summary="configures the exclusive geometry of this surface">
+ Requests that the compositor avoids occluding an area 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 one
+ edge or an edge and both perpendicular edges. If the surface is not
+ anchored, anchored to only two perpendicular edges (a corner), anchored
+ to only two parallel edges or anchored to all edges, a positive value
+ will be treated the same as zero.
+
+ A positive zone is the distance from the edge in surface-local
+ coordinates to consider 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 exclusive zone. If set to -1, the surface
+ indicates that it would not like to be moved to accommodate for other
+ surfaces, and the compositor should extend it all the way to the edges
+ it is anchored to.
+
+ For example, a panel might set its exclusive zone to 10, so that
+ maximized shell surfaces are not shown on top of it. A notification
+ might set its exclusive zone to 0, so that it is moved to avoid
+ occluding the panel, but shell surfaces are shown underneath it. A
+ wallpaper or lock screen might set their exclusive zone to -1, so that
+ they stretch below or over the panel.
+
+ The default value is 0.
+
+ Exclusive zone is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="zone" type="int"/>
+ </request>
+
+ <request name="set_margin">
+ <description summary="sets a margin from the anchor point">
+ Requests that the surface be placed some distance away from the anchor
+ point on the output, in surface-local coordinates. Setting this value
+ for edges you are not anchored to has no effect.
+
+ The exclusive zone includes the margin.
+
+ Margin is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="top" type="int"/>
+ <arg name="right" type="int"/>
+ <arg name="bottom" type="int"/>
+ <arg name="left" type="int"/>
+ </request>
+
+ <request name="set_keyboard_interactivity">
+ <description summary="requests keyboard events">
+ Set to 1 to request that the seat send keyboard events to this layer
+ surface. For layers below the shell surface layer, the seat will use
+ normal focus semantics. For layers above the shell surface layers, the
+ seat will always give exclusive keyboard focus to the top-most layer
+ which has keyboard interactivity set to true.
+
+ Layer surfaces receive pointer, touch, and tablet events normally. If
+ you do not want to receive them, set the input region on your surface
+ to an empty region.
+
+ Events is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="keyboard_interactivity" type="uint"/>
+ </request>
+
+ <request name="get_popup">
+ <description summary="assign this layer_surface as an xdg_popup parent">
+ This assigns an xdg_popup's parent to this layer_surface. This popup
+ should have been created via xdg_surface::get_popup with the parent set
+ to NULL, and this request must be invoked before committing the popup's
+ initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+ </description>
+ <arg name="popup" type="object" interface="xdg_popup"/>
+ </request>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the configure event"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the layer_surface">
+ This request destroys the layer surface.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event asks the client to resize its surface.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ The client is free to dismiss all but the last configure event it
+ received.
+
+ The width and height arguments specify the size of the window in
+ surface-local coordinates.
+
+ The size is a hint, in the sense that the client is free to ignore it if
+ it doesn't resize, pick a smaller size (to satisfy aspect ratio or
+ resize in steps of NxM pixels). If the client picks a smaller size and
+ is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
+ surface will be centered on this axis.
+
+ If the width or height arguments are zero, it means the client should
+ decide its own window dimension.
+ </description>
+ <arg name="serial" type="uint"/>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </event>
+
+ <event name="closed">
+ <description summary="surface should be closed">
+ The closed event is sent by the compositor when the surface will no
+ longer be shown. The output may have been destroyed or the user may
+ have asked for it to be removed. Further changes to the surface will be
+ ignored. The client should destroy the resource after receiving this
+ event, and create a new surface if they so choose.
+ </description>
+ </event>
+
+ <enum name="error">
+ <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
+ <entry name="invalid_size" value="1" summary="size is invalid"/>
+ <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
+ </enum>
+
+ <enum name="anchor" bitfield="true">
+ <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
+ <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
+ <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
+ <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
+ </enum>
+
+ <!-- Version 2 additions -->
+
+ <request name="set_layer" since="2">
+ <description summary="change the layer of the surface">
+ Change the layer that the surface is rendered on.
+
+ Layer is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
+ </request>
+ </interface>
+</protocol>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_output_unstable_v1">
+
+ <copyright>
+ Copyright © 2017 Red Hat Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="Protocol to describe output regions">
+ This protocol aims at describing outputs in a way which is more in line
+ with the concept of an output on desktop oriented systems.
+
+ Some information are more specific to the concept of an output for
+ a desktop oriented system and may not make sense in other applications,
+ such as IVI systems for example.
+
+ Typically, the global compositor space on a desktop system is made of
+ a contiguous or overlapping set of rectangular regions.
+
+ Some of the information provided in this protocol might be identical
+ to their counterparts already available from wl_output, in which case
+ the information provided by this protocol should be preferred to their
+ equivalent in wl_output. The goal is to move the desktop specific
+ concepts (such as output location within the global compositor space,
+ the connector name and types, etc.) out of the core wl_output protocol.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible
+ changes may be added together with the corresponding interface
+ version bump.
+ Backward incompatible changes are done by bumping the version
+ number in the protocol and interface names and resetting the
+ interface version. Once the protocol is to be declared stable,
+ the 'z' prefix and the version number in the protocol and
+ interface names are removed and the interface version number is
+ reset.
+ </description>
+
+ <interface name="zxdg_output_manager_v1" version="3">
+ <description summary="manage xdg_output objects">
+ A global factory interface for xdg_output objects.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_output_manager object">
+ Using this request a client can tell the server that it is not
+ going to use the xdg_output_manager object anymore.
+
+ Any objects already created through this instance are not affected.
+ </description>
+ </request>
+
+ <request name="get_xdg_output">
+ <description summary="create an xdg output from a wl_output">
+ This creates a new xdg_output object for the given wl_output.
+ </description>
+ <arg name="id" type="new_id" interface="zxdg_output_v1"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+ </interface>
+
+ <interface name="zxdg_output_v1" version="3">
+ <description summary="compositor logical output region">
+ An xdg_output describes part of the compositor geometry.
+
+ This typically corresponds to a monitor that displays part of the
+ compositor space.
+
+ For objects version 3 onwards, after all xdg_output properties have been
+ sent (when the object is created and when properties are updated), a
+ wl_output.done event is sent. This allows changes to the output
+ properties to be seen as atomic, even if they happen via multiple events.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_output object">
+ Using this request a client can tell the server that it is not
+ going to use the xdg_output object anymore.
+ </description>
+ </request>
+
+ <event name="logical_position">
+ <description summary="position of the output within the global compositor space">
+ The position event describes the location of the wl_output within
+ the global compositor space.
+
+ The logical_position event is sent after creating an xdg_output
+ (see xdg_output_manager.get_xdg_output) and whenever the location
+ of the output changes within the global compositor space.
+ </description>
+ <arg name="x" type="int"
+ summary="x position within the global compositor space"/>
+ <arg name="y" type="int"
+ summary="y position within the global compositor space"/>
+ </event>
+
+ <event name="logical_size">
+ <description summary="size of the output in the global compositor space">
+ The logical_size event describes the size of the output in the
+ global compositor space.
+
+ For example, a surface without any buffer scale, transformation
+ nor rotation set, with the size matching the logical_size will
+ have the same size as the corresponding output when displayed.
+
+ Most regular Wayland clients should not pay attention to the
+ logical size and would rather rely on xdg_shell interfaces.
+
+ Some clients such as Xwayland, however, need this to configure
+ their surfaces in the global compositor space as the compositor
+ may apply a different scale from what is advertised by the output
+ scaling property (to achieve fractional scaling, for example).
+
+ For example, for a wl_output mode 3840×2160 and a scale factor 2:
+
+ - A compositor not scaling the surface buffers will advertise a
+ logical size of 3840×2160,
+
+ - A compositor automatically scaling the surface buffers will
+ advertise a logical size of 1920×1080,
+
+ - A compositor using a fractional scale of 1.5 will advertise a
+ logical size to 2560×1620.
+
+ For example, for a wl_output mode 1920×1080 and a 90 degree rotation,
+ the compositor will advertise a logical size of 1080x1920.
+
+ The logical_size event is sent after creating an xdg_output
+ (see xdg_output_manager.get_xdg_output) and whenever the logical
+ size of the output changes, either as a result of a change in the
+ applied scale or because of a change in the corresponding output
+ mode(see wl_output.mode) or transform (see wl_output.transform).
+ </description>
+ <arg name="width" type="int"
+ summary="width in global compositor space"/>
+ <arg name="height" type="int"
+ summary="height in global compositor space"/>
+ </event>
+
+ <event name="done">
+ <description summary="all information about the output have been sent">
+ This event is sent after all other properties of an xdg_output
+ have been sent.
+
+ This allows changes to the xdg_output properties to be seen as
+ atomic, even if they happen via multiple events.
+
+ For objects version 3 onwards, this event is deprecated. Compositors
+ are not required to send it anymore and must send wl_output.done
+ instead.
+ </description>
+ </event>
+
+ <!-- Version 2 additions -->
+
+ <event name="name" since="2">
+ <description summary="name of this output">
+ Many compositors will assign names to their outputs, show them to the
+ user, allow them to be configured by name, etc. The client may wish to
+ know this name as well to offer the user similar behaviors.
+
+ The naming convention is compositor defined, but limited to
+ alphanumeric characters and dashes (-). Each name is unique among all
+ wl_output globals, but if a wl_output global is destroyed the same name
+ may be reused later. The names will also remain consistent across
+ sessions with the same hardware and software configuration.
+
+ Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
+ not assume that the name is a reflection of an underlying DRM
+ connector, X11 connection, etc.
+
+ The name event is sent after creating an xdg_output (see
+ xdg_output_manager.get_xdg_output). This event is only sent once per
+ xdg_output, and the name does not change over the lifetime of the
+ wl_output global.
+ </description>
+ <arg name="name" type="string" summary="output name"/>
+ </event>
+
+ <event name="description" since="2">
+ <description summary="human-readable description of this output">
+ Many compositors can produce human-readable descriptions of their
+ outputs. The client may wish to know this description as well, to
+ communicate the user for various purposes.
+
+ The description is a UTF-8 string with no convention defined for its
+ contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11
+ output via :1'.
+
+ The description event is sent after creating an xdg_output (see
+ xdg_output_manager.get_xdg_output) and whenever the description
+ changes. The description is optional, and may not be sent at all.
+
+ For objects of version 2 and lower, this event is only sent once per
+ xdg_output, and the description does not change over the lifetime of
+ the wl_output global.
+ </description>
+ <arg name="description" type="string" summary="output description"/>
+ </event>
+
+ </interface>
+</protocol>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_shell_unstable_v6">
+
+ <copyright>
+ Copyright © 2008-2013 Kristian Høgsberg
+ Copyright © 2013 Rafael Antognolli
+ Copyright © 2013 Jasper St. Pierre
+ Copyright © 2010-2013 Intel Corporation
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="zxdg_shell_v6" version="1">
+ <description summary="create desktop-style surfaces">
+ xdg_shell allows clients to turn a wl_surface into a "real window"
+ which can be dragged, resized, stacked, and moved around by the
+ user. Everything about this interface is suited towards traditional
+ desktop environments.
+ </description>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ <entry name="defunct_surfaces" value="1"
+ summary="xdg_shell was destroyed before children"/>
+ <entry name="not_the_topmost_popup" value="2"
+ summary="the client tried to map or destroy a non-topmost popup"/>
+ <entry name="invalid_popup_parent" value="3"
+ summary="the client specified an invalid popup parent surface"/>
+ <entry name="invalid_surface_state" value="4"
+ summary="the client provided an invalid surface state"/>
+ <entry name="invalid_positioner" value="5"
+ summary="the client provided an invalid positioner"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy xdg_shell">
+ Destroy this xdg_shell object.
+
+ Destroying a bound xdg_shell object while there are surfaces
+ still alive created by this xdg_shell object instance is illegal
+ and will result in a protocol error.
+ </description>
+ </request>
+
+ <request name="create_positioner">
+ <description summary="create a positioner object">
+ Create a positioner object. A positioner object is used to position
+ surfaces relative to some parent surface. See the interface description
+ and xdg_surface.get_popup for details.
+ </description>
+ <arg name="id" type="new_id" interface="zxdg_positioner_v6"/>
+ </request>
+
+ <request name="get_xdg_surface">
+ <description summary="create a shell surface from a surface">
+ This creates an xdg_surface for the given surface. While xdg_surface
+ itself is not a role, the corresponding surface may only be assigned
+ a role extending xdg_surface, such as xdg_toplevel or xdg_popup.
+
+ This creates an xdg_surface for the given surface. An xdg_surface is
+ used as basis to define a role to a given surface, such as xdg_toplevel
+ or xdg_popup. It also manages functionality shared between xdg_surface
+ based surface roles.
+
+ See the documentation of xdg_surface for more details about what an
+ xdg_surface is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="zxdg_surface_v6"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+
+ <request name="pong">
+ <description summary="respond to a ping event">
+ A client must respond to a ping event with a pong request or
+ the client may be deemed unresponsive. See xdg_shell.ping.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the ping event"/>
+ </request>
+
+ <event name="ping">
+ <description summary="check if the client is alive">
+ The ping event asks the client if it's still alive. Pass the
+ serial specified in the event back to the compositor by sending
+ a "pong" request back with the specified serial. See xdg_shell.ping.
+
+ Compositors can use this to determine if the client is still
+ alive. It's unspecified what will happen if the client doesn't
+ respond to the ping request, or in what timeframe. Clients should
+ try to respond in a reasonable amount of time.
+
+ A compositor is free to ping in any way it wants, but a client must
+ always respond to any xdg_shell object it created.
+ </description>
+ <arg name="serial" type="uint" summary="pass this to the pong request"/>
+ </event>
+ </interface>
+
+ <interface name="zxdg_positioner_v6" version="1">
+ <description summary="child surface positioner">
+ The xdg_positioner provides a collection of rules for the placement of a
+ child surface relative to a parent surface. Rules can be defined to ensure
+ the child surface remains within the visible area's borders, and to
+ specify how the child surface changes its position, such as sliding along
+ an axis, or flipping around a rectangle. These positioner-created rules are
+ constrained by the requirement that a child surface must intersect with or
+ be at least partially adjacent to its parent surface.
+
+ See the various requests for details about possible rules.
+
+ At the time of the request, the compositor makes a copy of the rules
+ specified by the xdg_positioner. Thus, after the request is complete the
+ xdg_positioner object can be destroyed or reused; further changes to the
+ object will have no effect on previous usages.
+
+ For an xdg_positioner object to be considered complete, it must have a
+ non-zero size set by set_size, and a non-zero anchor rectangle set by
+ set_anchor_rect. Passing an incomplete xdg_positioner object when
+ positioning a surface raises an error.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_input" value="0" summary="invalid input provided"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_positioner object">
+ Notify the compositor that the xdg_positioner will no longer be used.
+ </description>
+ </request>
+
+ <request name="set_size">
+ <description summary="set the size of the to-be positioned rectangle">
+ Set the size of the surface that is to be positioned with the positioner
+ object. The size is in surface-local coordinates and corresponds to the
+ window geometry. See xdg_surface.set_window_geometry.
+
+ If a zero or negative size is set the invalid_input error is raised.
+ </description>
+ <arg name="width" type="int" summary="width of positioned rectangle"/>
+ <arg name="height" type="int" summary="height of positioned rectangle"/>
+ </request>
+
+ <request name="set_anchor_rect">
+ <description summary="set the anchor rectangle within the parent surface">
+ Specify the anchor rectangle within the parent surface that the child
+ surface will be placed relative to. The rectangle is relative to the
+ window geometry as defined by xdg_surface.set_window_geometry of the
+ parent surface. The rectangle must be at least 1x1 large.
+
+ When the xdg_positioner object is used to position a child surface, the
+ anchor rectangle may not extend outside the window geometry of the
+ positioned child's parent surface.
+
+ If a zero or negative size is set the invalid_input error is raised.
+ </description>
+ <arg name="x" type="int" summary="x position of anchor rectangle"/>
+ <arg name="y" type="int" summary="y position of anchor rectangle"/>
+ <arg name="width" type="int" summary="width of anchor rectangle"/>
+ <arg name="height" type="int" summary="height of anchor rectangle"/>
+ </request>
+
+ <enum name="anchor" bitfield="true">
+ <entry name="none" value="0"
+ summary="the center of the anchor rectangle"/>
+ <entry name="top" value="1"
+ summary="the top edge of the anchor rectangle"/>
+ <entry name="bottom" value="2"
+ summary="the bottom edge of the anchor rectangle"/>
+ <entry name="left" value="4"
+ summary="the left edge of the anchor rectangle"/>
+ <entry name="right" value="8"
+ summary="the right edge of the anchor rectangle"/>
+ </enum>
+
+ <request name="set_anchor">
+ <description summary="set anchor rectangle anchor edges">
+ Defines a set of edges for the anchor rectangle. These are used to
+ derive an anchor point that the child surface will be positioned
+ relative to. If two orthogonal edges are specified (e.g. 'top' and
+ 'left'), then the anchor point will be the intersection of the edges
+ (e.g. the top left position of the rectangle); otherwise, the derived
+ anchor point will be centered on the specified edge, or in the center of
+ the anchor rectangle if no edge is specified.
+
+ If two parallel anchor edges are specified (e.g. 'left' and 'right'),
+ the invalid_input error is raised.
+ </description>
+ <arg name="anchor" type="uint" enum="anchor"
+ summary="bit mask of anchor edges"/>
+ </request>
+
+ <enum name="gravity" bitfield="true">
+ <entry name="none" value="0"
+ summary="center over the anchor edge"/>
+ <entry name="top" value="1"
+ summary="position above the anchor edge"/>
+ <entry name="bottom" value="2"
+ summary="position below the anchor edge"/>
+ <entry name="left" value="4"
+ summary="position to the left of the anchor edge"/>
+ <entry name="right" value="8"
+ summary="position to the right of the anchor edge"/>
+ </enum>
+
+ <request name="set_gravity">
+ <description summary="set child surface gravity">
+ Defines in what direction a surface should be positioned, relative to
+ the anchor point of the parent surface. If two orthogonal gravities are
+ specified (e.g. 'bottom' and 'right'), then the child surface will be
+ placed in the specified direction; otherwise, the child surface will be
+ centered over the anchor point on any axis that had no gravity
+ specified.
+
+ If two parallel gravities are specified (e.g. 'left' and 'right'), the
+ invalid_input error is raised.
+ </description>
+ <arg name="gravity" type="uint" enum="gravity"
+ summary="bit mask of gravity directions"/>
+ </request>
+
+ <enum name="constraint_adjustment" bitfield="true">
+ <description summary="constraint adjustments">
+ The constraint adjustment value define ways the compositor will adjust
+ the position of the surface, if the unadjusted position would result
+ in the surface being partly constrained.
+
+ Whether a surface is considered 'constrained' is left to the compositor
+ to determine. For example, the surface may be partly outside the
+ compositor's defined 'work area', thus necessitating the child surface's
+ position be adjusted until it is entirely inside the work area.
+
+ The adjustments can be combined, according to a defined precedence: 1)
+ Flip, 2) Slide, 3) Resize.
+ </description>
+ <entry name="none" value="0">
+ <description summary="don't move the child surface when constrained">
+ Don't alter the surface position even if it is constrained on some
+ axis, for example partially outside the edge of a monitor.
+ </description>
+ </entry>
+ <entry name="slide_x" value="1">
+ <description summary="move along the x axis until unconstrained">
+ Slide the surface along the x axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the x axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ x axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+ </description>
+ </entry>
+ <entry name="slide_y" value="2">
+ <description summary="move along the y axis until unconstrained">
+ Slide the surface along the y axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the y axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ y axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+ </description>
+ </entry>
+ <entry name="flip_x" value="4">
+ <description summary="invert the anchor and gravity on the x axis">
+ Invert the anchor and gravity on the x axis if the surface is
+ constrained on the x axis. For example, if the left edge of the
+ surface is constrained, the gravity is 'left' and the anchor is
+ 'left', change the gravity to 'right' and the anchor to 'right'.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_x adjustment will be the one before the
+ adjustment.
+ </description>
+ </entry>
+ <entry name="flip_y" value="8">
+ <description summary="invert the anchor and gravity on the y axis">
+ Invert the anchor and gravity on the y axis if the surface is
+ constrained on the y axis. For example, if the bottom edge of the
+ surface is constrained, the gravity is 'bottom' and the anchor is
+ 'bottom', change the gravity to 'top' and the anchor to 'top'.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_y adjustment will be the one before the
+ adjustment.
+ </description>
+ </entry>
+ <entry name="resize_x" value="16">
+ <description summary="horizontally resize the surface">
+ Resize the surface horizontally so that it is completely
+ unconstrained.
+ </description>
+ </entry>
+ <entry name="resize_y" value="32">
+ <description summary="vertically resize the surface">
+ Resize the surface vertically so that it is completely unconstrained.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_constraint_adjustment">
+ <description summary="set the adjustment to be done when constrained">
+ Specify how the window should be positioned if the originally intended
+ position caused the surface to be constrained, meaning at least
+ partially outside positioning boundaries set by the compositor. The
+ adjustment is set by constructing a bitmask describing the adjustment to
+ be made when the surface is constrained on that axis.
+
+ If no bit for one axis is set, the compositor will assume that the child
+ surface should not change its position on that axis when constrained.
+
+ If more than one bit for one axis is set, the order of how adjustments
+ are applied is specified in the corresponding adjustment descriptions.
+
+ The default adjustment is none.
+ </description>
+ <arg name="constraint_adjustment" type="uint"
+ summary="bit mask of constraint adjustments"/>
+ </request>
+
+ <request name="set_offset">
+ <description summary="set surface position offset">
+ Specify the surface position offset relative to the position of the
+ anchor on the anchor rectangle and the anchor on the surface. For
+ example if the anchor of the anchor rectangle is at (x, y), the surface
+ has the gravity bottom|right, and the offset is (ox, oy), the calculated
+ surface position will be (x + ox, y + oy). The offset position of the
+ surface is the one used for constraint testing. See
+ set_constraint_adjustment.
+
+ An example use case is placing a popup menu on top of a user interface
+ element, while aligning the user interface element of the parent surface
+ with some user interface element placed somewhere in the popup surface.
+ </description>
+ <arg name="x" type="int" summary="surface position x offset"/>
+ <arg name="y" type="int" summary="surface position y offset"/>
+ </request>
+ </interface>
+
+ <interface name="zxdg_surface_v6" version="1">
+ <description summary="desktop user interface surface base interface">
+ An interface that may be implemented by a wl_surface, for
+ implementations that provide a desktop-style user interface.
+
+ It provides a base set of functionality required to construct user
+ interface elements requiring management by the compositor, such as
+ toplevel windows, menus, etc. The types of functionality are split into
+ xdg_surface roles.
+
+ Creating an xdg_surface does not set the role for a wl_surface. In order
+ to map an xdg_surface, the client must create a role-specific object
+ using, e.g., get_toplevel, get_popup. The wl_surface for any given
+ xdg_surface can have at most one role, and may not be assigned any role
+ not based on xdg_surface.
+
+ A role must be assigned before any other requests are made to the
+ xdg_surface object.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_surface state to take effect.
+
+ Creating an xdg_surface from a wl_surface which has a buffer attached or
+ committed is a client error, and any attempts by a client to attach or
+ manipulate a buffer prior to the first xdg_surface.configure call must
+ also be treated as errors.
+
+ For a surface to be mapped by the compositor, the following conditions
+ must be met: (1) the client has assigned a xdg_surface based role to the
+ surface, (2) the client has set and committed the xdg_surface state and
+ the role dependent state to the surface and (3) the client has committed a
+ buffer to the surface.
+ </description>
+
+ <enum name="error">
+ <entry name="not_constructed" value="1"/>
+ <entry name="already_constructed" value="2"/>
+ <entry name="unconfigured_buffer" value="3"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_surface">
+ Destroy the xdg_surface object. An xdg_surface must only be destroyed
+ after its role object has been destroyed.
+ </description>
+ </request>
+
+ <request name="get_toplevel">
+ <description summary="assign the xdg_toplevel surface role">
+ This creates an xdg_toplevel object for the given xdg_surface and gives
+ the associated wl_surface the xdg_toplevel role.
+
+ See the documentation of xdg_toplevel for more details about what an
+ xdg_toplevel is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="zxdg_toplevel_v6"/>
+ </request>
+
+ <request name="get_popup">
+ <description summary="assign the xdg_popup surface role">
+ This creates an xdg_popup object for the given xdg_surface and gives the
+ associated wl_surface the xdg_popup role.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="zxdg_popup_v6"/>
+ <arg name="parent" type="object" interface="zxdg_surface_v6"/>
+ <arg name="positioner" type="object" interface="zxdg_positioner_v6"/>
+ </request>
+
+ <request name="set_window_geometry">
+ <description summary="set the new window geometry">
+ The window geometry of a surface is its "visible bounds" from the
+ user's perspective. Client-side decorations often have invisible
+ portions like drop-shadows which should be ignored for the
+ purposes of aligning, placing and constraining windows.
+
+ The window geometry is double buffered, and will be applied at the
+ time wl_surface.commit of the corresponding wl_surface is called.
+
+ Once the window geometry of the surface is set, it is not possible to
+ unset it, and it will remain the same until set_window_geometry is
+ called again, even if a new subsurface or buffer is attached.
+
+ If never set, the value is the full bounds of the surface,
+ including any subsurfaces. This updates dynamically on every
+ commit. This unset is meant for extremely simple clients.
+
+ The arguments are given in the surface-local coordinate space of
+ the wl_surface associated with this xdg_surface.
+
+ The width and height must be greater than zero. Setting an invalid size
+ will raise an error. When applied, the effective window geometry will be
+ the set window geometry clamped to the bounding rectangle of the
+ combined geometry of the surface of the xdg_surface and the associated
+ subsurfaces.
+ </description>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ For instance, for toplevel surfaces the compositor might use this
+ information to move a surface to the top left only when the client has
+ drawn itself for the maximized or fullscreen state.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the configure event"/>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event marks the end of a configure sequence. A configure
+ sequence is a set of one or more events configuring the state of the
+ xdg_surface, including the final xdg_surface.configure event.
+
+ Where applicable, xdg_surface surface roles will during a configure
+ sequence extend this event as a latched state sent as events before the
+ xdg_surface.configure event. Such events should be considered to make up
+ a set of atomically applied configuration states, where the
+ xdg_surface.configure commits the accumulated state.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ If the client receives multiple configure events before it can respond
+ to one, it is free to discard all but the last event it received.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the configure event"/>
+ </event>
+ </interface>
+
+ <interface name="zxdg_toplevel_v6" version="1">
+ <description summary="toplevel surface">
+ This interface defines an xdg_surface role which allows a surface to,
+ among other things, set window-like properties such as maximize,
+ fullscreen, and minimize, set application-specific metadata like title and
+ id, and well as trigger user interactive operations such as interactive
+ resize and move.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_toplevel">
+ Unmap and destroy the window. The window will be effectively
+ hidden from the user's point of view, and all state like
+ maximization, fullscreen, and so on, will be lost.
+ </description>
+ </request>
+
+ <request name="set_parent">
+ <description summary="set the parent of this surface">
+ Set the "parent" of this surface. This window should be stacked
+ above a parent. The parent surface must be mapped as long as this
+ surface is mapped.
+
+ Parent windows should be set on dialogs, toolboxes, or other
+ "auxiliary" surfaces, so that the parent is raised when the dialog
+ is raised.
+ </description>
+ <arg name="parent" type="object" interface="zxdg_toplevel_v6" allow-null="true"/>
+ </request>
+
+ <request name="set_title">
+ <description summary="set surface title">
+ Set a short title for the surface.
+
+ This string may be used to identify the surface in a task bar,
+ window list, or other user interface elements provided by the
+ compositor.
+
+ The string must be encoded in UTF-8.
+ </description>
+ <arg name="title" type="string"/>
+ </request>
+
+ <request name="set_app_id">
+ <description summary="set application ID">
+ Set an application identifier for the surface.
+
+ The app ID identifies the general class of applications to which
+ the surface belongs. The compositor can use this to group multiple
+ surfaces together, or to determine how to launch a new application.
+
+ For D-Bus activatable applications, the app ID is used as the D-Bus
+ service name.
+
+ The compositor shell will try to group application surfaces together
+ by their app ID. As a best practice, it is suggested to select app
+ ID's that match the basename of the application's .desktop file.
+ For example, "org.freedesktop.FooViewer" where the .desktop file is
+ "org.freedesktop.FooViewer.desktop".
+
+ See the desktop-entry specification [0] for more details on
+ application identifiers and how they relate to well-known D-Bus
+ names and .desktop files.
+
+ [0] http://standards.freedesktop.org/desktop-entry-spec/
+ </description>
+ <arg name="app_id" type="string"/>
+ </request>
+
+ <request name="show_window_menu">
+ <description summary="show the window menu">
+ Clients implementing client-side decorations might want to show
+ a context menu when right-clicking on the decorations, giving the
+ user a menu that they can use to maximize or minimize the window.
+
+ This request asks the compositor to pop up such a window menu at
+ the given position, relative to the local surface coordinates of
+ the parent surface. There are no guarantees as to what menu items
+ the window menu contains.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ <arg name="x" type="int" summary="the x position to pop up the window menu at"/>
+ <arg name="y" type="int" summary="the y position to pop up the window menu at"/>
+ </request>
+
+ <request name="move">
+ <description summary="start an interactive move">
+ Start an interactive, user-driven move of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive move (touch,
+ pointer, etc).
+
+ The server may ignore move requests depending on the state of
+ the surface (e.g. fullscreen or maximized), or if the passed serial
+ is no longer valid.
+
+ If triggered, the surface will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the move. It is up to the
+ compositor to visually indicate that the move is taking place, such as
+ updating a pointer cursor, during the move. There is no guarantee
+ that the device focus will return when the move is completed.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ </request>
+
+ <enum name="resize_edge">
+ <description summary="edge values for resizing">
+ These values are used to indicate which edge of a surface
+ is being dragged in a resize operation.
+ </description>
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="right" value="8"/>
+ <entry name="top_right" value="9"/>
+ <entry name="bottom_right" value="10"/>
+ </enum>
+
+ <request name="resize">
+ <description summary="start an interactive resize">
+ Start a user-driven, interactive resize of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive resize (touch,
+ pointer, etc).
+
+ The server may ignore resize requests depending on the state of
+ the surface (e.g. fullscreen or maximized).
+
+ If triggered, the client will receive configure events with the
+ "resize" state enum value and the expected sizes. See the "resize"
+ enum value for more details about what is required. The client
+ must also acknowledge configure events using "ack_configure". After
+ the resize is completed, the client will receive another "configure"
+ event without the resize state.
+
+ If triggered, the surface also will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the resize. It is up to the
+ compositor to visually indicate that the resize is taking place,
+ such as updating a pointer cursor, during the resize. There is no
+ guarantee that the device focus will return when the resize is
+ completed.
+
+ The edges parameter specifies how the surface should be resized,
+ and is one of the values of the resize_edge enum. The compositor
+ may use this information to update the surface position for
+ example when dragging the top left corner. The compositor may also
+ use this information to adapt its behavior, e.g. choose an
+ appropriate cursor image.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ <arg name="edges" type="uint" summary="which edge or corner is being dragged"/>
+ </request>
+
+ <enum name="state">
+ <description summary="types of state on the surface">
+ The different state values used on the surface. This is designed for
+ state values like maximized, fullscreen. It is paired with the
+ configure event to ensure that both the client and the compositor
+ setting the state can be synchronized.
+
+ States set in this way are double-buffered. They will get applied on
+ the next commit.
+ </description>
+ <entry name="maximized" value="1" summary="the surface is maximized">
+ <description summary="the surface is maximized">
+ The surface is maximized. The window geometry specified in the configure
+ event must be obeyed by the client.
+ </description>
+ </entry>
+ <entry name="fullscreen" value="2" summary="the surface is fullscreen">
+ <description summary="the surface is fullscreen">
+ The surface is fullscreen. The window geometry specified in the configure
+ event must be obeyed by the client.
+ </description>
+ </entry>
+ <entry name="resizing" value="3" summary="the surface is being resized">
+ <description summary="the surface is being resized">
+ The surface is being resized. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it.
+ Clients that have aspect ratio or cell sizing configuration can use
+ a smaller size, however.
+ </description>
+ </entry>
+ <entry name="activated" value="4" summary="the surface is now activated">
+ <description summary="the surface is now activated">
+ Client window decorations should be painted as if the window is
+ active. Do not assume this means that the window actually has
+ keyboard or pointer focus.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_max_size">
+ <description summary="set the maximum size">
+ Set a maximum size for the window.
+
+ The client can specify a maximum size so that the compositor does
+ not try to configure the window beyond this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered. They will get applied
+ on the next commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the maximum
+ size. The compositor may decide to ignore the values set by the
+ client and request a larger size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected maximum size in the given dimension.
+ As a result, a client wishing to reset the maximum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a maximum size to be smaller than the minimum size of
+ a surface is illegal and will result in a protocol error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width and height will result in a
+ protocol error.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="set_min_size">
+ <description summary="set the minimum size">
+ Set a minimum size for the window.
+
+ The client can specify a minimum size so that the compositor does
+ not try to configure the window below this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered. They will get applied
+ on the next commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the minimum
+ size. The compositor may decide to ignore the values set by the
+ client and request a smaller size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected minimum size in the given dimension.
+ As a result, a client wishing to reset the minimum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a minimum size to be larger than the maximum size of
+ a surface is illegal and will result in a protocol error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width and height will result in a
+ protocol error.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="set_maximized">
+ <description summary="maximize the window">
+ Maximize the surface.
+
+ After requesting that the surface should be maximized, the compositor
+ will respond by emitting a configure event with the "maximized" state
+ and the required window geometry. The client should then update its
+ content, drawing it in a maximized state, i.e. without shadow or other
+ decoration outside of the window geometry. The client must also
+ acknowledge the configure when committing the new content (see
+ ack_configure).
+
+ It is up to the compositor to decide how and where to maximize the
+ surface, for example which output and what region of the screen should
+ be used.
+
+ If the surface was already maximized, the compositor will still emit
+ a configure event with the "maximized" state.
+ </description>
+ </request>
+
+ <request name="unset_maximized">
+ <description summary="unmaximize the window">
+ Unmaximize the surface.
+
+ After requesting that the surface should be unmaximized, the compositor
+ will respond by emitting a configure event without the "maximized"
+ state. If available, the compositor will include the window geometry
+ dimensions the window had prior to being maximized in the configure
+ request. The client must then update its content, drawing it in a
+ regular state, i.e. potentially with shadow, etc. The client must also
+ acknowledge the configure when committing the new content (see
+ ack_configure).
+
+ It is up to the compositor to position the surface after it was
+ unmaximized; usually the position the surface had before maximizing, if
+ applicable.
+
+ If the surface was already not maximized, the compositor will still
+ emit a configure event without the "maximized" state.
+ </description>
+ </request>
+
+ <request name="set_fullscreen">
+ <description summary="set the window as fullscreen on a monitor">
+ Make the surface fullscreen.
+
+ You can specify an output that you would prefer to be fullscreen.
+ If this value is NULL, it's up to the compositor to choose which
+ display will be used to map this surface.
+
+ If the surface doesn't cover the whole output, the compositor will
+ position the surface in the center of the output and compensate with
+ black borders filling the rest of the output.
+ </description>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ </request>
+ <request name="unset_fullscreen" />
+
+ <request name="set_minimized">
+ <description summary="set the window as minimized">
+ Request that the compositor minimize your surface. There is no
+ way to know if the surface is currently minimized, nor is there
+ any way to unset minimization on this surface.
+
+ If you are looking to throttle redrawing when minimized, please
+ instead use the wl_surface.frame event for this, as this will
+ also work with live previews on windows in Alt-Tab, Expose or
+ similar compositor features.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ This configure event asks the client to resize its toplevel surface or
+ to change its state. The configured state should not be applied
+ immediately. See xdg_surface.configure for details.
+
+ The width and height arguments specify a hint to the window
+ about how its surface should be resized in window geometry
+ coordinates. See set_window_geometry.
+
+ If the width or height arguments are zero, it means the client
+ should decide its own window dimension. This may happen when the
+ compositor needs to configure the state of the surface but doesn't
+ have any information about any previous or expected dimension.
+
+ The states listed in the event specify how the width/height
+ arguments should be interpreted, and possibly how it should be
+ drawn.
+
+ Clients must send an ack_configure in response to this event. See
+ xdg_surface.configure and xdg_surface.ack_configure for details.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ <arg name="states" type="array"/>
+ </event>
+
+ <event name="close">
+ <description summary="surface wants to be closed">
+ The close event is sent by the compositor when the user
+ wants the surface to be closed. This should be equivalent to
+ the user clicking the close button in client-side decorations,
+ if your application has any.
+
+ This is only a request that the user intends to close the
+ window. The client may choose to ignore this request, or show
+ a dialog to ask the user to save their data, etc.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zxdg_popup_v6" version="1">
+ <description summary="short-lived, popup surfaces for menus">
+ A popup surface is a short-lived, temporary surface. It can be used to
+ implement for example menus, popovers, tooltips and other similar user
+ interface concepts.
+
+ A popup can be made to take an explicit grab. See xdg_popup.grab for
+ details.
+
+ When the popup is dismissed, a popup_done event will be sent out, and at
+ the same time the surface will be unmapped. See the xdg_popup.popup_done
+ event for details.
+
+ Explicitly destroying the xdg_popup object will also dismiss the popup and
+ unmap the surface. Clients that want to dismiss the popup when another
+ surface of their own is clicked should dismiss the popup using the destroy
+ request.
+
+ The parent surface must have either the xdg_toplevel or xdg_popup surface
+ role.
+
+ A newly created xdg_popup will be stacked on top of all previously created
+ xdg_popup surfaces associated with the same xdg_toplevel.
+
+ The parent of an xdg_popup must be mapped (see the xdg_surface
+ description) before the xdg_popup itself.
+
+ The x and y arguments passed when creating the popup object specify
+ where the top left of the popup should be placed, relative to the
+ local surface coordinates of the parent surface. See
+ xdg_surface.get_popup. An xdg_popup must intersect with or be at least
+ partially adjacent to its parent surface.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_popup state to take effect.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_grab" value="0"
+ summary="tried to grab after being mapped"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove xdg_popup interface">
+ This destroys the popup. Explicitly destroying the xdg_popup
+ object will also dismiss the popup, and unmap the surface.
+
+ If this xdg_popup is not the "topmost" popup, a protocol error
+ will be sent.
+ </description>
+ </request>
+
+ <request name="grab">
+ <description summary="make the popup take an explicit grab">
+ This request makes the created popup take an explicit grab. An explicit
+ grab will be dismissed when the user dismisses the popup, or when the
+ client destroys the xdg_popup. This can be done by the user clicking
+ outside the surface, using the keyboard, or even locking the screen
+ through closing the lid or a timeout.
+
+ If the compositor denies the grab, the popup will be immediately
+ dismissed.
+
+ This request must be used in response to some sort of user action like a
+ button press, key press, or touch down event. The serial number of the
+ event should be passed as 'serial'.
+
+ The parent of a grabbing popup must either be an xdg_toplevel surface or
+ another xdg_popup with an explicit grab. If the parent is another
+ xdg_popup it means that the popups are nested, with this popup now being
+ the topmost popup.
+
+ Nested popups must be destroyed in the reverse order they were created
+ in, e.g. the only popup you are allowed to destroy at all times is the
+ topmost one.
+
+ When compositors choose to dismiss a popup, they may dismiss every
+ nested grabbing popup as well. When a compositor dismisses popups, it
+ will follow the same dismissing order as required from the client.
+
+ The parent of a grabbing popup must either be another xdg_popup with an
+ active explicit grab, or an xdg_popup or xdg_toplevel, if there are no
+ explicit grabs already taken.
+
+ If the topmost grabbing popup is destroyed, the grab will be returned to
+ the parent of the popup, if that parent previously had an explicit grab.
+
+ If the parent is a grabbing popup which has already been dismissed, this
+ popup will be immediately dismissed. If the parent is a popup that did
+ not take an explicit grab, an error will be raised.
+
+ During a popup grab, the client owning the grab will receive pointer
+ and touch events for all their surfaces as normal (similar to an
+ "owner-events" grab in X11 parlance), while the top most grabbing popup
+ will always have keyboard focus.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"
+ summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ </request>
+
+ <event name="configure">
+ <description summary="configure the popup surface">
+ This event asks the popup surface to configure itself given the
+ configuration. The configured state should not be applied immediately.
+ See xdg_surface.configure for details.
+
+ The x and y arguments represent the position the popup was placed at
+ given the xdg_positioner rule, relative to the upper left corner of the
+ window geometry of the parent surface.
+ </description>
+ <arg name="x" type="int"
+ summary="x position relative to parent surface window geometry"/>
+ <arg name="y" type="int"
+ summary="y position relative to parent surface window geometry"/>
+ <arg name="width" type="int" summary="window geometry width"/>
+ <arg name="height" type="int" summary="window geometry height"/>
+ </event>
+
+ <event name="popup_done">
+ <description summary="popup interaction is done">
+ The popup_done event is sent out when a popup is dismissed by the
+ compositor. The client should destroy the xdg_popup object at this
+ point.
+ </description>
+ </event>
+
+ </interface>
+</protocol>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_shell">
+
+ <copyright>
+ Copyright © 2008-2013 Kristian Høgsberg
+ Copyright © 2013 Rafael Antognolli
+ Copyright © 2013 Jasper St. Pierre
+ Copyright © 2010-2013 Intel Corporation
+ Copyright © 2015-2017 Samsung Electronics Co., Ltd
+ Copyright © 2015-2017 Red Hat Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="xdg_wm_base" version="1">
+ <description summary="create desktop-style surfaces">
+ The xdg_wm_base interface is exposed as a global object enabling clients
+ to turn their wl_surfaces into windows in a desktop environment. It
+ defines the basic functionality needed for clients and the compositor to
+ create windows that can be dragged, resized, maximized, etc, as well as
+ creating transient windows such as popup menus.
+ </description>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ <entry name="defunct_surfaces" value="1"
+ summary="xdg_wm_base was destroyed before children"/>
+ <entry name="not_the_topmost_popup" value="2"
+ summary="the client tried to map or destroy a non-topmost popup"/>
+ <entry name="invalid_popup_parent" value="3"
+ summary="the client specified an invalid popup parent surface"/>
+ <entry name="invalid_surface_state" value="4"
+ summary="the client provided an invalid surface state"/>
+ <entry name="invalid_positioner" value="5"
+ summary="the client provided an invalid positioner"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy xdg_wm_base">
+ Destroy this xdg_wm_base object.
+
+ Destroying a bound xdg_wm_base object while there are surfaces
+ still alive created by this xdg_wm_base object instance is illegal
+ and will result in a protocol error.
+ </description>
+ </request>
+
+ <request name="create_positioner">
+ <description summary="create a positioner object">
+ Create a positioner object. A positioner object is used to position
+ surfaces relative to some parent surface. See the interface description
+ and xdg_surface.get_popup for details.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_positioner"/>
+ </request>
+
+ <request name="get_xdg_surface">
+ <description summary="create a shell surface from a surface">
+ This creates an xdg_surface for the given surface. While xdg_surface
+ itself is not a role, the corresponding surface may only be assigned
+ a role extending xdg_surface, such as xdg_toplevel or xdg_popup.
+
+ This creates an xdg_surface for the given surface. An xdg_surface is
+ used as basis to define a role to a given surface, such as xdg_toplevel
+ or xdg_popup. It also manages functionality shared between xdg_surface
+ based surface roles.
+
+ See the documentation of xdg_surface for more details about what an
+ xdg_surface is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_surface"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+
+ <request name="pong">
+ <description summary="respond to a ping event">
+ A client must respond to a ping event with a pong request or
+ the client may be deemed unresponsive. See xdg_wm_base.ping.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the ping event"/>
+ </request>
+
+ <event name="ping">
+ <description summary="check if the client is alive">
+ The ping event asks the client if it's still alive. Pass the
+ serial specified in the event back to the compositor by sending
+ a "pong" request back with the specified serial. See xdg_wm_base.ping.
+
+ Compositors can use this to determine if the client is still
+ alive. It's unspecified what will happen if the client doesn't
+ respond to the ping request, or in what timeframe. Clients should
+ try to respond in a reasonable amount of time.
+
+ A compositor is free to ping in any way it wants, but a client must
+ always respond to any xdg_wm_base object it created.
+ </description>
+ <arg name="serial" type="uint" summary="pass this to the pong request"/>
+ </event>
+ </interface>
+
+ <interface name="xdg_positioner" version="1">
+ <description summary="child surface positioner">
+ The xdg_positioner provides a collection of rules for the placement of a
+ child surface relative to a parent surface. Rules can be defined to ensure
+ the child surface remains within the visible area's borders, and to
+ specify how the child surface changes its position, such as sliding along
+ an axis, or flipping around a rectangle. These positioner-created rules are
+ constrained by the requirement that a child surface must intersect with or
+ be at least partially adjacent to its parent surface.
+
+ See the various requests for details about possible rules.
+
+ At the time of the request, the compositor makes a copy of the rules
+ specified by the xdg_positioner. Thus, after the request is complete the
+ xdg_positioner object can be destroyed or reused; further changes to the
+ object will have no effect on previous usages.
+
+ For an xdg_positioner object to be considered complete, it must have a
+ non-zero size set by set_size, and a non-zero anchor rectangle set by
+ set_anchor_rect. Passing an incomplete xdg_positioner object when
+ positioning a surface raises an error.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_input" value="0" summary="invalid input provided"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_positioner object">
+ Notify the compositor that the xdg_positioner will no longer be used.
+ </description>
+ </request>
+
+ <request name="set_size">
+ <description summary="set the size of the to-be positioned rectangle">
+ Set the size of the surface that is to be positioned with the positioner
+ object. The size is in surface-local coordinates and corresponds to the
+ window geometry. See xdg_surface.set_window_geometry.
+
+ If a zero or negative size is set the invalid_input error is raised.
+ </description>
+ <arg name="width" type="int" summary="width of positioned rectangle"/>
+ <arg name="height" type="int" summary="height of positioned rectangle"/>
+ </request>
+
+ <request name="set_anchor_rect">
+ <description summary="set the anchor rectangle within the parent surface">
+ Specify the anchor rectangle within the parent surface that the child
+ surface will be placed relative to. The rectangle is relative to the
+ window geometry as defined by xdg_surface.set_window_geometry of the
+ parent surface.
+
+ When the xdg_positioner object is used to position a child surface, the
+ anchor rectangle may not extend outside the window geometry of the
+ positioned child's parent surface.
+
+ If a negative size is set the invalid_input error is raised.
+ </description>
+ <arg name="x" type="int" summary="x position of anchor rectangle"/>
+ <arg name="y" type="int" summary="y position of anchor rectangle"/>
+ <arg name="width" type="int" summary="width of anchor rectangle"/>
+ <arg name="height" type="int" summary="height of anchor rectangle"/>
+ </request>
+
+ <enum name="anchor">
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="3"/>
+ <entry name="right" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="top_right" value="7"/>
+ <entry name="bottom_right" value="8"/>
+ </enum>
+
+ <request name="set_anchor">
+ <description summary="set anchor rectangle anchor">
+ Defines the anchor point for the anchor rectangle. The specified anchor
+ is used derive an anchor point that the child surface will be
+ positioned relative to. If a corner anchor is set (e.g. 'top_left' or
+ 'bottom_right'), the anchor point will be at the specified corner;
+ otherwise, the derived anchor point will be centered on the specified
+ edge, or in the center of the anchor rectangle if no edge is specified.
+ </description>
+ <arg name="anchor" type="uint" enum="anchor"
+ summary="anchor"/>
+ </request>
+
+ <enum name="gravity">
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="3"/>
+ <entry name="right" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="top_right" value="7"/>
+ <entry name="bottom_right" value="8"/>
+ </enum>
+
+ <request name="set_gravity">
+ <description summary="set child surface gravity">
+ Defines in what direction a surface should be positioned, relative to
+ the anchor point of the parent surface. If a corner gravity is
+ specified (e.g. 'bottom_right' or 'top_left'), then the child surface
+ will be placed towards the specified gravity; otherwise, the child
+ surface will be centered over the anchor point on any axis that had no
+ gravity specified.
+ </description>
+ <arg name="gravity" type="uint" enum="gravity"
+ summary="gravity direction"/>
+ </request>
+
+ <enum name="constraint_adjustment" bitfield="true">
+ <description summary="constraint adjustments">
+ The constraint adjustment value define ways the compositor will adjust
+ the position of the surface, if the unadjusted position would result
+ in the surface being partly constrained.
+
+ Whether a surface is considered 'constrained' is left to the compositor
+ to determine. For example, the surface may be partly outside the
+ compositor's defined 'work area', thus necessitating the child surface's
+ position be adjusted until it is entirely inside the work area.
+
+ The adjustments can be combined, according to a defined precedence: 1)
+ Flip, 2) Slide, 3) Resize.
+ </description>
+ <entry name="none" value="0">
+ <description summary="don't move the child surface when constrained">
+ Don't alter the surface position even if it is constrained on some
+ axis, for example partially outside the edge of an output.
+ </description>
+ </entry>
+ <entry name="slide_x" value="1">
+ <description summary="move along the x axis until unconstrained">
+ Slide the surface along the x axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the x axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ x axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+ </description>
+ </entry>
+ <entry name="slide_y" value="2">
+ <description summary="move along the y axis until unconstrained">
+ Slide the surface along the y axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the y axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ y axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+ </description>
+ </entry>
+ <entry name="flip_x" value="4">
+ <description summary="invert the anchor and gravity on the x axis">
+ Invert the anchor and gravity on the x axis if the surface is
+ constrained on the x axis. For example, if the left edge of the
+ surface is constrained, the gravity is 'left' and the anchor is
+ 'left', change the gravity to 'right' and the anchor to 'right'.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_x adjustment will be the one before the
+ adjustment.
+ </description>
+ </entry>
+ <entry name="flip_y" value="8">
+ <description summary="invert the anchor and gravity on the y axis">
+ Invert the anchor and gravity on the y axis if the surface is
+ constrained on the y axis. For example, if the bottom edge of the
+ surface is constrained, the gravity is 'bottom' and the anchor is
+ 'bottom', change the gravity to 'top' and the anchor to 'top'.
+
+ The adjusted position is calculated given the original anchor
+ rectangle and offset, but with the new flipped anchor and gravity
+ values.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_y adjustment will be the one before the
+ adjustment.
+ </description>
+ </entry>
+ <entry name="resize_x" value="16">
+ <description summary="horizontally resize the surface">
+ Resize the surface horizontally so that it is completely
+ unconstrained.
+ </description>
+ </entry>
+ <entry name="resize_y" value="32">
+ <description summary="vertically resize the surface">
+ Resize the surface vertically so that it is completely unconstrained.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_constraint_adjustment">
+ <description summary="set the adjustment to be done when constrained">
+ Specify how the window should be positioned if the originally intended
+ position caused the surface to be constrained, meaning at least
+ partially outside positioning boundaries set by the compositor. The
+ adjustment is set by constructing a bitmask describing the adjustment to
+ be made when the surface is constrained on that axis.
+
+ If no bit for one axis is set, the compositor will assume that the child
+ surface should not change its position on that axis when constrained.
+
+ If more than one bit for one axis is set, the order of how adjustments
+ are applied is specified in the corresponding adjustment descriptions.
+
+ The default adjustment is none.
+ </description>
+ <arg name="constraint_adjustment" type="uint"
+ summary="bit mask of constraint adjustments"/>
+ </request>
+
+ <request name="set_offset">
+ <description summary="set surface position offset">
+ Specify the surface position offset relative to the position of the
+ anchor on the anchor rectangle and the anchor on the surface. For
+ example if the anchor of the anchor rectangle is at (x, y), the surface
+ has the gravity bottom|right, and the offset is (ox, oy), the calculated
+ surface position will be (x + ox, y + oy). The offset position of the
+ surface is the one used for constraint testing. See
+ set_constraint_adjustment.
+
+ An example use case is placing a popup menu on top of a user interface
+ element, while aligning the user interface element of the parent surface
+ with some user interface element placed somewhere in the popup surface.
+ </description>
+ <arg name="x" type="int" summary="surface position x offset"/>
+ <arg name="y" type="int" summary="surface position y offset"/>
+ </request>
+ </interface>
+
+ <interface name="xdg_surface" version="1">
+ <description summary="desktop user interface surface base interface">
+ An interface that may be implemented by a wl_surface, for
+ implementations that provide a desktop-style user interface.
+
+ It provides a base set of functionality required to construct user
+ interface elements requiring management by the compositor, such as
+ toplevel windows, menus, etc. The types of functionality are split into
+ xdg_surface roles.
+
+ Creating an xdg_surface does not set the role for a wl_surface. In order
+ to map an xdg_surface, the client must create a role-specific object
+ using, e.g., get_toplevel, get_popup. The wl_surface for any given
+ xdg_surface can have at most one role, and may not be assigned any role
+ not based on xdg_surface.
+
+ A role must be assigned before any other requests are made to the
+ xdg_surface object.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_surface state to take effect.
+
+ Creating an xdg_surface from a wl_surface which has a buffer attached or
+ committed is a client error, and any attempts by a client to attach or
+ manipulate a buffer prior to the first xdg_surface.configure call must
+ also be treated as errors.
+
+ Mapping an xdg_surface-based role surface is defined as making it
+ possible for the surface to be shown by the compositor. Note that
+ a mapped surface is not guaranteed to be visible once it is mapped.
+
+ For an xdg_surface to be mapped by the compositor, the following
+ conditions must be met:
+ (1) the client has assigned an xdg_surface-based role to the surface
+ (2) the client has set and committed the xdg_surface state and the
+ role-dependent state to the surface
+ (3) the client has committed a buffer to the surface
+
+ A newly-unmapped surface is considered to have met condition (1) out
+ of the 3 required conditions for mapping a surface if its role surface
+ has not been destroyed.
+ </description>
+
+ <enum name="error">
+ <entry name="not_constructed" value="1"/>
+ <entry name="already_constructed" value="2"/>
+ <entry name="unconfigured_buffer" value="3"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_surface">
+ Destroy the xdg_surface object. An xdg_surface must only be destroyed
+ after its role object has been destroyed.
+ </description>
+ </request>
+
+ <request name="get_toplevel">
+ <description summary="assign the xdg_toplevel surface role">
+ This creates an xdg_toplevel object for the given xdg_surface and gives
+ the associated wl_surface the xdg_toplevel role.
+
+ See the documentation of xdg_toplevel for more details about what an
+ xdg_toplevel is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_toplevel"/>
+ </request>
+
+ <request name="get_popup">
+ <description summary="assign the xdg_popup surface role">
+ This creates an xdg_popup object for the given xdg_surface and gives
+ the associated wl_surface the xdg_popup role.
+
+ If null is passed as a parent, a parent surface must be specified using
+ some other protocol, before committing the initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_popup"/>
+ <arg name="parent" type="object" interface="xdg_surface" allow-null="true"/>
+ <arg name="positioner" type="object" interface="xdg_positioner"/>
+ </request>
+
+ <request name="set_window_geometry">
+ <description summary="set the new window geometry">
+ The window geometry of a surface is its "visible bounds" from the
+ user's perspective. Client-side decorations often have invisible
+ portions like drop-shadows which should be ignored for the
+ purposes of aligning, placing and constraining windows.
+
+ The window geometry is double buffered, and will be applied at the
+ time wl_surface.commit of the corresponding wl_surface is called.
+
+ When maintaining a position, the compositor should treat the (x, y)
+ coordinate of the window geometry as the top left corner of the window.
+ A client changing the (x, y) window geometry coordinate should in
+ general not alter the position of the window.
+
+ Once the window geometry of the surface is set, it is not possible to
+ unset it, and it will remain the same until set_window_geometry is
+ called again, even if a new subsurface or buffer is attached.
+
+ If never set, the value is the full bounds of the surface,
+ including any subsurfaces. This updates dynamically on every
+ commit. This unset is meant for extremely simple clients.
+
+ The arguments are given in the surface-local coordinate space of
+ the wl_surface associated with this xdg_surface.
+
+ The width and height must be greater than zero. Setting an invalid size
+ will raise an error. When applied, the effective window geometry will be
+ the set window geometry clamped to the bounding rectangle of the
+ combined geometry of the surface of the xdg_surface and the associated
+ subsurfaces.
+ </description>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ For instance, for toplevel surfaces the compositor might use this
+ information to move a surface to the top left only when the client has
+ drawn itself for the maximized or fullscreen state.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the configure event"/>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event marks the end of a configure sequence. A configure
+ sequence is a set of one or more events configuring the state of the
+ xdg_surface, including the final xdg_surface.configure event.
+
+ Where applicable, xdg_surface surface roles will during a configure
+ sequence extend this event as a latched state sent as events before the
+ xdg_surface.configure event. Such events should be considered to make up
+ a set of atomically applied configuration states, where the
+ xdg_surface.configure commits the accumulated state.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ If the client receives multiple configure events before it can respond
+ to one, it is free to discard all but the last event it received.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the configure event"/>
+ </event>
+ </interface>
+
+ <interface name="xdg_toplevel" version="1">
+ <description summary="toplevel surface">
+ This interface defines an xdg_surface role which allows a surface to,
+ among other things, set window-like properties such as maximize,
+ fullscreen, and minimize, set application-specific metadata like title and
+ id, and well as trigger user interactive operations such as interactive
+ resize and move.
+
+ Unmapping an xdg_toplevel means that the surface cannot be shown
+ by the compositor until it is explicitly mapped again.
+ All active operations (e.g., move, resize) are canceled and all
+ attributes (e.g. title, state, stacking, ...) are discarded for
+ an xdg_toplevel surface when it is unmapped.
+
+ Attaching a null buffer to a toplevel unmaps the surface.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_toplevel">
+ This request destroys the role surface and unmaps the surface;
+ see "Unmapping" behavior in interface section for details.
+ </description>
+ </request>
+
+ <request name="set_parent">
+ <description summary="set the parent of this surface">
+ Set the "parent" of this surface. This surface should be stacked
+ above the parent surface and all other ancestor surfaces.
+
+ Parent windows should be set on dialogs, toolboxes, or other
+ "auxiliary" surfaces, so that the parent is raised when the dialog
+ is raised.
+
+ Setting a null parent for a child window removes any parent-child
+ relationship for the child. Setting a null parent for a window which
+ currently has no parent is a no-op.
+
+ If the parent is unmapped then its children are managed as
+ though the parent of the now-unmapped parent has become the
+ parent of this surface. If no parent exists for the now-unmapped
+ parent then the children are managed as though they have no
+ parent surface.
+ </description>
+ <arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/>
+ </request>
+
+ <request name="set_title">
+ <description summary="set surface title">
+ Set a short title for the surface.
+
+ This string may be used to identify the surface in a task bar,
+ window list, or other user interface elements provided by the
+ compositor.
+
+ The string must be encoded in UTF-8.
+ </description>
+ <arg name="title" type="string"/>
+ </request>
+
+ <request name="set_app_id">
+ <description summary="set application ID">
+ Set an application identifier for the surface.
+
+ The app ID identifies the general class of applications to which
+ the surface belongs. The compositor can use this to group multiple
+ surfaces together, or to determine how to launch a new application.
+
+ For D-Bus activatable applications, the app ID is used as the D-Bus
+ service name.
+
+ The compositor shell will try to group application surfaces together
+ by their app ID. As a best practice, it is suggested to select app
+ ID's that match the basename of the application's .desktop file.
+ For example, "org.freedesktop.FooViewer" where the .desktop file is
+ "org.freedesktop.FooViewer.desktop".
+
+ See the desktop-entry specification [0] for more details on
+ application identifiers and how they relate to well-known D-Bus
+ names and .desktop files.
+
+ [0] http://standards.freedesktop.org/desktop-entry-spec/
+ </description>
+ <arg name="app_id" type="string"/>
+ </request>
+
+ <request name="show_window_menu">
+ <description summary="show the window menu">
+ Clients implementing client-side decorations might want to show
+ a context menu when right-clicking on the decorations, giving the
+ user a menu that they can use to maximize or minimize the window.
+
+ This request asks the compositor to pop up such a window menu at
+ the given position, relative to the local surface coordinates of
+ the parent surface. There are no guarantees as to what menu items
+ the window menu contains.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ <arg name="x" type="int" summary="the x position to pop up the window menu at"/>
+ <arg name="y" type="int" summary="the y position to pop up the window menu at"/>
+ </request>
+
+ <request name="move">
+ <description summary="start an interactive move">
+ Start an interactive, user-driven move of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive move (touch,
+ pointer, etc).
+
+ The server may ignore move requests depending on the state of
+ the surface (e.g. fullscreen or maximized), or if the passed serial
+ is no longer valid.
+
+ If triggered, the surface will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the move. It is up to the
+ compositor to visually indicate that the move is taking place, such as
+ updating a pointer cursor, during the move. There is no guarantee
+ that the device focus will return when the move is completed.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ </request>
+
+ <enum name="resize_edge">
+ <description summary="edge values for resizing">
+ These values are used to indicate which edge of a surface
+ is being dragged in a resize operation.
+ </description>
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="right" value="8"/>
+ <entry name="top_right" value="9"/>
+ <entry name="bottom_right" value="10"/>
+ </enum>
+
+ <request name="resize">
+ <description summary="start an interactive resize">
+ Start a user-driven, interactive resize of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive resize (touch,
+ pointer, etc).
+
+ The server may ignore resize requests depending on the state of
+ the surface (e.g. fullscreen or maximized).
+
+ If triggered, the client will receive configure events with the
+ "resize" state enum value and the expected sizes. See the "resize"
+ enum value for more details about what is required. The client
+ must also acknowledge configure events using "ack_configure". After
+ the resize is completed, the client will receive another "configure"
+ event without the resize state.
+
+ If triggered, the surface also will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the resize. It is up to the
+ compositor to visually indicate that the resize is taking place,
+ such as updating a pointer cursor, during the resize. There is no
+ guarantee that the device focus will return when the resize is
+ completed.
+
+ The edges parameter specifies how the surface should be resized,
+ and is one of the values of the resize_edge enum. The compositor
+ may use this information to update the surface position for
+ example when dragging the top left corner. The compositor may also
+ use this information to adapt its behavior, e.g. choose an
+ appropriate cursor image.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ <arg name="edges" type="uint" summary="which edge or corner is being dragged"/>
+ </request>
+
+ <enum name="state">
+ <description summary="types of state on the surface">
+ The different state values used on the surface. This is designed for
+ state values like maximized, fullscreen. It is paired with the
+ configure event to ensure that both the client and the compositor
+ setting the state can be synchronized.
+
+ States set in this way are double-buffered. They will get applied on
+ the next commit.
+ </description>
+ <entry name="maximized" value="1" summary="the surface is maximized">
+ <description summary="the surface is maximized">
+ The surface is maximized. The window geometry specified in the configure
+ event must be obeyed by the client.
+ </description>
+ </entry>
+ <entry name="fullscreen" value="2" summary="the surface is fullscreen">
+ <description summary="the surface is fullscreen">
+ The surface is fullscreen. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it. For
+ a surface to cover the whole fullscreened area, the geometry
+ dimensions must be obeyed by the client. For more details, see
+ xdg_toplevel.set_fullscreen.
+ </description>
+ </entry>
+ <entry name="resizing" value="3" summary="the surface is being resized">
+ <description summary="the surface is being resized">
+ The surface is being resized. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it.
+ Clients that have aspect ratio or cell sizing configuration can use
+ a smaller size, however.
+ </description>
+ </entry>
+ <entry name="activated" value="4" summary="the surface is now activated">
+ <description summary="the surface is now activated">
+ Client window decorations should be painted as if the window is
+ active. Do not assume this means that the window actually has
+ keyboard or pointer focus.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_max_size">
+ <description summary="set the maximum size">
+ Set a maximum size for the window.
+
+ The client can specify a maximum size so that the compositor does
+ not try to configure the window beyond this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered. They will get applied
+ on the next commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the maximum
+ size. The compositor may decide to ignore the values set by the
+ client and request a larger size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected maximum size in the given dimension.
+ As a result, a client wishing to reset the maximum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a maximum size to be smaller than the minimum size of
+ a surface is illegal and will result in a protocol error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width and height will result in a
+ protocol error.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="set_min_size">
+ <description summary="set the minimum size">
+ Set a minimum size for the window.
+
+ The client can specify a minimum size so that the compositor does
+ not try to configure the window below this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered. They will get applied
+ on the next commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the minimum
+ size. The compositor may decide to ignore the values set by the
+ client and request a smaller size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected minimum size in the given dimension.
+ As a result, a client wishing to reset the minimum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a minimum size to be larger than the maximum size of
+ a surface is illegal and will result in a protocol error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width and height will result in a
+ protocol error.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="set_maximized">
+ <description summary="maximize the window">
+ Maximize the surface.
+
+ After requesting that the surface should be maximized, the compositor
+ will respond by emitting a configure event with the "maximized" state
+ and the required window geometry. The client should then update its
+ content, drawing it in a maximized state, i.e. without shadow or other
+ decoration outside of the window geometry. The client must also
+ acknowledge the configure when committing the new content (see
+ ack_configure).
+
+ It is up to the compositor to decide how and where to maximize the
+ surface, for example which output and what region of the screen should
+ be used.
+
+ If the surface was already maximized, the compositor will still emit
+ a configure event with the "maximized" state.
+
+ If the surface is in a fullscreen state, this request has no direct
+ effect. It will alter the state the surface is returned to when
+ unmaximized if not overridden by the compositor.
+ </description>
+ </request>
+
+ <request name="unset_maximized">
+ <description summary="unmaximize the window">
+ Unmaximize the surface.
+
+ After requesting that the surface should be unmaximized, the compositor
+ will respond by emitting a configure event without the "maximized"
+ state. If available, the compositor will include the window geometry
+ dimensions the window had prior to being maximized in the configure
+ event. The client must then update its content, drawing it in a
+ regular state, i.e. potentially with shadow, etc. The client must also
+ acknowledge the configure when committing the new content (see
+ ack_configure).
+
+ It is up to the compositor to position the surface after it was
+ unmaximized; usually the position the surface had before maximizing, if
+ applicable.
+
+ If the surface was already not maximized, the compositor will still
+ emit a configure event without the "maximized" state.
+
+ If the surface is in a fullscreen state, this request has no direct
+ effect. It will alter the state the surface is returned to when
+ unmaximized if not overridden by the compositor.
+ </description>
+ </request>
+
+ <request name="set_fullscreen">
+ <description summary="set the window as fullscreen on an output">
+ Make the surface fullscreen.
+
+ After requesting that the surface should be fullscreened, the
+ compositor will respond by emitting a configure event with the
+ "fullscreen" state and the fullscreen window geometry. The client must
+ also acknowledge the configure when committing the new content (see
+ ack_configure).
+
+ The output passed by the request indicates the client's preference as
+ to which display it should be set fullscreen on. If this value is NULL,
+ it's up to the compositor to choose which display will be used to map
+ this surface.
+
+ If the surface doesn't cover the whole output, the compositor will
+ position the surface in the center of the output and compensate with
+ with border fill covering the rest of the output. The content of the
+ border fill is undefined, but should be assumed to be in some way that
+ attempts to blend into the surrounding area (e.g. solid black).
+
+ If the fullscreened surface is not opaque, the compositor must make
+ sure that other screen content not part of the same surface tree (made
+ up of subsurfaces, popups or similarly coupled surfaces) are not
+ visible below the fullscreened surface.
+ </description>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ </request>
+
+ <request name="unset_fullscreen">
+ <description summary="unset the window as fullscreen">
+ Make the surface no longer fullscreen.
+
+ After requesting that the surface should be unfullscreened, the
+ compositor will respond by emitting a configure event without the
+ "fullscreen" state.
+
+ Making a surface unfullscreen sets states for the surface based on the following:
+ * the state(s) it may have had before becoming fullscreen
+ * any state(s) decided by the compositor
+ * any state(s) requested by the client while the surface was fullscreen
+
+ The compositor may include the previous window geometry dimensions in
+ the configure event, if applicable.
+
+ The client must also acknowledge the configure when committing the new
+ content (see ack_configure).
+ </description>
+ </request>
+
+ <request name="set_minimized">
+ <description summary="set the window as minimized">
+ Request that the compositor minimize your surface. There is no
+ way to know if the surface is currently minimized, nor is there
+ any way to unset minimization on this surface.
+
+ If you are looking to throttle redrawing when minimized, please
+ instead use the wl_surface.frame event for this, as this will
+ also work with live previews on windows in Alt-Tab, Expose or
+ similar compositor features.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ This configure event asks the client to resize its toplevel surface or
+ to change its state. The configured state should not be applied
+ immediately. See xdg_surface.configure for details.
+
+ The width and height arguments specify a hint to the window
+ about how its surface should be resized in window geometry
+ coordinates. See set_window_geometry.
+
+ If the width or height arguments are zero, it means the client
+ should decide its own window dimension. This may happen when the
+ compositor needs to configure the state of the surface but doesn't
+ have any information about any previous or expected dimension.
+
+ The states listed in the event specify how the width/height
+ arguments should be interpreted, and possibly how it should be
+ drawn.
+
+ Clients must send an ack_configure in response to this event. See
+ xdg_surface.configure and xdg_surface.ack_configure for details.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ <arg name="states" type="array"/>
+ </event>
+
+ <event name="close">
+ <description summary="surface wants to be closed">
+ The close event is sent by the compositor when the user
+ wants the surface to be closed. This should be equivalent to
+ the user clicking the close button in client-side decorations,
+ if your application has any.
+
+ This is only a request that the user intends to close the
+ window. The client may choose to ignore this request, or show
+ a dialog to ask the user to save their data, etc.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="xdg_popup" version="1">
+ <description summary="short-lived, popup surfaces for menus">
+ A popup surface is a short-lived, temporary surface. It can be used to
+ implement for example menus, popovers, tooltips and other similar user
+ interface concepts.
+
+ A popup can be made to take an explicit grab. See xdg_popup.grab for
+ details.
+
+ When the popup is dismissed, a popup_done event will be sent out, and at
+ the same time the surface will be unmapped. See the xdg_popup.popup_done
+ event for details.
+
+ Explicitly destroying the xdg_popup object will also dismiss the popup and
+ unmap the surface. Clients that want to dismiss the popup when another
+ surface of their own is clicked should dismiss the popup using the destroy
+ request.
+
+ The parent surface must have either the xdg_toplevel or xdg_popup surface
+ role.
+
+ A newly created xdg_popup will be stacked on top of all previously created
+ xdg_popup surfaces associated with the same xdg_toplevel.
+
+ The parent of an xdg_popup must be mapped (see the xdg_surface
+ description) before the xdg_popup itself.
+
+ The x and y arguments passed when creating the popup object specify
+ where the top left of the popup should be placed, relative to the
+ local surface coordinates of the parent surface. See
+ xdg_surface.get_popup. An xdg_popup must intersect with or be at least
+ partially adjacent to its parent surface.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_popup state to take effect.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_grab" value="0"
+ summary="tried to grab after being mapped"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove xdg_popup interface">
+ This destroys the popup. Explicitly destroying the xdg_popup
+ object will also dismiss the popup, and unmap the surface.
+
+ If this xdg_popup is not the "topmost" popup, a protocol error
+ will be sent.
+ </description>
+ </request>
+
+ <request name="grab">
+ <description summary="make the popup take an explicit grab">
+ This request makes the created popup take an explicit grab. An explicit
+ grab will be dismissed when the user dismisses the popup, or when the
+ client destroys the xdg_popup. This can be done by the user clicking
+ outside the surface, using the keyboard, or even locking the screen
+ through closing the lid or a timeout.
+
+ If the compositor denies the grab, the popup will be immediately
+ dismissed.
+
+ This request must be used in response to some sort of user action like a
+ button press, key press, or touch down event. The serial number of the
+ event should be passed as 'serial'.
+
+ The parent of a grabbing popup must either be an xdg_toplevel surface or
+ another xdg_popup with an explicit grab. If the parent is another
+ xdg_popup it means that the popups are nested, with this popup now being
+ the topmost popup.
+
+ Nested popups must be destroyed in the reverse order they were created
+ in, e.g. the only popup you are allowed to destroy at all times is the
+ topmost one.
+
+ When compositors choose to dismiss a popup, they may dismiss every
+ nested grabbing popup as well. When a compositor dismisses popups, it
+ will follow the same dismissing order as required from the client.
+
+ The parent of a grabbing popup must either be another xdg_popup with an
+ active explicit grab, or an xdg_popup or xdg_toplevel, if there are no
+ explicit grabs already taken.
+
+ If the topmost grabbing popup is destroyed, the grab will be returned to
+ the parent of the popup, if that parent previously had an explicit grab.
+
+ If the parent is a grabbing popup which has already been dismissed, this
+ popup will be immediately dismissed. If the parent is a popup that did
+ not take an explicit grab, an error will be raised.
+
+ During a popup grab, the client owning the grab will receive pointer
+ and touch events for all their surfaces as normal (similar to an
+ "owner-events" grab in X11 parlance), while the top most grabbing popup
+ will always have keyboard focus.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"
+ summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ </request>
+
+ <event name="configure">
+ <description summary="configure the popup surface">
+ This event asks the popup surface to configure itself given the
+ configuration. The configured state should not be applied immediately.
+ See xdg_surface.configure for details.
+
+ The x and y arguments represent the position the popup was placed at
+ given the xdg_positioner rule, relative to the upper left corner of the
+ window geometry of the parent surface.
+ </description>
+ <arg name="x" type="int"
+ summary="x position relative to parent surface window geometry"/>
+ <arg name="y" type="int"
+ summary="y position relative to parent surface window geometry"/>
+ <arg name="width" type="int" summary="window geometry width"/>
+ <arg name="height" type="int" summary="window geometry height"/>
+ </event>
+
+ <event name="popup_done">
+ <description summary="popup interaction is done">
+ The popup_done event is sent out when a popup is dismissed by the
+ compositor. The client should destroy the xdg_popup object at this
+ point.
+ </description>
+ </event>
+
+ </interface>
+</protocol>
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "relative_pointer_unstable_v1.h"
+
+#include "in_process_server.h"
+#include <version_specifier.h>
+
+wlcs::ZwpRelativePointerManagerV1::ZwpRelativePointerManagerV1(Client& client) :
+ manager{client.bind_if_supported<zwp_relative_pointer_manager_v1>(AnyVersion)}
+{
+
+}
+
+wlcs::ZwpRelativePointerManagerV1::~ZwpRelativePointerManagerV1() = default;
+
+wlcs::ZwpRelativePointerManagerV1::operator zwp_relative_pointer_manager_v1*() const
+{
+ return manager;
+}
+
+wlcs::ZwpRelativePointerV1::ZwpRelativePointerV1(wlcs::ZwpRelativePointerManagerV1& manager, wl_pointer* pointer) :
+ relative_pointer{zwp_relative_pointer_manager_v1_get_relative_pointer(manager, pointer)},
+ version{zwp_relative_pointer_v1_get_version(relative_pointer)}
+{
+ zwp_relative_pointer_v1_set_user_data(relative_pointer, this);
+ zwp_relative_pointer_v1_add_listener(relative_pointer, &listener, this);
+}
+
+wlcs::ZwpRelativePointerV1::~ZwpRelativePointerV1()
+{
+ zwp_relative_pointer_v1_destroy(relative_pointer);
+}
+
+wlcs::ZwpRelativePointerV1::operator zwp_relative_pointer_v1*() const
+{
+ return relative_pointer;
+}
+
+zwp_relative_pointer_v1_listener const wlcs::ZwpRelativePointerV1::listener =
+ {
+ [](void* self, auto*, auto... args) { static_cast<ZwpRelativePointerV1*>(self)->relative_motion(args...); }
+ };
--- /dev/null
+/*
+ * Copyright © 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2 or 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "shared_library.h"
+
+#include <boost/throw_exception.hpp>
+#include <boost/exception/info.hpp>
+
+#include <dlfcn.h>
+
+#include <stdexcept>
+
+wlcs::SharedLibrary::SharedLibrary(char const* library_name) :
+ so(dlopen(library_name, RTLD_NOW | RTLD_LOCAL))
+{
+ if (!so)
+ {
+ BOOST_THROW_EXCEPTION(std::runtime_error(dlerror()));
+ }
+}
+
+wlcs::SharedLibrary::SharedLibrary(std::string const& library_name) :
+ SharedLibrary(library_name.c_str()) {}
+
+wlcs::SharedLibrary::~SharedLibrary() noexcept
+{
+ dlclose(so);
+}
+
+void* wlcs::SharedLibrary::load_symbol(char const* function_name) const
+{
+ if (void* result = dlsym(so, function_name))
+ {
+ return result;
+ }
+ else
+ {
+ BOOST_THROW_EXCEPTION(std::runtime_error(dlerror()));
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "surface_builder.h"
+#include "xdg_shell_stable.h"
+
+auto wlcs::SurfaceBuilder::all_surface_types() -> std::vector<std::shared_ptr<SurfaceBuilder>>
+{
+ return {
+ std::make_shared<WlShellSurfaceBuilder>(),
+ std::make_shared<XdgV6SurfaceBuilder>(),
+ std::make_shared<XdgStableSurfaceBuilder>(0, 0, 0, 0),
+ std::make_shared<XdgStableSurfaceBuilder>(12, 5, 20, 6),
+ std::make_shared<SubsurfaceBuilder>(std::make_pair(0, 0)),
+ std::make_shared<SubsurfaceBuilder>(std::make_pair(7, 12))};
+}
+
+auto wlcs::SurfaceBuilder::toplevel_surface_types() -> std::vector<std::shared_ptr<SurfaceBuilder>>
+{
+ return {
+ std::make_shared<WlShellSurfaceBuilder>(),
+ std::make_shared<XdgV6SurfaceBuilder>(),
+ std::make_shared<XdgStableSurfaceBuilder>(0, 0, 0, 0)};
+}
+
+auto wlcs::SurfaceBuilder::surface_builder_to_string(
+ testing::TestParamInfo<std::shared_ptr<SurfaceBuilder>> builder) -> std::string
+{
+ return builder.param->name;
+}
+
+auto wlcs::WlShellSurfaceBuilder::build(
+ wlcs::Server& server,
+ wlcs::Client& client,
+ std::pair<int, int> position,
+ std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface>
+{
+ auto surface = std::make_unique<wlcs::Surface>(
+ client.create_wl_shell_surface(size.first, size.second));
+ server.move_surface_to(*surface, position.first, position.second);
+ return surface;
+}
+
+auto wlcs::XdgV6SurfaceBuilder::build(
+ wlcs::Server& server,
+ wlcs::Client& client,
+ std::pair<int, int> position,
+ std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface>
+{
+ auto surface = std::make_unique<wlcs::Surface>(
+ client.create_xdg_shell_v6_surface(size.first, size.second));
+ server.move_surface_to(*surface, position.first, position.second);
+ return surface;
+}
+
+wlcs::XdgStableSurfaceBuilder::XdgStableSurfaceBuilder(int left_offset, int top_offset, int right_offset, int bottom_offset)
+ : SurfaceBuilder{"xdg_surface_stable" +
+ ((left_offset || top_offset || right_offset || bottom_offset) ? ("_" +
+ std::to_string(left_offset) + "_" +
+ std::to_string(top_offset) + "_" +
+ std::to_string(right_offset) + "_" +
+ std::to_string(bottom_offset)) : "")},
+ left_offset{left_offset},
+ top_offset{top_offset},
+ right_offset{right_offset},
+ bottom_offset{bottom_offset}
+{
+}
+
+auto wlcs::XdgStableSurfaceBuilder::build(
+ wlcs::Server& server,
+ wlcs::Client& client,
+ std::pair<int, int> position,
+ std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface>
+{
+ auto surface = std::make_unique<wlcs::Surface>(client);
+ auto xdg_surface = std::make_shared<wlcs::XdgSurfaceStable>(client, *surface);
+ auto xdg_toplevel = std::make_shared<wlcs::XdgToplevelStable>(*xdg_surface);
+ // The logical window will be shrunk and moved based on the offset, but the underlying surface will not
+ xdg_surface_set_window_geometry(
+ *xdg_surface,
+ left_offset, top_offset,
+ size.first - left_offset - right_offset, size.second -bottom_offset - top_offset);
+ surface->attach_visible_buffer(size.first, size.second);
+ surface->run_on_destruction(
+ [xdg_surface, xdg_toplevel]() mutable
+ {
+ xdg_toplevel.reset();
+ xdg_surface.reset();
+ });
+ server.move_surface_to(*surface, position.first + left_offset, position.second + top_offset);
+ return surface;
+}
+
+wlcs::SubsurfaceBuilder::SubsurfaceBuilder(std::pair<int, int> offset)
+ : SurfaceBuilder{
+ "subsurface_at_x" +
+ std::to_string(offset.first) +
+ "_y" +
+ std::to_string(offset.second)},
+ offset{offset}
+{
+}
+
+auto wlcs::SubsurfaceBuilder::build(
+ wlcs::Server& server,
+ wlcs::Client& client,
+ std::pair<int, int> position,
+ std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface>
+{
+ auto main_surface = std::make_shared<wlcs::Surface>(
+ client.create_visible_surface(80, 50));
+ server.move_surface_to(
+ *main_surface,
+ position.first - offset.first,
+ position.second - offset.second);
+ client.run_on_destruction(
+ [main_surface]() mutable
+ {
+ main_surface.reset();
+ });
+ auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+ *main_surface,
+ offset.first, offset.second,
+ size.first, size.second));
+ // if subsurface is sync, tests would have to commit the parent to modify it
+ // this is inconvenient to do in a generic way, so we make it desync
+ wl_subsurface_set_desync(*subsurface);
+ return subsurface;
+}
+
+std::ostream& std::operator<<(std::ostream& out, std::shared_ptr<wlcs::SurfaceBuilder> const& param)
+{
+ return out << param->name;
+}
--- /dev/null
+//!
+//! termcolor
+//! ~~~~~~~~~
+//!
+//! termcolor is a header-only c++ library for printing colored messages
+//! to the terminal. Written just for fun with a help of the Force.
+//!
+//! :copyright: (c) 2013 by Ihor Kalnytskyi
+//! :license: BSD, see LICENSE for details
+//!
+
+#ifndef TERMCOLOR_HPP_
+#define TERMCOLOR_HPP_
+
+// the following snippet of code detects the current OS and
+// defines the appropriate macro that is used to wrap some
+// platform specific things
+#if defined(_WIN32) || defined(_WIN64)
+# define TERMCOLOR_OS_WINDOWS
+#elif defined(__APPLE__)
+# define TERMCOLOR_OS_MACOS
+#elif defined(__unix__) || defined(__unix)
+# define TERMCOLOR_OS_LINUX
+#else
+# error unsupported platform
+#endif
+
+
+// This headers provides the `isatty()`/`fileno()` functions,
+// which are used for testing whether a standart stream refers
+// to the terminal. As for Windows, we also need WinApi funcs
+// for changing colors attributes of the terminal.
+#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+# include <unistd.h>
+#elif defined(TERMCOLOR_OS_WINDOWS)
+# include <io.h>
+# include <windows.h>
+#endif
+
+
+#include <iostream>
+#include <cstdio>
+
+
+
+namespace termcolor
+{
+ // Forward declaration of the `_internal` namespace.
+ // All comments are below.
+ namespace _internal
+ {
+ // An index to be used to access a private storage of I/O streams. See
+ // colorize / nocolorize I/O manipulators for details.
+ static int colorize_index = std::ios_base::xalloc();
+
+ inline FILE* get_standard_stream(const std::ostream& stream);
+ inline bool is_colorized(std::ostream& stream);
+ inline bool is_atty(const std::ostream& stream);
+
+ #if defined(TERMCOLOR_OS_WINDOWS)
+ inline void win_change_attributes(std::ostream& stream, int foreground, int background=-1);
+ #endif
+ }
+
+ inline
+ std::ostream& colorize(std::ostream& stream)
+ {
+ stream.iword(_internal::colorize_index) = 1L;
+ return stream;
+ }
+
+ inline
+ std::ostream& nocolorize(std::ostream& stream)
+ {
+ stream.iword(_internal::colorize_index) = 0L;
+ return stream;
+ }
+
+ inline
+ std::ostream& reset(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[00m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream, -1, -1);
+ #endif
+ }
+ return stream;
+ }
+
+
+ inline
+ std::ostream& bold(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[1m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ #endif
+ }
+ return stream;
+ }
+
+
+ inline
+ std::ostream& dark(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[2m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ #endif
+ }
+ return stream;
+ }
+
+
+ inline
+ std::ostream& underline(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[4m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ #endif
+ }
+ return stream;
+ }
+
+
+ inline
+ std::ostream& blink(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[5m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ #endif
+ }
+ return stream;
+ }
+
+
+ inline
+ std::ostream& reverse(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[7m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ #endif
+ }
+ return stream;
+ }
+
+
+ inline
+ std::ostream& concealed(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[8m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ #endif
+ }
+ return stream;
+ }
+
+
+ inline
+ std::ostream& grey(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[30m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream,
+ 0 // grey (black)
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& red(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[31m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& green(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[32m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_GREEN
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& yellow(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[33m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_GREEN | FOREGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& blue(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[34m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& magenta(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[35m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& cyan(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[36m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_GREEN
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& white(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[37m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+
+
+ inline
+ std::ostream& on_grey(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[40m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream, -1,
+ 0 // grey (black)
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_red(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[41m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_green(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[42m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_yellow(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[43m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_blue(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[44m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_BLUE
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_magenta(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[45m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_BLUE | BACKGROUND_RED
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_cyan(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[46m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_BLUE
+ );
+ #endif
+ }
+ return stream;
+ }
+
+ inline
+ std::ostream& on_white(std::ostream& stream)
+ {
+ if (_internal::is_colorized(stream))
+ {
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ stream << "\033[47m";
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED
+ );
+ #endif
+ }
+
+ return stream;
+ }
+
+
+
+ //! Since C++ hasn't a way to hide something in the header from
+ //! the outer access, I have to introduce this namespace which
+ //! is used for internal purpose and should't be access from
+ //! the user code.
+ namespace _internal
+ {
+ //! Since C++ hasn't a true way to extract stream handler
+ //! from the a given `std::ostream` object, I have to write
+ //! this kind of hack.
+ inline
+ FILE* get_standard_stream(const std::ostream& stream)
+ {
+ if (&stream == &std::cout)
+ return stdout;
+ else if ((&stream == &std::cerr) || (&stream == &std::clog))
+ return stderr;
+
+ return 0;
+ }
+
+ // Say whether a given stream should be colorized or not. It's always
+ // true for ATTY streams and may be true for streams marked with
+ // colorize flag.
+ inline
+ bool is_colorized(std::ostream& stream)
+ {
+ return is_atty(stream) || static_cast<bool>(stream.iword(colorize_index));
+ }
+
+ //! Test whether a given `std::ostream` object refers to
+ //! a terminal.
+ inline
+ bool is_atty(const std::ostream& stream)
+ {
+ FILE* std_stream = get_standard_stream(stream);
+
+ // Unfortunately, fileno() ends with segmentation fault
+ // if invalid file descriptor is passed. So we need to
+ // handle this case gracefully and assume it's not a tty
+ // if standard stream is not detected, and 0 is returned.
+ if (!std_stream)
+ return false;
+
+ #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+ return ::isatty(fileno(std_stream));
+ #elif defined(TERMCOLOR_OS_WINDOWS)
+ return ::_isatty(_fileno(std_stream));
+ #endif
+ }
+
+ #if defined(TERMCOLOR_OS_WINDOWS)
+ //! Change Windows Terminal colors attribute. If some
+ //! parameter is `-1` then attribute won't changed.
+ inline void win_change_attributes(std::ostream& stream, int foreground, int background)
+ {
+ // yeah, i know.. it's ugly, it's windows.
+ static WORD defaultAttributes = 0;
+
+ // Windows doesn't have ANSI escape sequences and so we use special
+ // API to change Terminal output color. That means we can't
+ // manipulate colors by means of "std::stringstream" and hence
+ // should do nothing in this case.
+ if (!_internal::is_atty(stream))
+ return;
+
+ // get terminal handle
+ HANDLE hTerminal = INVALID_HANDLE_VALUE;
+ if (&stream == &std::cout)
+ hTerminal = GetStdHandle(STD_OUTPUT_HANDLE);
+ else if (&stream == &std::cerr)
+ hTerminal = GetStdHandle(STD_ERROR_HANDLE);
+
+ // save default terminal attributes if it unsaved
+ if (!defaultAttributes)
+ {
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if (!GetConsoleScreenBufferInfo(hTerminal, &info))
+ return;
+ defaultAttributes = info.wAttributes;
+ }
+
+ // restore all default settings
+ if (foreground == -1 && background == -1)
+ {
+ SetConsoleTextAttribute(hTerminal, defaultAttributes);
+ return;
+ }
+
+ // get current settings
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if (!GetConsoleScreenBufferInfo(hTerminal, &info))
+ return;
+
+ if (foreground != -1)
+ {
+ info.wAttributes &= ~(info.wAttributes & 0x0F);
+ info.wAttributes |= static_cast<WORD>(foreground);
+ }
+
+ if (background != -1)
+ {
+ info.wAttributes &= ~(info.wAttributes & 0xF0);
+ info.wAttributes |= static_cast<WORD>(background);
+ }
+
+ SetConsoleTextAttribute(hTerminal, info.wAttributes);
+ }
+ #endif // TERMCOLOR_OS_WINDOWS
+
+ } // namespace _internal
+
+} // namespace termcolor
+
+
+#undef TERMCOLOR_OS_WINDOWS
+#undef TERMCOLOR_OS_MACOS
+#undef TERMCOLOR_OS_LINUX
+
+#endif // TERMCOLOR_HPP_
--- /dev/null
+#include "wlcs/display_server.h"
+#include "wlcs/pointer.h"
+#include "wlcs/touch.h"
+
+WlcsServerIntegration server_integration;
+WlcsPointer pointer;
+WlcsTouch touch;
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_THREAD_PROXY_H_
+#define WLCS_THREAD_PROXY_H_
+
+#include <wayland-server-core.h>
+#include <functional>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <mutex>
+#include <thread>
+#include <tuple>
+#include <boost/throw_exception.hpp>
+
+/*
+ * ThreadProxy: Take a Callable and run it on the Wayland event loop.
+ *
+ * There are two main parts here: some template metaprogramming, and
+ * the ThreadProxy class.
+ *
+ * ThreadProxy maintains the infrastructure for taking Callables
+ * and invoking them on a Wayland event loop. The meat here is
+ * in register_op(), which takes a Callable and returns a
+ * new Callable that, when invoked, will result in the original
+ * Callable being called on the Wayland event loop.
+ *
+ * The mechanism used for invoking on the Wayland event loop is a
+ * socket: arguments are serialised to the socket, and then deserialised
+ * when processed by the event loop.
+ *
+ * The template metaprogramming is mostly for manipulating parameter
+ * packs: serialising them to and from linear buffers (so that they
+ * can be passed across a socket), and invoking a Callable with a
+ * tuple of args, and deducing the arguments and return value of
+ * a Callable
+ *
+ */
+
+namespace
+{
+/*
+ * Deduce the argument types and return values of a Callable.
+ *
+ * Notably this only works for Callables with exactly one implementation
+ * of operator(), otherwise template deduction will fail as it is unable
+ * to select which operator() to operate on.
+ *
+ * This isn't an issue for what we're using this with, which is primarly
+ * lambdas.
+ */
+template<typename Callable>
+struct callable_traits : public callable_traits<decltype(&Callable::operator())> {};
+
+template<typename Callable, typename Returns, typename... Args>
+struct callable_traits<Returns(Callable::*)(Args...) const>
+{
+ using return_type = Returns;
+ using args = typename std::tuple<Args...>;
+};
+
+/*
+ * call_helper and call are downmarket versions of C++17's std::invoke()
+ *
+ * We don't yet require C++17, so downmarket it is!
+ */
+template<typename Returns, typename Callable, typename... Args, std::size_t... I>
+Returns call_helper(Callable const& func, std::tuple<Args...> const& args, std::index_sequence<I...>)
+{
+ return func(std::get<I>(args)...);
+}
+
+template<typename Returns, typename Callable, typename... Args>
+Returns call(Callable const& func, std::tuple<Args...> const& args)
+{
+ return call_helper<Returns>(func, args, std::index_sequence_for<Args...>{});
+}
+
+/*
+ * tuple_from_buffer base case: To unpack a buffer containing no values,
+ * return a std::tuple<>.
+ */
+template<
+ typename... Args,
+ typename std::enable_if<sizeof...(Args) == 0, int>::type = 0>
+std::tuple<Args...> tuple_from_buffer(char*, std::tuple<Args...> const*)
+{
+ return std::tuple<Args...>{};
+}
+
+/*
+ * Unpack a linear buffer into a std::tuple of its component types.
+ */
+template<typename Head, typename... Tail>
+std::tuple<Head, Tail...> tuple_from_buffer(char* buffer, std::tuple<Head, Tail...> const*)
+{
+ // We need to memcpy out of the buffer as there's no guarantee data in the buffer has correct
+ // alignment for this type.
+ Head aligned_arg;
+ ::memcpy(&aligned_arg, buffer, sizeof(Head));
+
+ std::tuple<Tail...> const* type_deducer = nullptr;
+ return std::tuple_cat(std::make_tuple(aligned_arg), tuple_from_buffer(buffer + sizeof(Head), type_deducer));
+}
+
+std::array<int, 2> setup_socketpair()
+{
+ std::array<int, 2> socket_fds;
+
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, socket_fds.data()) < 0)
+ {
+ BOOST_THROW_EXCEPTION((
+ std::system_error{
+ errno,
+ std::system_category(),
+ "Failed to create Wayland thread communication socket"}));
+ }
+
+ return socket_fds;
+}
+
+constexpr ssize_t arguments_size()
+{
+ return 0;
+}
+
+template<typename T, typename... Remainder>
+constexpr ssize_t arguments_size(T const& head, Remainder const&... tail)
+{
+ return sizeof(head) + arguments_size(tail...);
+}
+
+/*
+ * pack_buffer base case: To pack no arguments into a buffer, do nothing.
+ */
+void pack_buffer(void*)
+{
+}
+
+/*
+ * Pack a any number of (TriviallyCopyable) arguments into a linear buffer.
+ */
+template<typename T, typename... Remainder>
+void pack_buffer(char* buffer, T&& arg, Remainder&& ...args)
+{
+ memcpy(buffer, &arg, sizeof(arg));
+ pack_buffer(buffer + sizeof(arg), std::forward<Remainder>(args)...);
+}
+
+/*
+ * A bit more C++17 backporting...
+ *
+ * This bit of metaprogramming is in support of statically-asserting that
+ * all the types passed through the proxy are TriviallyCopyable.
+ */
+template<class...>
+struct conjunction : std::true_type {};
+
+template<class Cond>
+struct conjunction<Cond> : Cond {};
+
+template<class Head, class... Tail>
+struct conjunction<Head, Tail...> :
+ std::conditional_t<bool(Head::value), conjunction<Tail...>, Head> {};
+
+template<typename... Args>
+constexpr bool all_args_are_trivially_copyable(std::tuple<Args...> const*)
+{
+ return conjunction<std::is_trivially_copyable<Args>...>::value;
+}
+
+/*
+ * ThreadProxy: a mechanism to take a functor from any thread, and turn it into
+ * a functor that runs on the Wayland event loop.
+ *
+ * Theory of operation:
+ * The inter-thread channel used is a SOCK_SEQPACKET socket.
+ *
+ * register_op() takes an (almost) arbitrary Callable, wraps it in a
+ * std::function<> that recieves a pointer to a buffer of arguments,
+ * unpacks the buffer into a std::tuple, then invokes the original
+ * Callable, and adds this function to the handler array.
+ *
+ * register_op() then *returns* a function with the same signature that
+ * copies the array index of the relevant handler into a falt buffer,
+ * marshalls the arguments passed in into the buffer and then pushes that buffer
+ * into the socket.
+ *
+ * On the Wayland event loop side, we add a fd event_source to the event_loop
+ * which reads from the Wayland end of the socket, extracts the index into the
+ * handler array, then calls the handler with the rest of the message buffer.
+ */
+class ThreadProxy : public std::enable_shared_from_this<ThreadProxy>
+{
+private:
+
+ template<typename... Args>
+ void send_message(uint32_t opcode, Args&& ...args) const
+ {
+ char buffer[max_message_size];
+ ssize_t const message_size = sizeof(uint32_t) + arguments_size(args...);
+
+ *reinterpret_cast<uint32_t*>(buffer) = opcode;
+ pack_buffer(buffer + sizeof(uint32_t), std::forward<Args>(args)...);
+
+ auto const written = send(fds[Fd::Client], buffer, message_size, MSG_DONTWAIT);
+
+ if (written < message_size)
+ {
+ if (written < 0)
+ {
+ BOOST_THROW_EXCEPTION((
+ std::system_error{
+ errno,
+ std::system_category(),
+ "Failed to send message to Wayland thread"}));
+ }
+ BOOST_THROW_EXCEPTION((
+ std::runtime_error{
+ "Failed to send whole message to Wayland thread"}));
+ }
+ }
+
+ template<typename T>
+ T wait_for_reply() const
+ {
+ T buffer;
+ // Wait for it to be processed
+ auto const read = recv(fds[Fd::Client], &buffer, sizeof(buffer), 0);
+ if (read < static_cast<ssize_t>(sizeof(buffer)))
+ {
+ if (read < 0)
+ {
+ BOOST_THROW_EXCEPTION((
+ std::system_error{
+ errno,
+ std::system_category(),
+ "Failed to receive reply from Wayland thread"}));
+ }
+ BOOST_THROW_EXCEPTION((
+ std::runtime_error{
+ "Received short reply from Wayland thread"}));
+ }
+ return buffer;
+ }
+
+ template<typename... Args>
+ auto make_send_functor(uint32_t opcode, std::tuple<Args...> const*) const
+ {
+ return
+ [me = this->shared_from_this(), opcode](Args&& ...args)
+ {
+ // We technically don't need to serialise the send/receive, but
+ // it's a bit easier if only one request is in flight at once.
+ std::lock_guard<std::mutex> lock{me->message_serialiser};
+ me->send_message(opcode, std::forward<Args>(args)...);
+ me->wait_for_reply<char>();
+ };
+ }
+
+ template<typename Returns, typename... Args>
+ auto make_send_functor(uint32_t opcode, std::tuple<Args...> const*) const
+ {
+ return
+ [me = this->shared_from_this(), opcode](Args&& ...args)
+ {
+ // We technically don't need to serialise the send/receive, but
+ // it's a bit easier if only one request is in flight at once.
+ std::lock_guard<std::mutex> lock{me->message_serialiser};
+ me->send_message(opcode, std::forward<Args>(args)...);
+ return me->wait_for_reply<Returns>();
+ };
+ }
+
+ template<
+ typename Callable,
+ typename... Args,
+ typename
+ std::enable_if_t<
+ std::is_same<
+ typename callable_traits<typename std::decay<Callable>::type>::return_type,
+ void>::value,
+ int> = 0>
+ auto make_recv_functor(Callable handler, std::tuple<Args...> const*)
+ {
+ return
+ [this, handler = std::move(handler)](void* data)
+ {
+ std::tuple<Args...> const* type_resolver = nullptr;
+ auto const args = tuple_from_buffer(static_cast<char*>(data), type_resolver);
+
+ call<void>(handler, args);
+
+ // void functions send a dummy char, to notify the other end that
+ // the call has completed.
+ char const dummy{0};
+ send(fds[Fd::Wayland], &dummy, sizeof(dummy), 0);
+ };
+ }
+
+ template<
+ typename Callable,
+ typename... Args,
+ typename
+ std::enable_if_t<
+ !std::is_same<
+ typename callable_traits<typename std::decay<Callable>::type>::return_type,
+ void>::value, int> = 0>
+ auto make_recv_functor(Callable handler, std::tuple<Args...> const*)
+ {
+ return
+ [this, handler = std::move(handler)](void* data)
+ {
+ using traits = callable_traits<typename std::decay<Callable>::type>;
+
+ std::tuple<Args...> const* type_resolver = nullptr;
+ auto const args = tuple_from_buffer(static_cast<char*>(data), type_resolver);
+
+ auto const val = call<typename traits::return_type>(handler, args);
+
+ send(this->fds[Fd::Wayland], &val, sizeof(val), 0);
+ };
+ }
+public:
+ template<
+ typename Callable,
+ typename
+ std::enable_if_t<
+ std::is_same<
+ typename callable_traits<typename std::decay<Callable>::type>::return_type,
+ void>::value, int> = 0>
+ auto register_op(Callable handler)
+ {
+ using traits = callable_traits<typename std::decay<Callable>::type>;
+ // This is technically too restrictive; std::tuple<Args...> might have a size greater
+ // than the sum of its parts.
+ static_assert(
+ sizeof(typename traits::args) < max_arguments_size,
+ "Attempt to call function with too many arguments; bump max_message_size");
+
+
+ // Get template deduction to work by passing an (unused) argument of type
+ // std::tuple<Args...>*; we can't access Args... ourselves here.
+ constexpr typename traits::args const* type_resolver = nullptr;
+
+ static_assert(
+ all_args_are_trivially_copyable(type_resolver),
+ "All arguments of register_op must be TriviallyCopyable as they must support memcpy");
+
+ auto const next_opcode = handlers.size();
+ handlers.emplace_back(make_recv_functor(std::move(handler), type_resolver));
+
+ return make_send_functor(next_opcode, type_resolver);
+ }
+
+ template<
+ typename Callable,
+ typename
+ std::enable_if_t<
+ !std::is_same<
+ typename callable_traits<typename std::decay<Callable>::type>::return_type,
+ void>::value, int> = 0>
+ auto register_op(Callable handler)
+ {
+ using traits = callable_traits<typename std::decay<Callable>::type>;
+ static_assert(
+ sizeof(typename traits::args) < max_arguments_size,
+ "Attempt to call function with too many arguments; bump max_message_size");
+ static_assert(
+ sizeof(typename traits::return_type) < max_message_size,
+ "Attempt to call function with too large return value; bump max_message_size");
+
+ // Get template deduction to work by passing an (unused) argument of type
+ // std::tuple<Args...>; we can't access Args... ourselves here.
+ constexpr typename traits::args const* type_resolver = nullptr;
+ static_assert(
+ all_args_are_trivially_copyable(type_resolver),
+ "All arguments of register_op must be TriviallyCopyable as they must support memcpy");
+
+ auto const next_opcode = handlers.size();
+ handlers.emplace_back(make_recv_functor(std::move(handler), type_resolver));
+
+ return make_send_functor<typename traits::return_type>(next_opcode, type_resolver);
+ }
+
+ explicit ThreadProxy(struct wl_event_loop* event_loop)
+ : fds{setup_socketpair()},
+ source{
+ wl_event_loop_add_fd(
+ event_loop,
+ fds[Fd::Wayland],
+ WL_EVENT_READABLE,
+ &ThreadProxy::socket_readable,
+ this)},
+ // We have to manually implement the destructor() thunk rather than using register_op because
+ // the send_functor returned by register_op takes shared reference to this, care of
+ // shared_from_this(). But during construction `this` is not a shared_ptr, so shared_from_this()
+ // isn't usable.
+ destructor{
+ [this]()
+ {
+ send_message(0);
+ }}
+ {
+ // We must run wl_event_source_remove() on the Wayland mainloop, too.
+ handlers.emplace_back(
+ [this](void*)
+ {
+ wl_event_source_remove(source);
+ });
+ }
+
+ ~ThreadProxy()
+ {
+ destructor();
+ close(fds[0]);
+ close(fds[1]);
+ }
+
+private:
+ static int socket_readable(int fd, uint32_t /*mask*/, void* data) noexcept
+ {
+ auto const& me = *static_cast<ThreadProxy*>(data);
+ char buffer[max_message_size];
+
+ recv(fd, buffer, sizeof(buffer), 0);
+
+ auto const opcode = *reinterpret_cast<uint32_t*>(buffer);
+ me.handlers[opcode](buffer + sizeof(uint32_t));
+
+ return 0;
+ }
+
+ static constexpr ssize_t max_arguments_size{1024};
+ static constexpr ssize_t max_message_size = max_arguments_size + sizeof(uint32_t);
+ enum Fd
+ {
+ Wayland = 0,
+ Client
+ };
+ std::array<int, 2> const fds;
+ struct wl_event_source* const source;
+ std::mutex mutable message_serialiser;
+ std::vector<std::function<void(void*)>> handlers;
+ std::function<void()> const destructor;
+};
+}
+
+
+#endif //WLCS_THREAD_PROXY_H_
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include "version_specifier.h"
+
+using std::experimental::optional;
+
+wlcs::ExactlyVersion::ExactlyVersion(uint32_t version) noexcept
+ : version{version}
+{
+}
+
+auto wlcs::ExactlyVersion::select_version(uint32_t max_version) const -> optional<uint32_t>
+{
+ if (max_version < version)
+ {
+ return {};
+ }
+ return {version};
+}
+
+auto wlcs::ExactlyVersion::describe() const -> std::string
+{
+ return std::string{"= "} + std::to_string(version);
+}
+
+wlcs::AtLeastVersion::AtLeastVersion(uint32_t version) noexcept
+ : version{version}
+{
+}
+
+auto wlcs::AtLeastVersion::select_version(uint32_t max_version) const -> optional<uint32_t>
+{
+ if (max_version < version)
+ {
+ return {};
+ }
+ return {max_version};
+}
+
+auto wlcs::AtLeastVersion::describe() const -> std::string
+{
+ return std::string{">= "} + std::to_string(version);
+}
+
+wlcs::VersionSpecifier const& wlcs::AnyVersion = wlcs::AtLeastVersion{1};
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "xdg_output_v1.h"
+#include "wl_handle.h"
+#include "version_specifier.h"
+#include <boost/throw_exception.hpp>
+
+struct wlcs::XdgOutputManagerV1::Impl
+{
+ Impl(Client& client)
+ : client{client},
+ manager{client.bind_if_supported<zxdg_output_manager_v1>(AnyVersion)}
+ {
+ }
+
+ Client& client;
+ WlHandle<zxdg_output_manager_v1> const manager;
+};
+
+wlcs::XdgOutputManagerV1::XdgOutputManagerV1(Client& client)
+ : impl{std::make_unique<Impl>(client)}
+{
+}
+
+wlcs::XdgOutputManagerV1::~XdgOutputManagerV1() = default;
+
+wlcs::XdgOutputManagerV1::operator zxdg_output_manager_v1*() const
+{
+ return impl->manager;
+}
+
+auto wlcs::XdgOutputManagerV1::client() const -> Client&
+{
+ return impl->client;
+}
+
+struct wlcs::XdgOutputV1::Impl
+{
+ Impl(XdgOutputManagerV1& manager, size_t output_index)
+ : output{zxdg_output_manager_v1_get_xdg_output(
+ manager,
+ manager.client().output_state(output_index).output)},
+ version{zxdg_output_v1_get_version(output)}
+ {
+ zxdg_output_v1_add_listener(output, &Impl::listener, this);
+ }
+
+ ~Impl()
+ {
+ zxdg_output_v1_destroy(output);
+ }
+
+ zxdg_output_v1* const output;
+ uint32_t const version;
+ bool dirty{false};
+ State _state;
+
+ static zxdg_output_v1_listener const listener;
+};
+
+zxdg_output_v1_listener const wlcs::XdgOutputV1::Impl::listener = {
+ [] /* logical_position */ (void *data, zxdg_output_v1 *, int32_t x, int32_t y)
+ {
+ auto const impl = static_cast<Impl*>(data);
+ impl->_state.logical_position = std::make_pair(x, y);
+ impl->dirty = true;
+ },
+
+ [] /* logical_size */ (void *data, zxdg_output_v1 *, int32_t width, int32_t height)
+ {
+ auto const impl = static_cast<Impl*>(data);
+ impl->_state.logical_size = std::make_pair(width, height);
+ impl->dirty = true;
+ },
+
+ [] /* done */ (void *data, zxdg_output_v1 *)
+ {
+ auto const impl = static_cast<Impl*>(data);
+ if (impl->version < 3)
+ impl->dirty = false;
+ },
+
+ [] /* name */ (void *data, zxdg_output_v1 *, const char *name)
+ {
+ auto const impl = static_cast<Impl*>(data);
+ std::string str{name};
+ impl->_state.name = str;
+ impl->dirty = true;
+ },
+
+ [] /* description */ (void *data, zxdg_output_v1 *, const char *description)
+ {
+ auto const impl = static_cast<Impl*>(data);
+ impl->_state.description = std::string{description};
+ impl->dirty = true;
+ },
+};
+
+wlcs::XdgOutputV1::XdgOutputV1(XdgOutputManagerV1& manager, size_t output_index)
+ : impl{std::make_shared<Impl>(manager, output_index)}
+{
+ if (impl->version >= 3)
+ {
+ manager.client().add_output_done_notifier(output_index, [weak = std::weak_ptr<Impl>(impl)]()
+ {
+ if (auto const impl = weak.lock())
+ {
+ impl->dirty = false;
+ }
+ });
+ }
+}
+
+wlcs::XdgOutputV1::operator zxdg_output_v1*() const
+{
+ return impl->output;
+}
+
+auto wlcs::XdgOutputV1::state() -> State const&
+{
+ if (impl->dirty)
+ {
+ std::string done_event = (impl->version >= 3 ? "wl_output.done" : "zxdg_output_v1.done");
+ BOOST_THROW_EXCEPTION(std::logic_error("State change was not finished with a " + done_event + " event"));
+ }
+
+ return impl->_state;
+}
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "xdg_shell_stable.h"
+
+// XdgSurfaceStable
+
+wlcs::XdgSurfaceStable::XdgSurfaceStable(wlcs::Client& client, wlcs::Surface& surface)
+{
+ if (!client.xdg_shell_stable())
+ throw std::runtime_error("XDG shell stable not supported by compositor");
+ shell_surface = xdg_wm_base_get_xdg_surface(client.xdg_shell_stable(), surface);
+ static struct xdg_surface_listener const listener = {configure_thunk};
+ xdg_surface_add_listener(shell_surface, &listener, this);
+}
+
+wlcs::XdgSurfaceStable::~XdgSurfaceStable()
+{
+ xdg_surface_destroy(shell_surface);
+}
+
+// XdgToplevelStable
+
+wlcs::XdgToplevelStable::State::State(int32_t width, int32_t height, struct wl_array *states)
+ : width{width},
+ height{height},
+ maximized{false},
+ fullscreen{false},
+ resizing{false},
+ activated{false}
+{
+ if (!states)
+ return;
+ xdg_toplevel_state* item;
+ for (item = static_cast<xdg_toplevel_state*>(states->data);
+ (char*)item < static_cast<char*>(states->data) + states->size;
+ item++)
+ {
+ switch (*item)
+ {
+ case XDG_TOPLEVEL_STATE_MAXIMIZED:
+ maximized = true;
+ break;
+ case XDG_TOPLEVEL_STATE_FULLSCREEN:
+ fullscreen = true;
+ break;
+ case XDG_TOPLEVEL_STATE_RESIZING:
+ resizing = true;
+ break;
+ case XDG_TOPLEVEL_STATE_ACTIVATED:
+ activated = true;
+ break;
+ }
+ }
+}
+
+wlcs::XdgToplevelStable::XdgToplevelStable(XdgSurfaceStable& shell_surface_)
+ : shell_surface{&shell_surface_}
+{
+ toplevel = xdg_surface_get_toplevel(*shell_surface);
+ static struct xdg_toplevel_listener const listener = {configure_thunk, close_thunk};
+ xdg_toplevel_add_listener(toplevel, &listener, this);
+}
+
+wlcs::XdgToplevelStable::~XdgToplevelStable()
+{
+ xdg_toplevel_destroy(toplevel);
+}
+
+wlcs::XdgPositionerStable::XdgPositionerStable(wlcs::Client& client)
+ : positioner{xdg_wm_base_create_positioner(client.xdg_shell_stable())}
+{
+}
+
+wlcs::XdgPositionerStable::~XdgPositionerStable()
+{
+ xdg_positioner_destroy(positioner);
+}
+
+wlcs::XdgPopupStable::XdgPopupStable(
+ XdgSurfaceStable& shell_surface_,
+ std::experimental::optional<XdgSurfaceStable*> parent,
+ XdgPositionerStable& positioner)
+ : shell_surface{&shell_surface_},
+ popup{xdg_surface_get_popup(
+ *shell_surface,
+ parent ? *parent.value() : (xdg_surface*)nullptr,
+ positioner)}
+{
+ static struct xdg_popup_listener const listener = {configure_thunk, popup_done_thunk};
+ xdg_popup_add_listener(popup, &listener, this);
+}
+
+wlcs::XdgPopupStable::~XdgPopupStable()
+{
+ xdg_popup_destroy(popup);
+}
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "xdg_shell_v6.h"
+
+// XdgSurfaceV6
+
+wlcs::XdgSurfaceV6::XdgSurfaceV6(wlcs::Client& client, wlcs::Surface& surface)
+{
+ if (!client.xdg_shell_v6())
+ throw std::runtime_error("XDG shell unstable V6 not supported by compositor");
+ shell_surface = zxdg_shell_v6_get_xdg_surface(client.xdg_shell_v6(), surface);
+ static struct zxdg_surface_v6_listener const listener = {configure_thunk};
+ zxdg_surface_v6_add_listener(shell_surface, &listener, this);
+}
+
+wlcs::XdgSurfaceV6::~XdgSurfaceV6()
+{
+ zxdg_surface_v6_destroy(shell_surface);
+}
+
+// XdgToplevelV6
+
+wlcs::XdgToplevelV6::State::State(int32_t width, int32_t height, struct wl_array *states)
+ : width{width},
+ height{height},
+ maximized{false},
+ fullscreen{false},
+ resizing{false},
+ activated{false}
+{
+ if (!states)
+ return;
+ zxdg_toplevel_v6_state* item;
+ for (item = static_cast<zxdg_toplevel_v6_state*>(states->data);
+ (char*)item < static_cast<char*>(states->data) + states->size;
+ item++)
+ {
+ switch (*item)
+ {
+ case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
+ maximized = true;
+ break;
+ case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
+ fullscreen = true;
+ break;
+ case ZXDG_TOPLEVEL_V6_STATE_RESIZING:
+ resizing = true;
+ break;
+ case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
+ activated = true;
+ break;
+ }
+ }
+}
+
+wlcs::XdgToplevelV6::XdgToplevelV6(XdgSurfaceV6& shell_surface_)
+ : shell_surface{&shell_surface_}
+{
+ toplevel = zxdg_surface_v6_get_toplevel(*shell_surface);
+ static struct zxdg_toplevel_v6_listener const listener = {configure_thunk, close_thunk};
+ zxdg_toplevel_v6_add_listener(toplevel, &listener, this);
+}
+
+wlcs::XdgToplevelV6::~XdgToplevelV6()
+{
+ zxdg_toplevel_v6_destroy(toplevel);
+}
+
+wlcs::XdgPositionerV6::XdgPositionerV6(wlcs::Client& client)
+ : positioner{zxdg_shell_v6_create_positioner(client.xdg_shell_v6())}
+{
+}
+
+wlcs::XdgPositionerV6::~XdgPositionerV6()
+{
+ zxdg_positioner_v6_destroy(positioner);
+}
+
+wlcs::XdgPopupV6::XdgPopupV6(XdgSurfaceV6& shell_surface_, XdgSurfaceV6& parent, XdgPositionerV6& positioner)
+ : shell_surface{&shell_surface_},
+ popup{zxdg_surface_v6_get_popup(*shell_surface, parent, positioner)}
+{
+ static struct zxdg_popup_v6_listener const listener = {configure_thunk, popup_done_thunk};
+ zxdg_popup_v6_add_listener(popup, &listener, this);
+}
+
+wlcs::XdgPopupV6::~XdgPopupV6()
+{
+ zxdg_popup_v6_destroy(popup);
+}
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include "xfail_supporting_test_listener.h"
+#include <gtest/gtest.h>
+#include <chrono>
+#include "termcolor.hpp"
+
+testing::XFailSupportingTestListenerWrapper::XFailSupportingTestListenerWrapper(std::unique_ptr<testing::TestEventListener>&& wrapped)
+ : delegate{std::move(wrapped)}
+{
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestProgramStart(testing::UnitTest const& unit_test)
+{
+ delegate->OnTestProgramStart(unit_test);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestIterationStart(testing::UnitTest const& unit_test, int iteration)
+{
+ failed_test_names.clear();
+ skipped_test_names.clear();
+ delegate->OnTestIterationStart(unit_test, iteration);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsSetUpStart(testing::UnitTest const& unit_test)
+{
+ delegate->OnEnvironmentsSetUpStart(unit_test);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsSetUpEnd(testing::UnitTest const& unit_test)
+{
+ delegate->OnEnvironmentsSetUpEnd(unit_test);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestCaseStart(testing::TestCase const& test_case)
+{
+ delegate->OnTestCaseStart(test_case);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestStart(testing::TestInfo const& test_info)
+{
+ current_test_info = &test_info;
+ current_test_start = std::chrono::steady_clock::now();
+ delegate->OnTestStart(test_info);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestPartResult(testing::TestPartResult const& test_part_result)
+{
+ if (test_part_result.failed())
+ {
+ // Search for expected-failure property
+ for (int i= 0; i < current_test_info->result()->test_property_count() ; ++i)
+ {
+ auto prop = current_test_info->result()->GetTestProperty(i);
+ if (strcmp(prop.key(), "wlcs-skip-test") == 0)
+ {
+ if (!current_skip_reasons)
+ {
+ current_skip_reasons = std::vector<std::string>{};
+ }
+ current_skip_reasons->push_back(prop.value());
+ }
+ }
+ if (current_skip_reasons)
+ {
+ skipped_test_names.insert(std::string{current_test_info->test_case_name()} + "." + current_test_info->name());
+ return;
+ }
+
+ failed_test_names.insert(std::string{current_test_info->test_case_name()} + "." + current_test_info->name());
+ }
+ delegate->OnTestPartResult(test_part_result);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestEnd(testing::TestInfo const& test_info)
+{
+ if (current_skip_reasons)
+ {
+ for (auto const& reason : *current_skip_reasons)
+ {
+ std::cout
+ << termcolor::yellow << "[ ]"
+ << termcolor::reset << " " << reason << std::endl;
+ }
+ auto elapsed_time = std::chrono::steady_clock::now() - current_test_start;
+
+ std::cout
+ << termcolor::yellow << "[ SKIP ]"
+ << termcolor::reset << " "
+ << test_info.test_case_name() << "." << test_info.name()
+ << " ("
+ << std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time).count()
+ << "ms)" << std::endl;
+ }
+ else
+ {
+ delegate->OnTestEnd(test_info);
+ }
+ current_skip_reasons = {};
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestCaseEnd(testing::TestCase const& test_case)
+{
+ delegate->OnTestCaseEnd(test_case);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsTearDownStart(testing::UnitTest const& unit_test)
+{
+ delegate->OnEnvironmentsTearDownStart(unit_test);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsTearDownEnd(testing::UnitTest const& unit_test)
+{
+ delegate->OnEnvironmentsTearDownEnd(unit_test);
+}
+
+namespace
+{
+std::string singular_or_plural(char const* base, int count)
+{
+ if (count == 1)
+ {
+ return base;
+ }
+ return std::string{base} + "s";
+}
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestIterationEnd(testing::UnitTest const& unit_test, int /*iteration*/)
+{
+ std::cout
+ << termcolor::green << "[==========] "
+ << termcolor::reset
+ << unit_test.test_to_run_count()
+ << " tests from "
+ << unit_test.test_case_to_run_count()
+ << " test cases run. ("
+ << unit_test.elapsed_time()
+ << "ms total elapsed)" << std::endl;
+
+ std::cout
+ << termcolor::green << "[ PASSED ]"
+ << termcolor::reset
+ << " "
+ << unit_test.successful_test_count()
+ << singular_or_plural(" test", unit_test.successful_test_count()) << std::endl;
+
+ auto const skipped_tests = skipped_test_names.size();
+ if (skipped_tests > 0)
+ {
+ std::cout
+ << termcolor::yellow << "[ SKIPPED ] "
+ << termcolor::reset
+ << skipped_tests
+ << singular_or_plural(" test", skipped_tests)
+ << " skipped:" << std::endl;
+ for (auto const& name : skipped_test_names)
+ {
+ std::cout
+ << termcolor::yellow << "[ SKIPPED ] "
+ << termcolor::reset << name << std::endl;
+ }
+ }
+ auto const failed_tests = failed_test_names.size();
+ if (failed_tests > 0)
+ {
+ /* Mark the test run as a failure; if multiple iterations are run
+ * only one might fail, making failed_test_names() an unreliable
+ * indicator.
+ */
+ failed_ = true;
+
+ std::cout
+ << termcolor::red << "[ FAILED ] "
+ << termcolor::reset
+ << failed_tests
+ << singular_or_plural(" test", failed_tests)
+ << " failed:" << std::endl;
+ for (auto const& name : failed_test_names)
+ {
+ std::cout
+ << termcolor::red << "[ FAILED ] "
+ << termcolor::reset << name << std::endl;
+ }
+ }
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestProgramEnd(testing::UnitTest const& unit_test)
+{
+ delegate->OnTestProgramEnd(unit_test);
+}
+
+bool testing::XFailSupportingTestListenerWrapper::failed() const
+{
+ return failed_;
+}
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_
+#define WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_
+
+#include <gtest/gtest.h>
+
+#include <experimental/optional>
+#include <vector>
+#include <string>
+#include <chrono>
+#include <unordered_set>
+
+namespace testing
+{
+class XFailSupportingTestListenerWrapper : public testing::TestEventListener
+{
+public:
+ explicit XFailSupportingTestListenerWrapper(std::unique_ptr<testing::TestEventListener>&& wrapped);
+
+ void OnTestProgramStart(testing::UnitTest const& unit_test) override;
+
+ void OnTestIterationStart(testing::UnitTest const& unit_test, int iteration) override;
+
+ void OnEnvironmentsSetUpStart(testing::UnitTest const& unit_test) override;
+
+ void OnEnvironmentsSetUpEnd(testing::UnitTest const& unit_test) override;
+
+ void OnTestCaseStart(testing::TestCase const& test_case) override;
+
+ void OnTestStart(testing::TestInfo const& test_info) override;
+
+ void OnTestPartResult(testing::TestPartResult const& test_part_result) override;
+
+ void OnTestEnd(testing::TestInfo const& test_info) override;
+ void OnTestCaseEnd(testing::TestCase const& test_case) override;
+
+ void OnEnvironmentsTearDownStart(testing::UnitTest const& unit_test) override;
+
+ void OnEnvironmentsTearDownEnd(testing::UnitTest const& unit_test) override;
+
+ void OnTestIterationEnd(testing::UnitTest const& unit_test, int iteration) override;
+
+ void OnTestProgramEnd(testing::UnitTest const& unit_test) override;
+
+ bool failed() const;
+private:
+ std::unique_ptr<testing::TestEventListener> const delegate;
+
+ std::chrono::steady_clock::time_point current_test_start;
+ ::testing::TestInfo const* current_test_info;
+ std::experimental::optional<std::vector<std::string>> current_skip_reasons;
+
+ std::unordered_set<std::string> failed_test_names;
+ std::unordered_set<std::string> skipped_test_names;
+
+ bool failed_{false};
+};
+
+}
+
+#endif //WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "data_device.h"
+#include "helpers.h"
+#include "in_process_server.h"
+#include "wl_handle.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+using namespace testing;
+using namespace wlcs;
+
+namespace
+{
+
+auto static const any_width = 100;
+auto static const any_height = 100;
+auto static const any_mime_type = "AnyMimeType";
+
+struct CCnPSource : Client
+{
+ // Can't use "using Client::Client;" because Xenial
+ CCnPSource(Server& server) : Client{server} {}
+
+ Surface const surface{create_visible_surface(any_width, any_height)};
+ WlHandle<wl_data_device_manager> const manager{
+ this->bind_if_supported<wl_data_device_manager>(AnyVersion)};
+ DataDevice data_device{wl_data_device_manager_get_data_device(manager,seat())};
+ DataSource data{wl_data_device_manager_create_data_source(manager)};
+
+ void offer(char const* mime_type)
+ {
+ wl_data_source_offer(data, mime_type);
+ // TODO: collect a serial from the "event that triggered" the selection
+ // (This works while Mir fails to validate the serial)
+ uint32_t const serial = 0;
+ wl_data_device_set_selection(data_device, data, serial);
+ roundtrip();
+ }
+};
+
+struct MockDataDeviceListener : DataDeviceListener
+{
+ using DataDeviceListener::DataDeviceListener;
+
+ MOCK_METHOD2(data_offer, void(struct wl_data_device* wl_data_device, struct wl_data_offer* id));
+};
+
+struct MockDataOfferListener : DataOfferListener
+{
+ using DataOfferListener::DataOfferListener;
+
+ MOCK_METHOD2(offer, void(struct wl_data_offer* data_offer, char const* MimeType));
+};
+
+struct CCnPSink : Client
+{
+ // Can't use "using Client::Client;" because Xenial
+ CCnPSink(Server& server) : Client{server} {}
+
+ WlHandle<wl_data_device_manager> const manager{
+ this->bind_if_supported<wl_data_device_manager>(AnyVersion)};
+ DataDevice sink_data{wl_data_device_manager_get_data_device(manager, seat())};
+ MockDataDeviceListener listener{sink_data};
+
+ Surface create_surface_with_focus()
+ {
+ return Surface{create_visible_surface(any_width, any_height)};
+ }
+};
+
+struct CopyCutPaste : StartedInProcessServer
+{
+ CCnPSource source{the_server()};
+ CCnPSink sink{the_server()};
+
+ MockDataOfferListener mdol;
+
+ void TearDown() override
+ {
+ source.roundtrip();
+ sink.roundtrip();
+ StartedInProcessServer::TearDown();
+ }
+};
+}
+
+TEST_F(CopyCutPaste, given_source_has_offered_when_sink_gets_focus_it_sees_offer)
+{
+ source.offer(any_mime_type);
+
+ EXPECT_CALL(mdol, offer(_, StrEq(any_mime_type)));
+ EXPECT_CALL(sink.listener, data_offer(_,_))
+ .WillOnce(Invoke([&](struct wl_data_device*, struct wl_data_offer* id)
+ { mdol.listen_to(id); }));
+
+ sink.create_surface_with_focus();
+}
+
+TEST_F(CopyCutPaste, given_sink_has_focus_when_source_makes_offer_sink_sees_offer)
+{
+ auto sink_surface_with_focus = sink.create_surface_with_focus();
+
+ EXPECT_CALL(mdol, offer(_, StrEq(any_mime_type)));
+ EXPECT_CALL(sink.listener, data_offer(_,_))
+ .WillOnce(Invoke([&](struct wl_data_device*, struct wl_data_offer* id)
+ { mdol.listen_to(id); }));
+
+ source.offer(any_mime_type);
+}
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using namespace wlcs;
+
+namespace
+{
+
+struct FrameSubmission : StartedInProcessServer
+{
+ Client client{the_server()};
+ Surface surface{client};
+
+ void submit_frame(bool& frame_consumed);
+
+ void wait_for_frame(bool const& frame_consumed);
+};
+
+void FrameSubmission::submit_frame(bool& consumed_flag)
+{
+ static wl_callback_listener const frame_listener
+ {
+ [](void *data, struct wl_callback* callback, uint32_t /*callback_data*/)
+ {
+ *static_cast<bool*>(data) = true;
+ wl_callback_destroy(callback);
+ }
+ };
+
+ consumed_flag = false;
+ wl_callback_add_listener(wl_surface_frame(surface), &frame_listener, &consumed_flag);
+ wl_surface_damage(surface, 0, 0, 200, 200);
+ wl_surface_commit(surface);
+}
+
+void FrameSubmission::wait_for_frame(bool const& consumed_flag)
+{
+ // TODO timeout
+ client.dispatch_until([&consumed_flag]() { return consumed_flag; });
+}
+}
+
+TEST_F(FrameSubmission, post_one_frame_at_a_time)
+{
+ ShmBuffer buffer{client, 200, 200};
+ wl_surface_attach(surface, buffer, 0, 0);
+
+ auto shell_surface = wl_shell_get_shell_surface(client.shell(), surface);
+ wl_shell_surface_set_toplevel(shell_surface);
+
+ for (auto i = 0; i != 10; ++i)
+ {
+ auto frame_consumed = false;
+
+ submit_frame(frame_consumed);
+ wait_for_frame(frame_consumed);
+
+ EXPECT_THAT(frame_consumed, Eq(true));
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "gtk_primary_selection.h"
+
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+#include <sys/socket.h>
+
+using namespace wlcs;
+using namespace testing;
+
+namespace
+{
+char const any_mime_type[] = "AnyMimeType";
+char const any_mime_data[] = "AnyMimeData";
+
+struct SourceApp : Client
+{
+ // Can't use "using Client::Client;" because Xenial
+ explicit SourceApp(Server& server) : Client{server} {}
+
+ WlHandle<gtk_primary_selection_device_manager> manager{
+ this->bind_if_supported<gtk_primary_selection_device_manager>(AnyVersion)};
+ GtkPrimarySelectionSource source{manager};
+ GtkPrimarySelectionDevice device{manager, seat()};
+
+ void set_selection()
+ {
+ gtk_primary_selection_device_set_selection(device, source, 0);
+ roundtrip();
+ }
+
+ void offer(char const* mime_type)
+ {
+ gtk_primary_selection_source_offer(source, mime_type);
+ roundtrip();
+ }
+};
+
+struct SinkApp : Client
+{
+ explicit SinkApp(Server& server) : Client{server} { roundtrip(); }
+
+ WlHandle<gtk_primary_selection_device_manager> const manager{
+ this->bind_if_supported<gtk_primary_selection_device_manager>(AnyVersion)};
+ GtkPrimarySelectionDevice device{manager, seat()};
+};
+
+struct GtkPrimarySelection : StartedInProcessServer
+{
+ SourceApp source_app{the_server()};
+ SinkApp sink_app{the_server()};
+
+ void TearDown() override
+ {
+ source_app.roundtrip();
+ sink_app.roundtrip();
+ StartedInProcessServer::TearDown();
+ }
+};
+
+struct MockGtkPrimarySelectionDeviceListener : GtkPrimarySelectionDeviceListener
+{
+ using GtkPrimarySelectionDeviceListener::GtkPrimarySelectionDeviceListener;
+
+ MOCK_METHOD2(data_offer, void(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer));
+ MOCK_METHOD2(selection, void(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer));
+};
+
+struct MockGtkPrimarySelectionOfferListener : GtkPrimarySelectionOfferListener
+{
+ using GtkPrimarySelectionOfferListener::GtkPrimarySelectionOfferListener;
+
+ MOCK_METHOD2(offer, void(gtk_primary_selection_offer* offer, const char* mime_type));
+};
+
+struct MockGtkPrimarySelectionSourceListener : GtkPrimarySelectionSourceListener
+{
+ using GtkPrimarySelectionSourceListener::GtkPrimarySelectionSourceListener;
+
+ MOCK_METHOD3(send, void(gtk_primary_selection_source* source, const char* mime_type, int32_t fd));
+
+ MOCK_METHOD1(cancelled, void(gtk_primary_selection_source*));
+};
+
+struct StubGtkPrimarySelectionDeviceListener : GtkPrimarySelectionDeviceListener
+{
+ StubGtkPrimarySelectionDeviceListener(
+ gtk_primary_selection_device* device,
+ GtkPrimarySelectionOfferListener& offer_listener) :
+ GtkPrimarySelectionDeviceListener{device},
+ offer_listener{offer_listener}
+ {
+ }
+
+ void data_offer(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer) override
+ {
+ offer_listener.listen_to(offer);
+ GtkPrimarySelectionDeviceListener::data_offer(device, offer);
+ }
+
+ void selection(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer) override
+ {
+ selected = offer;
+ GtkPrimarySelectionDeviceListener::selection(device, offer);
+ }
+
+ GtkPrimarySelectionOfferListener& offer_listener;
+ gtk_primary_selection_offer* selected = nullptr;
+};
+
+struct StubGtkPrimarySelectionSourceListener : GtkPrimarySelectionSourceListener
+{
+ using GtkPrimarySelectionSourceListener::GtkPrimarySelectionSourceListener;
+
+ void send(gtk_primary_selection_source*, const char*, int32_t fd)
+ {
+ ASSERT_THAT(write(fd, any_mime_data, sizeof any_mime_data), Eq(ssize_t(sizeof any_mime_data)));
+ close(fd);
+ }
+};
+
+struct Pipe
+{
+ int source;
+ int sink;
+
+ Pipe() { socketpair(AF_LOCAL, SOCK_STREAM, 0, &source); }
+ Pipe(Pipe const&) = delete;
+ Pipe& operator=(Pipe const&) = delete;
+
+ ~Pipe() { close(source); close(sink); }
+};
+}
+
+TEST_F(GtkPrimarySelection, source_can_offer)
+{
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+}
+
+TEST_F(GtkPrimarySelection, sink_can_listen)
+{
+ MockGtkPrimarySelectionDeviceListener device_listener{sink_app.device};
+ MockGtkPrimarySelectionOfferListener offer_listener;
+
+ InSequence seq;
+ EXPECT_CALL(device_listener, data_offer(_, _))
+ .WillOnce(Invoke([&](auto*, auto* id) { offer_listener.listen_to(id); }));
+
+ EXPECT_CALL(offer_listener, offer(_, StrEq(any_mime_type)));
+
+ EXPECT_CALL(device_listener, selection(_, _));
+
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+
+ sink_app.roundtrip();
+}
+
+TEST_F(GtkPrimarySelection, sink_can_request)
+{
+ GtkPrimarySelectionOfferListener offer_listener;
+ StubGtkPrimarySelectionDeviceListener device_listener{sink_app.device, offer_listener};
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+ sink_app.roundtrip();
+ ASSERT_THAT(device_listener.selected, NotNull());
+
+ Pipe pipe;
+ gtk_primary_selection_offer_receive(device_listener.selected, any_mime_type, pipe.source);
+ sink_app.roundtrip();
+}
+
+TEST_F(GtkPrimarySelection, source_sees_request)
+{
+ MockGtkPrimarySelectionSourceListener source_listener{source_app.source};
+ GtkPrimarySelectionOfferListener offer_listener;
+ StubGtkPrimarySelectionDeviceListener device_listener{sink_app.device, offer_listener};
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+ sink_app.roundtrip();
+ ASSERT_THAT(device_listener.selected, NotNull());
+
+ EXPECT_CALL(source_listener, send(_, _, _))
+ .Times(1)
+ .WillRepeatedly(Invoke([&](auto*, auto*, int fd) { close(fd); }));
+
+ Pipe pipe;
+ gtk_primary_selection_offer_receive(device_listener.selected, any_mime_type, pipe.source);
+ sink_app.roundtrip();
+ source_app.roundtrip();
+}
+
+TEST_F(GtkPrimarySelection, source_can_supply_request)
+{
+ StubGtkPrimarySelectionSourceListener source_listener{source_app.source};
+ GtkPrimarySelectionOfferListener offer_listener;
+ StubGtkPrimarySelectionDeviceListener device_listener{sink_app.device, offer_listener};
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+ sink_app.roundtrip();
+ ASSERT_THAT(device_listener.selected, NotNull());
+
+ Pipe pipe;
+ gtk_primary_selection_offer_receive(device_listener.selected, any_mime_type, pipe.source);
+ sink_app.roundtrip();
+ source_app.roundtrip();
+
+ char buffer[128];
+ EXPECT_THAT(read(pipe.sink, buffer, sizeof buffer), Eq(ssize_t(sizeof any_mime_data)));
+ EXPECT_THAT(buffer, StrEq(any_mime_data));
+}
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "pointer_constraints_unstable_v1.h"
+#include "helpers.h"
+#include "in_process_server.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+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};
+
+ std::unique_ptr<ZwpLockedPointerV1> locked_ptr = nullptr;
+ std::unique_ptr<ZwpConfinedPointerV1> confined_ptr = nullptr;
+
+ 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
+ {
+ locked_ptr.reset();
+ confined_ptr.reset();
+ client.roundtrip();
+ StartedInProcessServer::TearDown();
+ }
+
+ void setup_locked_ptr_on(Surface& surface, zwp_pointer_constraints_v1_lifetime lifetime = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT)
+ {
+ locked_ptr = std::make_unique<ZwpLockedPointerV1>(pointer_constraints, surface, pointer, nullptr, lifetime);
+ }
+
+ void setup_confined_ptr_on(Surface& surface, zwp_pointer_constraints_v1_lifetime lifetime = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT)
+ {
+ confined_ptr = std::make_unique<ZwpConfinedPointerV1>(pointer_constraints, surface, pointer, nullptr, lifetime);
+ }
+
+ void setup_sync()
+ {
+ client.roundtrip();
+ using namespace testing;
+ if (locked_ptr) Mock::VerifyAndClearExpectations(locked_ptr.get());
+ if (confined_ptr) Mock::VerifyAndClearExpectations(confined_ptr.get());
+ }
+
+ void select_se_window()
+ {
+ cursor.move_to(se_middle_x, se_middle_y);
+ cursor.left_click();
+ client.roundtrip();
+ }
+
+ void select_nw_window()
+ {
+ cursor.move_to(nw_middle_x, nw_middle_y);
+ cursor.left_click();
+ client.roundtrip();
+ }
+};
+}
+
+TEST_F(PointerConstraints, can_get_locked_pointer)
+{
+ setup_locked_ptr_on(nw_surface);
+
+ EXPECT_THAT(*locked_ptr, NotNull());
+}
+
+TEST_F(PointerConstraints, locked_pointer_on_initially_focussed_surface_gets_locked_notification)
+{
+ setup_locked_ptr_on(nw_surface);
+
+ EXPECT_CALL(*locked_ptr, locked()).Times(1);
+
+ client.roundtrip();
+}
+
+TEST_F(PointerConstraints, locked_pointer_does_not_move)
+{
+ auto const initial_pointer_position = client.pointer_position();
+ setup_locked_ptr_on(nw_surface);
+ EXPECT_CALL(*locked_ptr, locked()).Times(AnyNumber());
+ setup_sync();
+
+ cursor.move_by(10, 10);
+ client.roundtrip();
+
+ EXPECT_THAT(client.pointer_position(), Eq(initial_pointer_position));
+}
+
+TEST_F(PointerConstraints, locked_pointer_on_initially_unfocussed_surface_gets_no_locked_notification)
+{
+ setup_locked_ptr_on(se_surface);
+
+ EXPECT_CALL(*locked_ptr, locked()).Times(0);
+
+ client.roundtrip();
+}
+
+TEST_F(PointerConstraints, when_surface_is_selected_locked_pointer_gets_locked_notification)
+{
+ setup_locked_ptr_on(se_surface);
+ setup_sync();
+
+ EXPECT_CALL(*locked_ptr, locked()).Times(1);
+
+ select_se_window();
+}
+
+TEST_F(PointerConstraints, when_surface_is_unselected_locked_pointer_gets_unlocked_notification)
+{
+ setup_locked_ptr_on(nw_surface);
+ EXPECT_CALL(*locked_ptr, locked()).Times(AnyNumber());
+ setup_sync();
+
+ EXPECT_CALL(*locked_ptr, unlocked()).Times(1);
+
+ // A new surface will be given focus
+ Surface a_new_surface{client.create_visible_surface(any_width, any_height)};
+ client.roundtrip();
+}
+
+TEST_F(PointerConstraints, when_surface_is_reselected_persistent_locked_pointer_gets_notifications)
+{
+ setup_locked_ptr_on(nw_surface, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ EXPECT_CALL(*locked_ptr, locked()).Times(AnyNumber());
+ setup_sync();
+
+ for (auto i = 3; i-- != 0;)
+ {
+ {
+ EXPECT_CALL(*locked_ptr, unlocked()).Times(1);
+ // A new surface will be given focus
+ Surface a_new_surface{client.create_visible_surface(any_width, any_height)};
+ setup_sync();
+
+ EXPECT_CALL(*locked_ptr, locked()).Times(1);
+ }
+ client.roundtrip();
+ }
+}
+
+TEST_F(PointerConstraints, can_get_confined_pointer)
+{
+ setup_confined_ptr_on(nw_surface);
+ EXPECT_THAT(*confined_ptr, NotNull());
+}
+
+TEST_F(PointerConstraints, confined_pointer_on_initially_focussed_surface_gets_confined_notification)
+{
+ setup_confined_ptr_on(nw_surface);
+
+ EXPECT_CALL(*confined_ptr, confined()).Times(1);
+
+ client.roundtrip();
+}
+
+TEST_F(PointerConstraints, confined_pointer_does_move)
+{
+ auto const initial_pointer_position = client.pointer_position();
+ setup_confined_ptr_on(nw_surface);
+ EXPECT_CALL(*confined_ptr, confined()).Times(AnyNumber());
+ setup_sync();
+
+ cursor.move_by(10, 10);
+ client.roundtrip();
+
+ EXPECT_THAT(client.pointer_position(), Ne(initial_pointer_position));
+}
+
+TEST_F(PointerConstraints, confined_pointer_movement_is_constrained)
+{
+ auto const top = wl_fixed_from_int(0);
+ auto const left = wl_fixed_from_int(0);
+ auto const bottom = wl_fixed_from_int(any_height-1);
+ auto const right = wl_fixed_from_int(any_width-1);
+
+ setup_confined_ptr_on(nw_surface);
+ EXPECT_CALL(*confined_ptr, confined()).Times(AnyNumber());
+ setup_sync();
+
+ cursor.move_by(2*any_width, 2*any_height);
+ client.roundtrip();
+
+ EXPECT_THAT(client.pointer_position(), Eq(std::make_pair(bottom, right)));
+
+ cursor.move_by(-2*any_width, -2*any_height);
+ client.roundtrip();
+
+ EXPECT_THAT(client.pointer_position(), Eq(std::make_pair(top, left)));
+
+ cursor.move_by(-2*any_width, 2*any_height);
+ client.roundtrip();
+
+ EXPECT_THAT(client.pointer_position(), Eq(std::make_pair(top, right)));
+
+ cursor.move_by(2*any_width, -2*any_height);
+ client.roundtrip();
+
+ EXPECT_THAT(client.pointer_position(), Eq(std::make_pair(bottom, left)));
+}
+
+TEST_F(PointerConstraints, confined_pointer_on_initially_unfocussed_surface_gets_no_confined_notification)
+{
+ setup_confined_ptr_on(se_surface);
+
+ EXPECT_CALL(*confined_ptr, confined()).Times(0);
+
+ client.roundtrip();
+}
+
+TEST_F(PointerConstraints, when_surface_is_selected_confined_pointer_gets_confined_notification)
+{
+ setup_confined_ptr_on(se_surface);
+ setup_sync();
+
+ EXPECT_CALL(*confined_ptr, confined()).Times(1);
+
+ select_se_window();
+}
+
+TEST_F(PointerConstraints, when_surface_is_unselected_confined_pointer_gets_unconfined_notification)
+{
+ setup_confined_ptr_on(nw_surface);
+ EXPECT_CALL(*confined_ptr, confined()).Times(AnyNumber());
+ setup_sync();
+
+ EXPECT_CALL(*confined_ptr, unconfined()).Times(1);
+
+ // A new surface will be given focus
+ Surface a_new_surface{client.create_visible_surface(any_width, any_height)};
+ client.roundtrip();
+}
+
+TEST_F(PointerConstraints, when_surface_is_reselected_persistent_confined_pointer_gets_notifications)
+{
+ setup_confined_ptr_on(nw_surface, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ EXPECT_CALL(*confined_ptr, confined()).Times(AnyNumber());
+ setup_sync();
+
+ for (auto i = 3; i-- != 0;)
+ {
+ {
+ EXPECT_CALL(*confined_ptr, unconfined()).Times(1);
+ // A new surface will be given focus
+ Surface a_new_surface{client.create_visible_surface(any_width, any_height)};
+ setup_sync();
+
+ EXPECT_CALL(*confined_ptr, confined()).Times(1);
+ }
+ client.roundtrip();
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "primary_selection.h"
+
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+#include <sys/socket.h>
+
+using namespace wlcs;
+using namespace testing;
+
+namespace
+{
+char const any_mime_type[] = "AnyMimeType";
+char const any_mime_data[] = "AnyMimeData";
+
+struct SourceApp : Client
+{
+ // Can't use "using Client::Client;" because Xenial
+ explicit SourceApp(Server& server) : Client{server} {}
+
+ WlHandle<zwp_primary_selection_device_manager_v1> const manager{
+ this->bind_if_supported<zwp_primary_selection_device_manager_v1>(AnyVersion)};
+ PrimarySelectionSource source{manager};
+ PrimarySelectionDevice device{manager, seat()};
+
+ void set_selection()
+ {
+ zwp_primary_selection_device_v1_set_selection(device, source, 0);
+ roundtrip();
+ }
+
+ void offer(char const* mime_type)
+ {
+ zwp_primary_selection_source_v1_offer(source, mime_type);
+ roundtrip();
+ }
+};
+
+struct SinkApp : Client
+{
+ explicit SinkApp(Server& server) : Client{server} { roundtrip(); }
+
+ WlHandle<zwp_primary_selection_device_manager_v1> const manager{
+ this->bind_if_supported<zwp_primary_selection_device_manager_v1>(AnyVersion)};
+ PrimarySelectionDevice device{manager, seat()};
+};
+
+struct PrimarySelection : StartedInProcessServer
+{
+ SourceApp source_app{the_server()};
+ SinkApp sink_app{the_server()};
+
+ void TearDown() override
+ {
+ source_app.roundtrip();
+ sink_app.roundtrip();
+ StartedInProcessServer::TearDown();
+ }
+};
+
+struct MockPrimarySelectionDeviceListener : PrimarySelectionDeviceListener
+{
+ using PrimarySelectionDeviceListener::PrimarySelectionDeviceListener;
+
+ MOCK_METHOD2(data_offer, void(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer));
+ MOCK_METHOD2(selection, void(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer));
+};
+
+struct MockPrimarySelectionOfferListener : PrimarySelectionOfferListener
+{
+ using PrimarySelectionOfferListener::PrimarySelectionOfferListener;
+
+ MOCK_METHOD2(offer, void(zwp_primary_selection_offer_v1* offer, const char* mime_type));
+};
+
+struct MockPrimarySelectionSourceListener : PrimarySelectionSourceListener
+{
+ using PrimarySelectionSourceListener::PrimarySelectionSourceListener;
+
+ MOCK_METHOD3(send, void(zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd));
+
+ MOCK_METHOD1(cancelled, void(zwp_primary_selection_source_v1*));
+};
+
+struct StubPrimarySelectionDeviceListener : PrimarySelectionDeviceListener
+{
+ StubPrimarySelectionDeviceListener(
+ zwp_primary_selection_device_v1* device,
+ PrimarySelectionOfferListener& offer_listener) :
+ PrimarySelectionDeviceListener{device},
+ offer_listener{offer_listener}
+ {
+ }
+
+ void data_offer(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer) override
+ {
+ offer_listener.listen_to(offer);
+ PrimarySelectionDeviceListener::data_offer(device, offer);
+ }
+
+ void selection(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer) override
+ {
+ selected = offer;
+ PrimarySelectionDeviceListener::selection(device, offer);
+ }
+
+ PrimarySelectionOfferListener& offer_listener;
+ zwp_primary_selection_offer_v1* selected = nullptr;
+};
+
+struct StubPrimarySelectionSourceListener : PrimarySelectionSourceListener
+{
+ using PrimarySelectionSourceListener::PrimarySelectionSourceListener;
+
+ void send(zwp_primary_selection_source_v1*, const char*, int32_t fd)
+ {
+ ASSERT_THAT(write(fd, any_mime_data, sizeof any_mime_data), Eq(ssize_t(sizeof any_mime_data)));
+ close(fd);
+ }
+};
+
+struct Pipe
+{
+ int source;
+ int sink;
+
+ Pipe() { socketpair(AF_LOCAL, SOCK_STREAM, 0, &source); }
+ Pipe(Pipe const&) = delete;
+ Pipe& operator=(Pipe const&) = delete;
+
+ ~Pipe() { close(source); close(sink); }
+};
+}
+
+TEST_F(PrimarySelection, source_can_offer)
+{
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+}
+
+TEST_F(PrimarySelection, sink_can_listen)
+{
+ MockPrimarySelectionDeviceListener device_listener{sink_app.device};
+ MockPrimarySelectionOfferListener offer_listener;
+
+ InSequence seq;
+ EXPECT_CALL(device_listener, data_offer(_, _))
+ .WillOnce(Invoke([&](auto*, auto* id) { offer_listener.listen_to(id); }));
+
+ EXPECT_CALL(offer_listener, offer(_, StrEq(any_mime_type)));
+
+ EXPECT_CALL(device_listener, selection(_, _));
+
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+
+ sink_app.roundtrip();
+}
+
+TEST_F(PrimarySelection, sink_can_request)
+{
+ PrimarySelectionOfferListener offer_listener;
+ StubPrimarySelectionDeviceListener device_listener{sink_app.device, offer_listener};
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+ sink_app.roundtrip();
+ ASSERT_THAT(device_listener.selected, NotNull());
+
+ Pipe pipe;
+ zwp_primary_selection_offer_v1_receive(device_listener.selected, any_mime_type, pipe.source);
+ sink_app.roundtrip();
+}
+
+TEST_F(PrimarySelection, source_sees_request)
+{
+ MockPrimarySelectionSourceListener source_listener{source_app.source};
+ PrimarySelectionOfferListener offer_listener;
+ StubPrimarySelectionDeviceListener device_listener{sink_app.device, offer_listener};
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+ sink_app.roundtrip();
+ ASSERT_THAT(device_listener.selected, NotNull());
+
+ EXPECT_CALL(source_listener, send(_, _, _))
+ .Times(1)
+ .WillRepeatedly(Invoke([&](auto*, auto*, int fd) { close(fd); }));
+
+ Pipe pipe;
+ zwp_primary_selection_offer_v1_receive(device_listener.selected, any_mime_type, pipe.source);
+ sink_app.roundtrip();
+ source_app.roundtrip();
+}
+
+TEST_F(PrimarySelection, source_can_supply_request)
+{
+ StubPrimarySelectionSourceListener source_listener{source_app.source};
+ PrimarySelectionOfferListener offer_listener;
+ StubPrimarySelectionDeviceListener device_listener{sink_app.device, offer_listener};
+ source_app.offer(any_mime_type);
+ source_app.set_selection();
+ sink_app.roundtrip();
+ ASSERT_THAT(device_listener.selected, NotNull());
+
+ Pipe pipe;
+ zwp_primary_selection_offer_v1_receive(device_listener.selected, any_mime_type, pipe.source);
+ sink_app.roundtrip();
+ source_app.roundtrip();
+
+ char buffer[128];
+ EXPECT_THAT(read(pipe.sink, buffer, sizeof buffer), Eq(ssize_t(sizeof any_mime_data)));
+ EXPECT_THAT(buffer, StrEq(any_mime_data));
+}
--- /dev/null
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "relative_pointer_unstable_v1.h"
+#include "helpers.h"
+#include "in_process_server.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using namespace wlcs;
+
+namespace
+{
+auto const any_width = 300;
+auto const any_height = 300;
+auto const nw_middle_x = any_width / 2;
+auto const nw_middle_y = any_height / 2;
+
+struct RelativePointer : StartedInProcessServer
+{
+ // Create a client with a surface and a cursor centered on the surface
+ Client a_client{the_server()};
+ Surface a_surface{a_client.create_visible_surface(any_width, any_height)};
+ Pointer cursor = the_server().create_pointer();
+
+ ZwpRelativePointerManagerV1 manager{a_client};
+ ZwpRelativePointerV1 pointer{manager, a_client.the_pointer()};
+
+ void SetUp() override
+ {
+ StartedInProcessServer::SetUp();
+
+ // Get the surface in a known position with the cursor over it
+ the_server().move_surface_to(a_surface, 0, 0);
+ cursor.move_to(nw_middle_x, nw_middle_y);
+ }
+
+ void TearDown() override
+ {
+ a_client.roundtrip();
+ StartedInProcessServer::TearDown();
+ }
+};
+}
+
+TEST_F(RelativePointer, can_get_relative_pointer)
+{
+ EXPECT_THAT(pointer, NotNull());
+}
+
+TEST_F(RelativePointer, relative_pointer_gets_movement)
+{
+ auto const move_x = any_width/6;
+ auto const move_y = any_height/6;
+
+ EXPECT_CALL(pointer, relative_motion(_, _,
+ wl_fixed_from_int(move_x), wl_fixed_from_int(move_y),
+ wl_fixed_from_int(move_x), wl_fixed_from_int(move_y))).Times(1);
+
+ cursor.move_by(move_x, move_y);
+}
+
+// #1959
+TEST_F(RelativePointer, default_pointer_still_gets_movement)
+{
+ auto const move_x = any_width/6;
+ auto const move_y = any_height/6;
+ EXPECT_CALL(pointer, relative_motion(_, _, _, _, _, _)).Times(AnyNumber());
+
+ bool moved = false;
+ a_client.add_pointer_motion_notification([&](auto...) { moved = true; return true; });
+
+ cursor.move_by(move_x, move_y);
+
+ a_client.roundtrip();
+ EXPECT_THAT(moved, IsTrue());
+}
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "data_device.h"
+#include "helpers.h"
+#include "gtest_helpers.h"
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+using namespace testing;
+using namespace wlcs;
+
+namespace
+{
+
+struct SelfTest : StartedInProcessServer
+{
+ Client client1{the_server()};
+};
+
+auto static const any_width = 100;
+auto static const any_height = 100;
+}
+
+TEST_F(SelfTest, when_creating_second_client_nothing_bad_happens)
+{
+ Client client2{the_server()};
+}
+
+TEST_F(SelfTest, given_second_client_when_roundtripping_first_client_nothing_bad_happens)
+{
+ Client client2{the_server()};
+ client1.roundtrip();
+}
+
+TEST_F(SelfTest, given_second_client_when_roundtripping_both_clients_nothing_bad_happens)
+{
+ Client client2{the_server()};
+
+ for (auto i = 0; i != 10; ++i)
+ {
+ client1.roundtrip();
+ client2.roundtrip();
+ }
+}
+
+TEST_F(SelfTest, when_a_client_creates_a_surface_nothing_bad_happens)
+{
+ Surface const surface1{client1.create_visible_surface(any_width, any_height)};
+ client1.roundtrip();
+}
+
+TEST_F(SelfTest, given_second_client_when_first_creates_a_surface_nothing_bad_happens)
+{
+ Client client2{the_server()};
+
+ Surface const surface1{client1.create_visible_surface(any_width, any_height)};
+
+ for (auto i = 0; i != 10; ++i)
+ {
+ client1.roundtrip();
+ client2.roundtrip();
+ }
+}
+
+TEST_F(SelfTest, given_second_client_when_both_create_a_surface_nothing_bad_happens)
+{
+ Client client2{the_server()};
+
+ Surface const surface1{client1.create_visible_surface(any_width, any_height)};
+
+ Surface const surface2{client2.create_visible_surface(any_width, any_height)};
+
+ for (auto i = 0; i != 10; ++i)
+ {
+ client1.roundtrip();
+ client2.roundtrip();
+ }
+}
+
+TEST_F(SelfTest, xfail_failure_is_noted)
+{
+ ::testing::Test::RecordProperty("wlcs-skip-test", "Reason goes here");
+
+ FAIL() << "This message shouldn't be seen";
+}
+
+TEST_F(SelfTest, expected_missing_extension_is_xfail)
+{
+ throw wlcs::ExtensionExpectedlyNotSupported("xdg_not_really_an_extension", wlcs::AtLeastVersion{1});
+}
+
+TEST_F(SelfTest, acquiring_unsupported_extension_is_xfail)
+{
+ auto const extension_list = the_server().supported_extensions();
+
+ if (!extension_list)
+ {
+ ::testing::Test::RecordProperty("wlcs-skip-test", "Compositor Integration module is too old for expected extension failures");
+ FAIL() << "Requires unsupported feature from module under test";
+ }
+
+ Client client{the_server()};
+
+ wl_interface unsupported_interface = wl_shell_interface;
+ unsupported_interface.name = "wlcs_non_existent_extension";
+ client.bind_if_supported(unsupported_interface, AnyVersion);
+
+ FAIL() << "We should have (x)failed at acquiring the interface";
+}
+
+TEST_F(SelfTest, acquiring_unsupported_extension_version_is_xfail)
+{
+ auto const extension_list = the_server().supported_extensions();
+
+ if (!extension_list)
+ {
+ ::testing::Test::RecordProperty("wlcs-skip-test", "Compositor Integration module is too old for expected extension failures");
+ FAIL() << "Requires unsupported feature from module under test";
+ }
+
+ Client client{the_server()};
+
+ client.bind_if_supported(wl_shell_interface, AtLeastVersion{wl_shell_interface.version + 1u});
+
+ FAIL() << "We should have (x)failed at acquiring the interface";
+}
+
+TEST_F(SelfTest, dispatch_until_times_out_on_failure)
+{
+ Client client{the_server()};
+
+ // Ensure that there's some events happening on the Wayland socket
+ auto dummy = client.create_visible_surface(300, 300);
+ dummy.attach_buffer(300, 300);
+ wl_surface_commit(dummy);
+
+ try
+ {
+ client.dispatch_until([]() { return false; }, std::chrono::seconds{1});
+ }
+ catch (wlcs::Timeout const&)
+ {
+ return;
+ }
+ FAIL() << "Dispatch did not raise a wlcs::Timeout exception";
+}
+
+TEST_F(SelfTest, dispatch_until_times_out_at_the_right_time)
+{
+ using namespace std::literals::chrono_literals;
+
+ Client client{the_server()};
+
+ auto const timeout = 5s;
+ auto const expected_end = std::chrono::steady_clock::now() + timeout;
+ try
+ {
+ client.dispatch_until([]() { return false; }, timeout);
+ }
+ catch (wlcs::Timeout const&)
+ {
+ EXPECT_THAT(std::chrono::steady_clock::now(), Gt(expected_end));
+ EXPECT_THAT(std::chrono::steady_clock::now(), Lt(expected_end + 5s));
+ return;
+ }
+ FAIL() << "Dispatch did not raise a wlcs::Timeout exception";
+}
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_v6.h"
+
+#include <gmock/gmock.h>
+
+#include <vector>
+
+using namespace testing;
+
+struct AbstractInputDevice
+{
+ virtual void to_screen_position(int x, int y) = 0;
+ virtual wl_surface* focused_window() = 0;
+ virtual std::pair<int, int> position_on_window() = 0;
+ virtual ~AbstractInputDevice() = default;
+};
+
+struct PointerInputDevice : AbstractInputDevice
+{
+ PointerInputDevice(wlcs::Server& server, wlcs::Client& client):
+ client{client},
+ pointer{server.create_pointer()}
+ {
+ }
+
+ void to_screen_position(int x, int y) override
+ {
+ pointer.move_to(0, 0);
+ pointer.move_to(x, y);
+ }
+
+ wl_surface* focused_window() override
+ {
+ return client.window_under_cursor();
+ }
+
+ std::pair<wl_fixed_t, wl_fixed_t> position_on_window() override
+ {
+ return client.pointer_position();
+ }
+
+ wlcs::Client& client;
+ wlcs::Pointer pointer;
+};
+
+struct TouchInputDevice : AbstractInputDevice
+{
+ TouchInputDevice(wlcs::Server& server, wlcs::Client& client):
+ client{client},
+ touch{server.create_touch()}
+ {
+ }
+
+ void to_screen_position(int x, int y) override
+ {
+ touch.up();
+ touch.down_at(x, y);
+ }
+
+ wl_surface* focused_window() override
+ {
+ return client.touched_window();
+ }
+
+ std::pair<wl_fixed_t, wl_fixed_t> position_on_window() override
+ {
+ return client.touch_position();
+ }
+
+ wlcs::Client& client;
+ wlcs::Touch touch;
+};
+
+struct SubsurfaceTestParams
+{
+ std::string name;
+ std::function<std::unique_ptr<wlcs::Surface>(wlcs::InProcessServer& server,
+ wlcs::Client& client,
+ int x, int y,
+ int width, int height)> make_surface;
+ std::function<std::unique_ptr<AbstractInputDevice>(wlcs::Server& server,
+ wlcs::Client& client)> make_input_device;
+};
+
+std::ostream& operator<<(std::ostream& out, SubsurfaceTestParams const& param)
+{
+ return out << param.name;
+}
+
+class SubsurfaceTest :
+ public wlcs::StartedInProcessServer,
+ public testing::WithParamInterface<SubsurfaceTestParams>
+{
+public:
+ static int const surface_width = 200, surface_height = 300;
+ static int const subsurface_width = 50, subsurface_height = 50;
+ static int const surface_x = 20, surface_y = 30;
+
+ SubsurfaceTest():
+ client{the_server()},
+ main_surface{std::move(*GetParam().make_surface(
+ *this, client, surface_x, surface_y, surface_width, surface_height))},
+ subsurface{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)},
+ input_device{GetParam().make_input_device(the_server(), client)}
+ {
+ client.roundtrip();
+ }
+
+ void move_subsurface_to(int x, int y)
+ {
+ wl_subsurface_set_position(subsurface, x, y);
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+ }
+
+ wlcs::Client client{the_server()};
+ wlcs::Surface main_surface;
+ wlcs::Subsurface subsurface;
+ std::unique_ptr<AbstractInputDevice> input_device;
+};
+
+class SubsurfaceMultilevelTest :
+ public wlcs::StartedInProcessServer,
+ public testing::WithParamInterface<SubsurfaceTestParams>
+{
+public:
+ static int const surface_width = 200, surface_height = 300;
+ static int const subsurface_width = 50, subsurface_height = 50;
+ static int const surface_x = 20, surface_y = 30;
+
+ SubsurfaceMultilevelTest():
+ client{the_server()},
+ main_surface{std::move(*GetParam().make_surface(
+ *this, client, surface_x, surface_y, surface_width, surface_height))},
+ parent_subsurface{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)},
+ child_subsurface{wlcs::Subsurface::create_visible(parent_subsurface, 0, 0, subsurface_width, subsurface_height)},
+ input_device{GetParam().make_input_device(the_server(), client)}
+ {
+ client.roundtrip();
+ }
+
+ wlcs::Client client{the_server()};
+ wlcs::Surface main_surface;
+ wlcs::Subsurface parent_subsurface;
+ wlcs::Subsurface child_subsurface;
+ std::unique_ptr<AbstractInputDevice> input_device;
+};
+
+TEST_P(SubsurfaceTest, subsurface_has_correct_parent)
+{
+ EXPECT_THAT(&subsurface.parent(), Eq(&main_surface));
+}
+
+TEST_P(SubsurfaceTest, subsurface_gets_pointer_input)
+{
+ int const pointer_x = surface_x + 10, pointer_y = surface_y + 5;
+
+ input_device->to_screen_position(pointer_x, pointer_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface";
+ EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface));
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x - surface_x),
+ wl_fixed_from_int(pointer_y - surface_y))));
+}
+
+TEST_P(SubsurfaceTest, pointer_input_correctly_offset_for_subsurface)
+{
+ int const pointer_x = surface_x + 13, pointer_y = surface_y + 24;
+ int const subsurface_x = 8, subsurface_y = 17;
+
+ move_subsurface_to(subsurface_x, subsurface_y);
+
+ input_device->to_screen_position(pointer_x, pointer_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface";
+ EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface));
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x - surface_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - surface_y - subsurface_y))));
+}
+
+TEST_P(SubsurfaceTest, sync_subsurface_moves_when_only_parent_committed)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+ // Position is applied when parent (main_surface) commits, so subsurface does not need to be committed
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))))
+ << "Subsurface did not move after parent commit";
+}
+
+TEST_P(SubsurfaceTest, desync_subsurface_moves_when_only_parent_committed)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_desync(subsurface);
+
+ wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+ // Position is applied when parent (main_surface) commits, so subsurface does not need to be committed
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))))
+ << "Subsurface did not move after parent commit";
+}
+
+TEST_P(SubsurfaceTest, subsurface_does_not_move_when_parent_not_committed)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_desync(subsurface);
+
+ wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+ wl_surface_commit(subsurface);
+ // We don't call wl_surface_commit(main_surface), so position should not be applied
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))))
+ << "Subsurface moved to new location without parent being committed";
+}
+
+TEST_P(SubsurfaceTest, subsurface_extends_parent_input_region)
+{
+ int const pointer_x = surface_x - 5, pointer_y = surface_y + surface_height + 8;
+ int const subsurface_x = -10, subsurface_y = surface_height - 10;
+
+ move_subsurface_to(subsurface_x, subsurface_y);
+
+ input_device->to_screen_position(pointer_x, pointer_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface";
+ EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface));
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x - surface_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - surface_y - subsurface_y))));
+}
+
+TEST_P(SubsurfaceTest, input_falls_through_empty_subsurface_input_region)
+{
+ int const pointer_x = surface_x + 10, pointer_y = surface_y + 5;
+
+ auto const wl_region = wl_compositor_create_region(client.compositor());
+ wl_surface_set_input_region(subsurface, wl_region);
+ wl_region_destroy(wl_region);
+ wl_surface_commit(subsurface);
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x, pointer_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface)) << "input was incorrectly caught by subsurface";
+ EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface));
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x - surface_x),
+ wl_fixed_from_int(pointer_y - surface_y))));
+}
+
+TEST_P(SubsurfaceTest, gets_input_over_surface_with_empty_region)
+{
+ int const pointer_x = surface_x + 32, pointer_y = surface_y + 21;
+
+ auto const wl_region = wl_compositor_create_region(client.compositor());
+ wl_surface_set_input_region(main_surface, wl_region);
+ wl_region_destroy(wl_region);
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x, pointer_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface";
+ EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface));
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x - surface_x),
+ wl_fixed_from_int(pointer_y - surface_y))));
+}
+
+TEST_P(SubsurfaceTest, one_subsurface_to_another_fallthrough)
+{
+ int const pointer_x_0 = 3, pointer_y_0 = 3;
+ int const pointer_x_1 = 3, pointer_y_1 = 10;
+ int const pointer_x_2 = 10, pointer_y_2 = 3;
+ int const subsurface_x = 0, subsurface_y = 5;
+ int const subsurface_top_x = 5, subsurface_top_y = 0;
+ move_subsurface_to(subsurface_x, subsurface_y);
+ auto subsurface_top{wlcs::Subsurface::create_visible(main_surface, subsurface_top_x, subsurface_top_y, subsurface_width, subsurface_height)};
+
+ input_device->to_screen_position(pointer_x_0 + surface_x, pointer_y_0 + surface_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)) << "main surface not focused";
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x_0),
+ wl_fixed_from_int(pointer_y_0))));
+
+ input_device->to_screen_position(pointer_x_1 + surface_x, pointer_y_1 + surface_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "lower subsurface not focused";
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x_1 - subsurface_x),
+ wl_fixed_from_int(pointer_y_1 - subsurface_y))));
+
+ input_device->to_screen_position(pointer_x_2 + surface_x, pointer_y_2 + surface_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface_top)) << "upper subsurface not focused";
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x_2 - subsurface_top_x),
+ wl_fixed_from_int(pointer_y_2 - subsurface_top_y))));
+}
+
+TEST_P(SubsurfaceTest, place_below_simple)
+{
+ auto subsurface_moving_down{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)};
+ wl_subsurface_place_below(subsurface_moving_down, subsurface);
+ wl_surface_commit(subsurface_moving_down);
+ wl_surface_commit(subsurface);
+ wl_surface_commit(main_surface);
+
+ input_device->to_screen_position(5 + surface_x, 5 + surface_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface_moving_down))
+ << "subsurface.place_below() did not have an effect";
+
+ ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface))
+ << "wrong surface/subsurface on top";
+}
+
+TEST_P(SubsurfaceTest, place_above_simple)
+{
+ auto subsurface_being_covered{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)};
+ wl_subsurface_place_above(subsurface, subsurface_being_covered);
+ wl_surface_commit(subsurface);
+ wl_surface_commit(subsurface_being_covered);
+ wl_surface_commit(main_surface);
+
+ input_device->to_screen_position(5 + surface_x, 5 + surface_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface_being_covered))
+ << "subsurface.place_above() did not have an effect";
+
+ ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface))
+ << "wrong surface/subsurface on top";
+}
+
+TEST_P(SubsurfaceTest, subsurface_of_a_subsurface_handled)
+{
+ int const pointer_x_0 = 3, pointer_y_0 = 3;
+ int const pointer_x_1 = 3, pointer_y_1 = 10;
+ int const pointer_x_2 = 10, pointer_y_2 = 3;
+ int const subsurface_x = 0, subsurface_y = 5;
+ int const subsurface_top_x = 5, subsurface_top_y = -5;
+ move_subsurface_to(subsurface_x, subsurface_y);
+ auto subsurface_top{wlcs::Subsurface::create_visible(subsurface, subsurface_top_x, subsurface_top_y, subsurface_width, subsurface_height)};
+
+ input_device->to_screen_position(pointer_x_0 + surface_x, pointer_y_0 + surface_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)) << "main surface not focused";
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x_0),
+ wl_fixed_from_int(pointer_y_0))));
+
+ input_device->to_screen_position(pointer_x_1 + surface_x, pointer_y_1 + surface_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "lower subsurface not focused";
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x_1 - subsurface_x),
+ wl_fixed_from_int(pointer_y_1 - subsurface_y))));
+
+ input_device->to_screen_position(pointer_x_2 + surface_x, pointer_y_2 + surface_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface_top)) << "subsurface of subsurface not focused";
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x_2 - subsurface_top_x - subsurface_x),
+ wl_fixed_from_int(pointer_y_2 - subsurface_top_y - subsurface_y))));
+}
+
+TEST_P(SubsurfaceTest, subsurface_moves_under_input_device_once)
+{
+ int const input_x = surface_x + 10, input_y = surface_y + 5;
+ int const subsurface_x = -23, subsurface_y = -17;
+
+ input_device->to_screen_position(input_x, input_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "precondition failed";
+ ASSERT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(input_x - surface_x),
+ wl_fixed_from_int(input_y - surface_y)))) << "precondition failed";
+
+ wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+ wl_surface_commit(subsurface);
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "subsurface not focuesed";
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(input_x - surface_x),
+ wl_fixed_from_int(input_y - surface_y)))) << "input device did not get new location";
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(input_x - surface_x - subsurface_x),
+ wl_fixed_from_int(input_y - surface_y - subsurface_y)))) << "input device in wrong location";
+}
+
+TEST_P(SubsurfaceTest, subsurface_moves_under_input_device_twice)
+{
+ int const input_x = surface_x + 10, input_y = surface_y + 5;
+ int const subsurface_x_0 = 4, subsurface_y_0 = 2;
+ int const subsurface_x_1 = -23, subsurface_y_1 = -17;
+
+ input_device->to_screen_position(input_x, input_y);
+ wl_subsurface_set_position(subsurface, subsurface_x_0, subsurface_y_0);
+ wl_surface_commit(subsurface);
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "precondition failed";
+ ASSERT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(input_x - surface_x - subsurface_x_0),
+ wl_fixed_from_int(input_y - surface_y - subsurface_y_0)))) << "precondition failed";
+
+ wl_subsurface_set_position(subsurface, subsurface_x_1, subsurface_y_1);
+ wl_surface_commit(subsurface);
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "subsurface not focuesed";
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(input_x - surface_x - subsurface_x_0),
+ wl_fixed_from_int(input_y - surface_y - subsurface_y_0)))) << "input device did not get new location";
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(input_x - surface_x - subsurface_x_1),
+ wl_fixed_from_int(input_y - surface_y - subsurface_y_1)))) << "input device in wrong location";
+}
+
+TEST_P(SubsurfaceTest, subsurface_moves_out_from_under_input_device)
+{
+ int const input_x = surface_x + 10, input_y = surface_y + 5;
+ int const subsurface_x = input_x - surface_x + 10, subsurface_y = input_y - surface_y + 10;
+
+ input_device->to_screen_position(input_x, input_y);
+ client.roundtrip();
+
+ ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "precondition failed";
+ ASSERT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(input_x - surface_x),
+ wl_fixed_from_int(input_y - surface_y)))) << "precondition failed";
+
+ wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+ wl_surface_commit(subsurface);
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)) << "main surface not focuesed";
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(input_x - surface_x),
+ wl_fixed_from_int(input_y - surface_y)))) << "input device in wrong location";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ WlShellSubsurfaces,
+ SubsurfaceTest,
+ testing::Values(
+ SubsurfaceTestParams{
+ "wl_shell_surface",
+ [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+ -> std::unique_ptr<wlcs::Surface>
+ {
+ auto surface = client.create_wl_shell_surface(
+ width,
+ height);
+ server.the_server().move_surface_to(surface, x, y);
+ return std::make_unique<wlcs::Surface>(std::move(surface));
+ },
+ [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+ {
+ return std::make_unique<PointerInputDevice>(server, client);
+ }
+ }
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ XdgShellV6Subsurfaces,
+ SubsurfaceTest,
+ testing::Values(
+ SubsurfaceTestParams{
+ "xdg_v6_surface",
+ [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+ -> std::unique_ptr<wlcs::Surface>
+ {
+ auto surface = client.create_xdg_shell_v6_surface(
+ width,
+ height);
+ server.the_server().move_surface_to(surface, x, y);
+ return std::make_unique<wlcs::Surface>(std::move(surface));
+ },
+ [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+ {
+ return std::make_unique<PointerInputDevice>(server, client);
+ }
+ }
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ XdgShellStableSubsurfaces,
+ SubsurfaceTest,
+ testing::Values(
+ SubsurfaceTestParams{
+ "xdg_stable_surface",
+ [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+ -> std::unique_ptr<wlcs::Surface>
+ {
+ auto surface = client.create_xdg_shell_stable_surface(
+ width,
+ height);
+ server.the_server().move_surface_to(surface, x, y);
+ return std::make_unique<wlcs::Surface>(std::move(surface));
+ },
+ [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+ {
+ return std::make_unique<PointerInputDevice>(server, client);
+ }
+ }
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ TouchInputSubsurfaces,
+ SubsurfaceTest,
+ testing::Values(
+ SubsurfaceTestParams{
+ "touch_input_subsurfaces",
+ [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+ -> std::unique_ptr<wlcs::Surface>
+ {
+ auto surface = client.create_xdg_shell_v6_surface(
+ width,
+ height);
+ server.the_server().move_surface_to(surface, x, y);
+ return std::make_unique<wlcs::Surface>(std::move(surface));
+ },
+ [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+ {
+ return std::make_unique<TouchInputDevice>(server, client);
+ }
+ }
+ ));
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_with_sync_parent_does_not_move_when_only_grandparent_committed)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+ // We don't call wl_surface_commit(parent_subsurface), so position should not be applied
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))))
+ << "Subsurface moved without parent being committed";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_with_desync_parent_does_not_move_when_only_grandparent_committed)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_desync(parent_subsurface);
+
+ wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+ // We don't call wl_surface_commit(parent_subsurface), so position should not be applied
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))))
+ << "Subsurface moved without parent being committed";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_with_sync_parent_does_not_move_when_only_parent_committed)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+ wl_surface_commit(parent_subsurface);
+ // We don't call wl_surface_commit(main_surface), which should be required before position is applied because
+ // parent_subsurface is sync
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))))
+ << "Subsurface of sync parent moved without grandparent being committed";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_with_desync_parent_moves_when_only_parent_committed)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_desync(parent_subsurface);
+
+ wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+ wl_surface_commit(parent_subsurface);
+ // wl_surface_commit(main_surface) should NOT be required for position to be applied because parent_subsurface is
+ // desync
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))))
+ << "Did not move on desync parent commit";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_does_not_move_when_grandparent_commit_is_before_sync_parent_commit)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+ wl_surface_commit(main_surface);
+ wl_surface_commit(parent_subsurface);
+ // Committing main_surface would need to happend AFTER parent_subsurface in order for position to be applied
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))))
+ << "Subsurface moved when hierarchy commits were in the wrong order";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_moves_after_both_sync_parent_and_grandparent_commit)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+ wl_surface_commit(parent_subsurface);
+ wl_surface_commit(main_surface);
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))))
+ << "Did not move after parent and grandparent both comitted";
+}
+
+TEST_P(SubsurfaceMultilevelTest, by_default_subsurface_is_sync)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_x);
+ wl_surface_commit(parent_subsurface);
+ // Not calling wl_surface_commit(main_surface), so new position should not be applied
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))))
+ << "Subsurface moved without parent commit (it should have been 'sync' by default, but is acting as desync)";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_can_be_set_to_sync)
+{
+ int const pointer_x = 30, pointer_y = 30;
+ int const subsurface_x = 20, subsurface_y = 20;
+
+ wl_subsurface_set_desync(child_subsurface);
+ wl_subsurface_set_sync(child_subsurface);
+
+ wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_x);
+ wl_surface_commit(parent_subsurface);
+ // Not calling wl_surface_commit(main_surface), so new position should not be applied
+ client.roundtrip();
+
+ input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+ client.roundtrip();
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(pointer_x),
+ wl_fixed_from_int(pointer_y))));
+
+ EXPECT_THAT(input_device->position_on_window(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(pointer_x - subsurface_x),
+ wl_fixed_from_int(pointer_y - subsurface_y))))
+ << "Subsurface moved without parent commit (it should have been 'sync' by default, but is acting as desync)";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ WlShellSubsurfaces,
+ SubsurfaceMultilevelTest,
+ testing::Values(
+ SubsurfaceTestParams{
+ "wl_shell_surface",
+ [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+ -> std::unique_ptr<wlcs::Surface>
+ {
+ auto surface = client.create_wl_shell_surface(
+ width,
+ height);
+ server.the_server().move_surface_to(surface, x, y);
+ return std::make_unique<wlcs::Surface>(std::move(surface));
+ },
+ [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+ {
+ return std::make_unique<PointerInputDevice>(server, client);
+ }
+ }
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ XdgShellV6Subsurfaces,
+ SubsurfaceMultilevelTest,
+ testing::Values(
+ SubsurfaceTestParams{
+ "xdg_v6_surface",
+ [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+ -> std::unique_ptr<wlcs::Surface>
+ {
+ auto surface = client.create_xdg_shell_v6_surface(
+ width,
+ height);
+ server.the_server().move_surface_to(surface, x, y);
+ return std::make_unique<wlcs::Surface>(std::move(surface));
+ },
+ [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+ {
+ return std::make_unique<PointerInputDevice>(server, client);
+ }
+ }
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ XdgShellStableSubsurfaces,
+ SubsurfaceMultilevelTest,
+ testing::Values(
+ SubsurfaceTestParams{
+ "xdg_stable_surface",
+ [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+ -> std::unique_ptr<wlcs::Surface>
+ {
+ auto surface = client.create_xdg_shell_stable_surface(
+ width,
+ height);
+ server.the_server().move_surface_to(surface, x, y);
+ return std::make_unique<wlcs::Surface>(std::move(surface));
+ },
+ [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+ {
+ return std::make_unique<PointerInputDevice>(server, client);
+ }
+ }
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ TouchInputSubsurfaces,
+ SubsurfaceMultilevelTest,
+ testing::Values(
+ SubsurfaceTestParams{
+ "touch_input_subsurfaces",
+ [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+ -> std::unique_ptr<wlcs::Surface>
+ {
+ auto surface = client.create_xdg_shell_v6_surface(
+ width,
+ height);
+ server.the_server().move_surface_to(surface, x, y);
+ return std::make_unique<wlcs::Surface>(std::move(surface));
+ },
+ [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+ {
+ return std::make_unique<TouchInputDevice>(server, client);
+ }
+ }
+ ));
+
+// TODO: combinations of sync and desync at various levels of the tree
+// TODO: "bad_surface" error
+// TODO: seitch to the new surface/input method abstraction
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "surface_builder.h"
+#include "input_method.h"
+
+#include <gmock/gmock.h>
+
+#include <vector>
+#include <memory>
+#include <experimental/optional>
+
+using namespace testing;
+
+enum class RegionAction
+{
+ add,
+ subtract,
+};
+
+struct Region
+{
+ struct Element
+ {
+ RegionAction action;
+ std::pair<int, int> top_left;
+ std::pair<int, int> size;
+ };
+
+ std::string name;
+ std::pair<int, int> surface_size;
+ std::vector<Element> elements;
+
+ void apply_to_surface(wlcs::Client& client, wl_surface* surface) const
+ {
+ if (elements.empty())
+ return;
+ auto const wl_region = wl_compositor_create_region(client.compositor());
+ for (auto const& e: elements)
+ {
+ switch(e.action)
+ {
+ case RegionAction::add:
+ wl_region_add(wl_region, e.top_left.first, e.top_left.second, e.size.first, e.size.second);
+ break;
+ case RegionAction::subtract:
+ wl_region_subtract(wl_region, e.top_left.first, e.top_left.second, e.size.first, e.size.second);
+ break;
+ }
+ }
+ wl_surface_set_input_region(surface, wl_region);
+ wl_region_destroy(wl_region);
+ wl_surface_commit(surface);
+ client.roundtrip();
+ }
+};
+
+struct RegionWithTestPoints
+{
+ RegionWithTestPoints()
+ : name{"default constructed"},
+ region{"default constructed", {0, 0}, {}},
+ on_surface{0, 0},
+ off_surface{0, 0}
+ {
+ }
+
+ RegionWithTestPoints(
+ std::string const& name,
+ Region region,
+ std::pair<int, int> on_surface,
+ std::pair<int, int> delta)
+ : name{name},
+ region{region},
+ on_surface{on_surface},
+ off_surface{on_surface.first + delta.first, on_surface.second + delta.second}
+ {
+ }
+
+ std::string name;
+ Region region;
+ std::pair<int, int> on_surface;
+ std::pair<int, int> off_surface;
+};
+
+std::ostream& operator<<(std::ostream& out, RegionWithTestPoints const& param)
+{
+ return out << param.region.name << " " << param.name;
+}
+
+auto const all_surface_types = ValuesIn(wlcs::SurfaceBuilder::all_surface_types());
+auto const toplevel_surface_types = ValuesIn(wlcs::SurfaceBuilder::toplevel_surface_types());
+auto const xdg_stable_surface_type = Values(
+ std::static_pointer_cast<wlcs::SurfaceBuilder>(std::make_shared<wlcs::XdgStableSurfaceBuilder>(0, 0, 0, 0)));
+
+auto const all_input_types = ValuesIn(wlcs::InputMethod::all_input_methods());
+
+class RegionSurfaceInputCombinations :
+ public wlcs::InProcessServer,
+ public testing::WithParamInterface<std::tuple<
+ RegionWithTestPoints,
+ std::shared_ptr<wlcs::SurfaceBuilder>,
+ std::shared_ptr<wlcs::InputMethod>>>
+{
+};
+
+auto const surface_size{std::make_pair(215, 108)};
+
+Region const default_region{"default", surface_size, {}};
+
+auto const default_edges = Values(
+ RegionWithTestPoints{"left edge", default_region,
+ {0, surface_size.second / 2},
+ {-1, 0}},
+ RegionWithTestPoints{"bottom edge", default_region,
+ {surface_size.first / 2, surface_size.second - 1},
+ {0, 1}},
+ RegionWithTestPoints{"right edge", default_region,
+ {surface_size.first - 1, surface_size.second / 2},
+ {1, 0}},
+ RegionWithTestPoints{"top edge", default_region,
+ {surface_size.first / 2, 0},
+ {0, -1}});
+
+Region const full_surface_region{"explicitly specified full surface", surface_size, {
+ {RegionAction::add, {0, 0}, surface_size}}};
+
+auto const full_surface_edges = Values(
+ RegionWithTestPoints{"left edge", full_surface_region,
+ {0, surface_size.second / 2},
+ {-1, 0}},
+ RegionWithTestPoints{"bottom edge", full_surface_region,
+ {surface_size.first / 2, surface_size.second - 1},
+ {0, 1}},
+ RegionWithTestPoints{"right edge", full_surface_region,
+ {surface_size.first - 1, surface_size.second / 2},
+ {1, 0}},
+ RegionWithTestPoints{"top edge", full_surface_region,
+ {surface_size.first / 2, 0},
+ {0, -1}});
+
+auto const region_inset = std::make_pair(12, 17);
+
+Region const smaller_region{"smaller", surface_size, {
+ {RegionAction::add, region_inset, {
+ surface_size.first - region_inset.first * 2,
+ surface_size.second - region_inset.second * 2}}}};
+
+auto const smaller_region_edges = Values(
+ RegionWithTestPoints{"left edge", smaller_region,
+ {region_inset.first, surface_size.second / 2},
+ {-1, 0}},
+ RegionWithTestPoints{"bottom edge", smaller_region,
+ {surface_size.first / 2, surface_size.second - region_inset.second - 1},
+ {0, 1}},
+ RegionWithTestPoints{"right edge", smaller_region,
+ {surface_size.first - region_inset.first - 1, surface_size.second / 2},
+ {1, 0}},
+ RegionWithTestPoints{"top edge", smaller_region,
+ {surface_size.first / 2, region_inset.second},
+ {0, -1}});
+
+// If a region is larger then the surface it should be clipped
+
+auto const region_outset = std::make_pair(12, 17);
+
+Region const larger_region{"larger", surface_size, {
+ {RegionAction::add, {-region_outset.first, -region_outset.second}, {
+ surface_size.first + region_inset.first * 2,
+ surface_size.second + region_inset.second * 2}}}};
+
+auto const larger_region_edges = Values(
+ RegionWithTestPoints{"left edge", larger_region,
+ {0, surface_size.second / 2},
+ {-1, 0}},
+ RegionWithTestPoints{"bottom edge", larger_region,
+ {surface_size.first / 2, surface_size.second - 1},
+ {0, 1}},
+ RegionWithTestPoints{"right edge", larger_region,
+ {surface_size.first - 1, surface_size.second / 2},
+ {1, 0}},
+ RegionWithTestPoints{"top edge", larger_region,
+ {surface_size.first / 2, 0},
+ {0, -1}});
+
+int const small_rect_inset = 16;
+
+// Looks something like this:
+// (dotted line is real surface, solid line is input region rectangles)
+// _______A_______
+// | |
+// B| |C
+// |_D___________E_|
+// : | | :
+// : F| |G :
+// '---|---H---|---'
+// |_______|
+// I
+Region const multi_rect_region{"multi-rect", surface_size, {
+ // upper rect
+ {RegionAction::add,
+ {0, 0},
+ {surface_size.first, surface_size.second / 2}},
+ // lower rect
+ {RegionAction::add,
+ {small_rect_inset, surface_size.second / 2},
+ {surface_size.first - small_rect_inset * 2, surface_size.second / 2 + 20}}}};
+
+auto const multi_rect_edges = Values(
+ RegionWithTestPoints{"top region edge at surface top edge", multi_rect_region, // A in diagram
+ {surface_size.first / 2, 0},
+ {0, -1}},
+ RegionWithTestPoints{"right region edge at surface right edge", multi_rect_region, // C in diagram
+ {surface_size.first - 1, surface_size.second / 4},
+ {1, 0}},
+ RegionWithTestPoints{"left region edge inside surface", multi_rect_region, // F in diagram
+ {small_rect_inset, surface_size.second * 3 / 4},
+ {-1, 0}},
+ RegionWithTestPoints{"step edge", multi_rect_region, // D in diagram
+ {small_rect_inset / 2, surface_size.second / 2 - 1},
+ {0, 1}},
+ RegionWithTestPoints{"bottom clipped edge", multi_rect_region, // I in diagram
+ {surface_size.first / 2, surface_size.second - 1},
+ {0, 1}});
+
+auto const multi_rect_corners = Values(
+ RegionWithTestPoints{
+ "top-left corner", multi_rect_region, // AxB in diagram
+ {0, 0},
+ {-1, -1}},
+ RegionWithTestPoints{
+ "top-right corner", multi_rect_region, // AxC in diagram
+ {surface_size.first - 1, 0},
+ {1, -1}},
+ RegionWithTestPoints{
+ "bottom-left corner", multi_rect_region, // HxF in diagram
+ {small_rect_inset, surface_size.second - 1},
+ {-1, 1}},
+ RegionWithTestPoints{
+ "bottom-right corner", multi_rect_region, // HxG in diagram
+ {surface_size.first - small_rect_inset - 1, surface_size.second - 1},
+ {1, 1}},
+ RegionWithTestPoints{
+ "left interior corner", multi_rect_region, // HxF in diagram
+ {small_rect_inset, surface_size.second / 2 - 1},
+ {-1, 1}},
+ RegionWithTestPoints{
+ "right interior corner", multi_rect_region, // HxG in diagram
+ {surface_size.first - small_rect_inset - 1, surface_size.second / 2 - 1},
+ {1, 1}});
+
+// TODO: test subtract
+// TODO: test empty region
+// TODO: test default region
+
+TEST_P(RegionSurfaceInputCombinations, input_inside_region_seen)
+{
+ auto const top_left = std::make_pair(64, 49);
+ wlcs::Client client{the_server()};
+ RegionWithTestPoints region;
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+
+ std::tie(region, builder, input) = GetParam();
+
+ auto surface = builder->build(
+ the_server(),
+ client,
+ top_left,
+ region.region.surface_size);
+ region.region.apply_to_surface(client, *surface);
+ struct wl_surface* const wl_surface = *surface;
+
+ auto const device = input->create_device(the_server());
+ device->down_at({
+ top_left.first + region.on_surface.first,
+ top_left.second + region.on_surface.second});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Eq(wl_surface))
+ << input << " not seen by "<< builder << " when inside " << region.name << " of " << region.region.name << " region";
+
+ if (input->current_surface(client) == wl_surface)
+ {
+ EXPECT_THAT(input->position_on_surface(client), Eq(region.on_surface))
+ << input << " in the wrong place over " << builder << " while testing " << region;
+ }
+}
+
+TEST_P(RegionSurfaceInputCombinations, input_not_seen_after_leaving_region)
+{
+ auto const top_left = std::make_pair(64, 49);
+ wlcs::Client client{the_server()};
+ RegionWithTestPoints region;
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(region, builder, input) = GetParam();
+
+ auto surface = builder->build(
+ the_server(),
+ client,
+ top_left,
+ region.region.surface_size);
+ region.region.apply_to_surface(client, *surface);
+ struct wl_surface* const wl_surface = *surface;
+
+ auto const device = input->create_device(the_server());
+ device->down_at({
+ top_left.first + region.on_surface.first,
+ top_left.second + region.on_surface.second});
+ client.roundtrip();
+ device->up();
+ device->down_at({
+ top_left.first + region.off_surface.first,
+ top_left.second + region.off_surface.second});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(wl_surface))
+ << input << " seen by " << builder << " when outside " << region.name << " of " << region.region.name << " region";
+}
+
+class SurfaceInputCombinations :
+ public wlcs::InProcessServer,
+ public testing::WithParamInterface<std::tuple<
+ std::shared_ptr<wlcs::SurfaceBuilder>,
+ std::shared_ptr<wlcs::InputMethod>>>
+{
+};
+
+TEST_P(SurfaceInputCombinations, input_not_seen_in_region_after_null_buffer_committed)
+{
+ auto const top_left = std::make_pair(64, 49);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto const region = full_surface_region;
+
+ auto surface = builder->build(
+ the_server(),
+ client,
+ top_left,
+ region.surface_size);
+ region.apply_to_surface(client, *surface);
+ struct wl_surface* const wl_surface = *surface;
+ wl_surface_attach(wl_surface, nullptr, 0, 0);
+ wl_surface_commit(wl_surface);
+ client.roundtrip();
+
+ auto const device = input->create_device(the_server());
+ device->down_at(top_left);
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(wl_surface))
+ << input << " seen by " << builder << " after null buffer committed";
+}
+
+TEST_P(SurfaceInputCombinations, input_not_seen_in_surface_without_region_after_null_buffer_committed)
+{
+ auto const top_left = std::make_pair(64, 49);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto surface = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ struct wl_surface* const wl_surface = *surface;
+ wl_surface_attach(wl_surface, nullptr, 0, 0);
+ wl_surface_commit(wl_surface);
+ client.roundtrip();
+
+ auto const device = input->create_device(the_server());
+ device->down_at(top_left);
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(wl_surface))
+ << input << " seen by " << builder << " after null buffer committed";
+}
+
+TEST_P(SurfaceInputCombinations, input_not_seen_over_empty_region)
+{
+ auto const top_left = std::make_pair(64, 49);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto surface = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ struct wl_surface* const wl_surface = *surface;
+
+ auto const wl_region = wl_compositor_create_region(client.compositor());
+ wl_surface_set_input_region(*surface, wl_region);
+ wl_region_destroy(wl_region);
+ wl_surface_commit(*surface);
+ client.roundtrip();
+
+ auto const device = input->create_device(the_server());
+ device->down_at({top_left.first + 4, top_left.second + 4});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(wl_surface))
+ << input << " seen by " << builder << " with empty input region";
+}
+
+TEST_P(SurfaceInputCombinations, input_hits_parent_after_falling_through_subsurface)
+{
+ auto const top_left = std::make_pair(64, 49);
+ auto const input_offset = std::make_pair(4, 4);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto parent = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ struct wl_surface* const parent_wl_surface = *parent;
+ auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+ *parent,
+ 0, 0,
+ surface_size.first, surface_size.second));
+ wl_subsurface_set_desync(*subsurface);
+ struct wl_surface* const sub_wl_surface = *subsurface;
+
+ Region const region{"region", surface_size, {{RegionAction::add, {0, 0}, {1, 1}}}};
+ region.apply_to_surface(client, sub_wl_surface);
+
+ auto const device = input->create_device(the_server());
+ device->down_at({top_left.first + input_offset.first, top_left.second + input_offset.second});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface))
+ << input << " seen by subsurface when not over region";
+
+ EXPECT_THAT(input->current_surface(client), Eq(parent_wl_surface))
+ << input << " not seen by " << builder << " when it should have fallen through the subsurface input region";
+
+ EXPECT_THAT(input->position_on_surface(client), Eq(input_offset))
+ << input << " seen in the wrong place";
+}
+
+TEST_P(SurfaceInputCombinations, unmapping_parent_stops_subsurface_getting_input)
+{
+ auto const top_left = std::make_pair(64, 49);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto parent = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ struct wl_surface* const parent_wl_surface = *parent;
+ auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+ *parent,
+ 0, 0,
+ surface_size.first, surface_size.second));
+ wl_subsurface_set_desync(*subsurface);
+ struct wl_surface* const sub_wl_surface = *subsurface;
+ client.roundtrip();
+
+ wl_surface_attach(parent_wl_surface, nullptr, 0, 0);
+ wl_surface_commit(parent_wl_surface);
+ client.roundtrip();
+
+ auto const device = input->create_device(the_server());
+ device->down_at({top_left.first + 4, top_left.second + 4});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface))
+ << input << " seen by " << builder << " after it was unmapped";
+
+ EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface))
+ << input << " seen by subsurface after parent " << builder << " was unmapped";
+}
+
+TEST_P(SurfaceInputCombinations, input_falls_through_subsurface_when_unmapped)
+{
+ auto const top_left = std::make_pair(200, 49);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto lower = client.create_visible_surface(surface_size.first, surface_size.second);
+ the_server().move_surface_to(lower, top_left.first - 100, top_left.second);
+ struct wl_surface* const lower_wl_surface = lower;
+
+ auto parent = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ ASSERT_THAT(surface_size.first, Gt(100));
+ struct wl_surface* const parent_wl_surface = *parent;
+ auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+ *parent,
+ -100, 0,
+ surface_size.first, surface_size.second));
+ wl_subsurface_set_desync(*subsurface);
+ struct wl_surface* const sub_wl_surface = *subsurface;
+ client.roundtrip();
+
+ wl_surface_attach(sub_wl_surface, nullptr, 0, 0);
+ wl_surface_commit(sub_wl_surface);
+ client.roundtrip();
+
+ auto const device = input->create_device(the_server());
+ device->down_at({top_left.first - 90, top_left.second + 10});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface))
+ << input << " seen by subsurface after it was unmapped";
+
+ EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface))
+ << input << " seen by " << builder << " even through it shouldn't have been over it's input region";
+
+ EXPECT_THAT(input->current_surface(client), Eq(lower_wl_surface))
+ << input << " not seen by lower surface";
+}
+
+TEST_P(SurfaceInputCombinations, input_falls_through_subsurface_when_parent_unmapped)
+{
+ auto const top_left = std::make_pair(200, 49);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto lower = client.create_visible_surface(surface_size.first, surface_size.second);
+ the_server().move_surface_to(lower, top_left.first - 100, top_left.second);
+ struct wl_surface* const lower_wl_surface = lower;
+
+ auto parent = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ ASSERT_THAT(surface_size.first, Gt(100));
+ struct wl_surface* const parent_wl_surface = *parent;
+ auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+ *parent,
+ -100, 0,
+ surface_size.first, surface_size.second));
+ wl_subsurface_set_desync(*subsurface);
+ struct wl_surface* const sub_wl_surface = *subsurface;
+ client.roundtrip();
+
+ wl_surface_attach(parent_wl_surface, nullptr, 0, 0);
+ wl_surface_commit(parent_wl_surface);
+ client.roundtrip();
+
+ auto const device = input->create_device(the_server());
+ device->down_at({top_left.first - 90, top_left.second + 10});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface))
+ << input << " seen by subsurface after parent was unmapped";
+
+ EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface))
+ << input << " seen by " << builder << " after being unmapped (also input should have gone to subsurface)";
+
+ EXPECT_THAT(input->current_surface(client), Eq(lower_wl_surface))
+ << input << " not seen by lower surface";
+}
+
+TEST_P(SurfaceInputCombinations, input_seen_after_surface_unmapped_and_remapped)
+{
+ auto const top_left = std::make_pair(200, 49);
+ auto const input_offset = std::make_pair(4, 4);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto surface = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ struct wl_surface* const wl_surface = *surface;
+
+ wl_surface_attach(wl_surface, nullptr, 0, 0);
+ wl_surface_commit(wl_surface);
+ client.roundtrip();
+
+ surface->attach_visible_buffer(surface_size.first, surface_size.second);
+
+ auto const device = input->create_device(the_server());
+ device->down_at({top_left.first + input_offset.first, top_left.second + input_offset.second});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Eq(wl_surface))
+ << input << " not seen by " << builder << " after it was unmapped and remapped";
+
+ EXPECT_THAT(input->position_on_surface(client), Eq(input_offset))
+ << input << " seen in the wrong place";
+}
+
+TEST_P(SurfaceInputCombinations, input_seen_by_subsurface_after_parent_unmapped_and_remapped)
+{
+ auto const top_left = std::make_pair(200, 49);
+ auto const input_offset = std::make_pair(-90, 10);
+ auto const subsurface_offset = std::make_pair(-100, 0);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto parent = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ ASSERT_THAT(surface_size.first, Gt(100));
+ struct wl_surface* const parent_wl_surface = *parent;
+ auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+ *parent,
+ subsurface_offset.first, subsurface_offset.second,
+ surface_size.first, surface_size.second));
+ wl_subsurface_set_desync(*subsurface);
+ struct wl_surface* const sub_wl_surface = *subsurface;
+ client.roundtrip();
+
+ wl_surface_attach(parent_wl_surface, nullptr, 0, 0);
+ wl_surface_commit(parent_wl_surface);
+ client.roundtrip();
+
+ parent->attach_visible_buffer(surface_size.first, surface_size.second);
+ //the_server().move_surface_to(*parent, top_left.first, top_left.second);
+
+ auto const device = input->create_device(the_server());
+ device->down_at({top_left.first + input_offset.first, top_left.second + input_offset.second});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface))
+ << input << " seen by " << builder << " when it should be seen by it's subsurface";
+
+ EXPECT_THAT(input->current_surface(client), Eq(sub_wl_surface))
+ << input << " not seen by subsurface after parent was unmapped and remapped";
+
+ EXPECT_THAT(input->position_on_surface(client), Eq(
+ std::make_pair(input_offset.first - subsurface_offset.first, input_offset.second - subsurface_offset.second)))
+ << input << " seen in the wrong place";
+}
+
+TEST_P(SurfaceInputCombinations, input_seen_after_dragged_off_surface)
+{
+ auto const top_left = std::make_pair(200, 49);
+ auto const input_offset = std::make_pair(-5, 5);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto other = client.create_visible_surface(100, 100);
+ the_server().move_surface_to(other, top_left.first - 102, top_left.second);
+ struct wl_surface* const other_wl_surface = other;
+
+ auto main = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ struct wl_surface* const main_wl_surface = *main;
+ client.roundtrip();
+
+ auto const device = input->create_device(the_server());
+ device->down_at({top_left.first + 5, top_left.second + 5});
+ client.roundtrip();
+ device->move_to({top_left.first + input_offset.first, top_left.second + input_offset.second});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(other_wl_surface))
+ << input << " seen by second surface event though it was dragged from first";
+
+ EXPECT_THAT(input->current_surface(client), Eq(main_wl_surface))
+ << input << " not seen by " << builder << " after being dragged away";
+
+ EXPECT_THAT(input->position_on_surface(client), Eq(input_offset))
+ << input << " not seen by " << builder << " after being dragged away";
+}
+
+TEST_P(SurfaceInputCombinations, input_seen_by_second_surface_after_drag_off_first_and_up)
+{
+ auto const top_left = std::make_pair(200, 49);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto other = builder->build(
+ the_server(),
+ client,
+ std::make_pair(top_left.first - 102, top_left.second),
+ std::make_pair(100, 100));
+ struct wl_surface* const other_wl_surface = *other;
+
+ auto main = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ struct wl_surface* const main_wl_surface = *main;
+ client.roundtrip();
+
+ auto const device = input->create_device(the_server());
+ device->down_at({top_left.first + 5, top_left.second + 5});
+ client.roundtrip();
+ device->move_to({top_left.first - 80, top_left.second + 5});
+ client.roundtrip();
+ device->up();
+ device->down_at({top_left.first - 80, top_left.second + 5});
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(main_wl_surface))
+ << input << " seen by first " << builder << " after being up";
+
+ EXPECT_THAT(input->current_surface(client), Eq(other_wl_surface))
+ << input << " not seen by second " << builder << " after being up";
+}
+
+// Will only be instantiated with toplevel surfaces
+class ToplevelInputCombinations :
+ public wlcs::InProcessServer,
+ public testing::WithParamInterface<std::tuple<
+ std::shared_ptr<wlcs::SurfaceBuilder>,
+ std::shared_ptr<wlcs::InputMethod>>>
+{
+};
+
+TEST_P(ToplevelInputCombinations, input_falls_through_surface_without_region_after_null_buffer_committed)
+{
+ auto const top_left = std::make_pair(64, 49);
+ wlcs::Client client{the_server()};
+ std::shared_ptr<wlcs::SurfaceBuilder> builder;
+ std::shared_ptr<wlcs::InputMethod> input;
+ std::tie(builder, input) = GetParam();
+
+ auto lower = client.create_visible_surface(surface_size.first, surface_size.second);
+ the_server().move_surface_to(lower, top_left.first, top_left.second);
+ struct wl_surface* const lower_wl_surface = lower;
+
+ auto upper = builder->build(
+ the_server(),
+ client,
+ top_left,
+ surface_size);
+ struct wl_surface* const upper_wl_surface = *upper;
+ wl_surface_attach(upper_wl_surface, nullptr, 0, 0);
+ wl_surface_commit(upper_wl_surface);
+ client.roundtrip();
+
+ auto const device = input->create_device(the_server());
+ device->down_at(top_left);
+ client.roundtrip();
+
+ EXPECT_THAT(input->current_surface(client), Ne(upper_wl_surface))
+ << input << " seen by " << builder << " after null buffer committed";
+
+ EXPECT_THAT(input->current_surface(client), Eq(lower_wl_surface))
+ << input << " not seen by lower toplevel after null buffer committed to " << builder;
+}
+
+// There's way too many region edges/surface type/input device combinations, so we don't run all of them
+// multi_rect_edges covers most cases, so we test it against all surface type/input device combinations
+// We test the rest against just XDG toplevel
+
+INSTANTIATE_TEST_SUITE_P(
+ MultiRectEdges,
+ RegionSurfaceInputCombinations,
+ Combine(multi_rect_edges, all_surface_types, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+ DefaultEdges,
+ RegionSurfaceInputCombinations,
+ Combine(default_edges, all_surface_types, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+ FullSurface,
+ RegionSurfaceInputCombinations,
+ Combine(full_surface_edges, xdg_stable_surface_type, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+ SmallerRegion,
+ RegionSurfaceInputCombinations,
+ Combine(smaller_region_edges, xdg_stable_surface_type, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+ ClippedLargerRegion,
+ RegionSurfaceInputCombinations,
+ Combine(larger_region_edges, xdg_stable_surface_type, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+ MultiRectCorners,
+ RegionSurfaceInputCombinations,
+ Combine(multi_rect_corners, xdg_stable_surface_type, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+ SurfaceInputRegions,
+ SurfaceInputCombinations,
+ Combine(all_surface_types, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+ ToplevelInputRegions,
+ ToplevelInputCombinations,
+ Combine(toplevel_surface_types, all_input_types));
--- /dev/null
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <gmock/gmock.h>
+
+#include "helpers.h"
+
+#include "in_process_server.h"
+
+/* tests, that attempt to crash the compositor on purpose */
+
+static struct wl_buffer *
+create_bad_shm_buffer(wlcs::Client& client, int width, int height)
+{
+ struct wl_shm *shm = client.shm();
+ int stride = width * 4;
+ int size = stride * height;
+ struct wl_shm_pool *pool;
+ struct wl_buffer *buffer;
+ int fd;
+
+ fd = wlcs::helpers::create_anonymous_file(size);
+
+ pool = wl_shm_create_pool(shm, fd, size);
+ buffer = wl_shm_pool_create_buffer(
+ pool,
+ 0,
+ width,
+ height,
+ stride,
+ WL_SHM_FORMAT_ARGB8888);
+ wl_shm_pool_destroy(pool);
+
+ /* Truncate the file to a small size, so that the compositor
+ * will access it out-of-bounds, and hit SIGBUS.
+ */
+ assert(ftruncate(fd, 12) == 0);
+ close(fd);
+
+ return buffer;
+}
+
+using BadBufferTest = wlcs::InProcessServer;
+
+TEST_F(BadBufferTest, test_truncated_shm_file)
+{
+ using namespace testing;
+
+ wlcs::Client client{the_server()};
+
+ bool buffer_consumed{false};
+
+ auto surface = client.create_visible_surface(200, 200);
+
+ wl_buffer* bad_buffer = create_bad_shm_buffer(client, 200, 200);
+
+ wl_surface_attach(surface, bad_buffer, 0, 0);
+ wl_surface_damage(surface, 0, 0, 200, 200);
+
+ surface.add_frame_callback([&buffer_consumed](int) { buffer_consumed = true; });
+
+ wl_surface_commit(surface);
+
+ try
+ {
+ client.dispatch_until([&buffer_consumed]() { return buffer_consumed; });
+ }
+ catch (wlcs::ProtocolError const& err)
+ {
+ wl_buffer_destroy(bad_buffer);
+ EXPECT_THAT(err.error_code(), Eq(WL_SHM_ERROR_INVALID_FD));
+ EXPECT_THAT(err.interface(), Eq(&wl_buffer_interface));
+ return;
+ }
+
+ FAIL() << "Expected protocol error not raised";
+}
+
+TEST_F(BadBufferTest, client_lies_about_buffer_size)
+{
+ using namespace testing;
+
+ wlcs::Client client{the_server()};
+
+ bool buffer_consumed{false};
+
+ auto surface = client.create_visible_surface(200, 200);
+
+ auto const width = 200, height = 200;
+ auto const incorrect_stride = width;
+
+ auto fd = wlcs::helpers::create_anonymous_file(height * incorrect_stride);
+
+ auto shm_pool = wl_shm_create_pool(client.shm(), fd, height * incorrect_stride);
+
+ auto bad_buffer = wl_shm_pool_create_buffer(
+ shm_pool,
+ 0,
+ width, height,
+ incorrect_stride, // Stride is in bytes, not pixels, so this is ¼ the correct value.
+ WL_SHM_FORMAT_ARGB8888);
+
+ wl_shm_pool_destroy(shm_pool);
+ close(fd);
+
+ wl_surface_attach(surface, bad_buffer, 0, 0);
+ wl_surface_damage(surface, 0, 0, 200, 200);
+
+ surface.add_frame_callback([&buffer_consumed](int) { buffer_consumed = true; });
+
+ wl_surface_commit(surface);
+
+ try
+ {
+ client.dispatch_until([&buffer_consumed]() { return buffer_consumed; });
+ }
+ catch (wlcs::ProtocolError const& err)
+ {
+ wl_buffer_destroy(bad_buffer);
+ EXPECT_THAT(err.error_code(), Eq(WL_SHM_ERROR_INVALID_STRIDE));
+ EXPECT_THAT(err.interface(), Eq(&wl_buffer_interface));
+ return;
+ }
+
+ FAIL() << "Expected protocol error not raised";
+}
+
+// This should be identical to the first tests case of the previous test. It tests if a 2nd instance of the server can
+// successfully handle a bad buffer. There have been issues with the server installing a SIGBUS handler (via
+// wl_shm_buffer_begin_access()) that only works for the first server instance.
+
+using SecondBadBufferTest = wlcs::InProcessServer;
+
+TEST_F(SecondBadBufferTest, DISABLED_test_truncated_shm_file)
+{
+ using namespace testing;
+
+ wlcs::Client client{the_server()};
+
+ bool buffer_consumed{false};
+
+ auto surface = client.create_visible_surface(200, 200);
+
+ wl_buffer* bad_buffer = create_bad_shm_buffer(client, 200, 200);
+
+ wl_surface_attach(surface, bad_buffer, 0, 0);
+ wl_surface_damage(surface, 0, 0, 200, 200);
+
+ surface.add_frame_callback([&buffer_consumed](int) { buffer_consumed = true; });
+
+ wl_surface_commit(surface);
+
+ try
+ {
+ client.dispatch_until([&buffer_consumed]() { return buffer_consumed; });
+ }
+ catch (wlcs::ProtocolError const& err)
+ {
+ wl_buffer_destroy(bad_buffer);
+ EXPECT_THAT(err.error_code(), Eq(WL_SHM_ERROR_INVALID_FD));
+ EXPECT_THAT(err.interface(), Eq(&wl_buffer_interface));
+ return;
+ }
+
+ FAIL() << "Expected protocol error not raised";
+}
--- /dev/null
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+
+#include <deque>
+#include <tuple>
+
+#include <gmock/gmock.h>
+
+using ClientSurfaceEventsTest = wlcs::InProcessServer;
+
+//
+//static void
+//check_pointer(struct client *client, int x, int y)
+//{
+// int sx, sy;
+//
+// /* check that the client got the global pointer update */
+// assert(client->test->pointer_x == x);
+// assert(client->test->pointer_y == y);
+//
+// /* Does global pointer map onto the surface? */
+// if (surface_contains(client->surface, x, y)) {
+// /* check that the surface has the pointer focus */
+// assert(client->input->pointer->focus == client->surface);
+//
+// /*
+// * check that the local surface pointer maps
+// * to the global pointer.
+// */
+// sx = client->input->pointer->x + client->surface->x;
+// sy = client->input->pointer->y + client->surface->y;
+// assert(sx == x);
+// assert(sy == y);
+// } else {
+// /*
+// * The global pointer does not map onto surface. So
+// * check that it doesn't have the pointer focus.
+// */
+// assert(client->input->pointer->focus == NULL);
+// }
+//}
+//
+//static void
+//check_pointer_move(struct client *client, int x, int y)
+//{
+// weston_test_move_pointer(client->test->weston_test, x, y);
+// client_roundtrip(client);
+// check_pointer(client, x, y);
+//}
+//
+//TEST(test_pointer_surface_move)
+//{
+// struct client *client;
+//
+// client = create_client_and_test_surface(100, 100, 100, 100);
+// assert(client);
+//
+// /* move pointer outside of client */
+// assert(!surface_contains(client->surface, 50, 50));
+// check_pointer_move(client, 50, 50);
+//
+// /* move client center to pointer */
+// move_client(client, 0, 0);
+// assert(surface_contains(client->surface, 50, 50));
+// check_pointer(client, 50, 50);
+//}
+//
+//static int
+//output_contains_client(struct client *client)
+//{
+// struct output *output = client->output;
+// struct surface *surface = client->surface;
+//
+// return !(output->x >= surface->x + surface->width
+// || output->x + output->width <= surface->x
+// || output->y >= surface->y + surface->height
+// || output->y + output->height <= surface->y);
+//}
+//
+//static void
+//check_client_move(struct client *client, int x, int y)
+//{
+// move_client(client, x, y);
+//
+// if (output_contains_client(client)) {
+// assert(client->surface->output == client->output);
+// } else {
+// assert(client->surface->output == NULL);
+// }
+//}
+//
+//TEST(test_surface_output)
+//{
+// struct client *client;
+// int x, y;
+//
+// client = create_client_and_test_surface(100, 100, 100, 100);
+// assert(client);
+//
+// assert(output_contains_client(client));
+//
+// /* not visible */
+// x = 0;
+// y = -client->surface->height;
+// check_client_move(client, x, y);
+//
+// /* visible */
+// check_client_move(client, x, ++y);
+//
+// /* not visible */
+// x = -client->surface->width;
+// y = 0;
+// check_client_move(client, x, y);
+//
+// /* visible */
+// check_client_move(client, ++x, y);
+//
+// /* not visible */
+// x = client->output->width;
+// y = 0;
+// check_client_move(client, x, y);
+//
+// /* visible */
+// check_client_move(client, --x, y);
+// assert(output_contains_client(client));
+//
+// /* not visible */
+// x = 0;
+// y = client->output->height;
+// check_client_move(client, x, y);
+// assert(!output_contains_client(client));
+//
+// /* visible */
+// check_client_move(client, x, --y);
+// assert(output_contains_client(client));
+//}
+
+struct PointerMotion
+{
+ static int constexpr window_width = 231;
+ static int constexpr window_height = 220;
+ std::string name;
+ int initial_x, initial_y; // Relative to surface top-left
+ int dx, dy;
+};
+
+std::ostream& operator<<(std::ostream& out, PointerMotion const& motion)
+{
+ return out << motion.name;
+}
+
+class SurfacePointerMotionTest :
+ public wlcs::InProcessServer,
+ public testing::WithParamInterface<PointerMotion>
+{
+};
+
+TEST_P(SurfacePointerMotionTest, pointer_movement)
+{
+ using namespace testing;
+
+ auto pointer = the_server().create_pointer();
+
+ wlcs::Client client{the_server()};
+
+ auto const params = GetParam();
+
+ auto surface = client.create_visible_surface(
+ params.window_width,
+ params.window_height);
+
+ int const top_left_x = 23, top_left_y = 231;
+ the_server().move_surface_to(surface, top_left_x, top_left_y);
+
+ auto const wl_surface = static_cast<struct wl_surface*>(surface);
+
+ pointer.move_to(top_left_x + params.initial_x, top_left_y + params.initial_y);
+
+ client.roundtrip();
+
+ EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface));
+
+ /* move pointer; it should now be inside the surface */
+ pointer.move_by(params.dx, params.dy);
+
+ client.roundtrip();
+
+ EXPECT_THAT(client.window_under_cursor(), Eq(wl_surface));
+ EXPECT_THAT(client.pointer_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(params.initial_x + params.dx),
+ wl_fixed_from_int(params.initial_y + params.dy))));
+
+ /* move pointer back; it should now be outside the surface */
+ pointer.move_by(-params.dx, -params.dy);
+
+ client.roundtrip();
+ EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ PointerCrossingSurfaceCorner,
+ SurfacePointerMotionTest,
+ testing::Values(
+ PointerMotion{"Top-left", -1, -1, 1, 1},
+ PointerMotion{"Bottom-left", -1, PointerMotion::window_height, 1, -1},
+ PointerMotion{"Bottom-right", PointerMotion::window_width, PointerMotion::window_height, -1, -1},
+ PointerMotion{"Top-right", PointerMotion::window_width, -1, -1, 1}
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ PointerCrossingSurfaceEdge,
+ SurfacePointerMotionTest,
+ testing::Values(
+ PointerMotion{
+ "Centre-left",
+ -1, PointerMotion::window_height / 2,
+ 1, 0},
+ PointerMotion{
+ "Bottom-centre",
+ PointerMotion::window_width / 2, PointerMotion::window_height,
+ 0, -1},
+ PointerMotion{
+ "Centre-right",
+ PointerMotion::window_width, PointerMotion::window_height / 2,
+ -1, 0},
+ PointerMotion{
+ "Top-centre",
+ PointerMotion::window_width / 2, -1,
+ 0, 1}
+ ));
+
+TEST_F(ClientSurfaceEventsTest, surface_moves_under_pointer)
+{
+ using namespace testing;
+
+ auto pointer = the_server().create_pointer();
+
+ wlcs::Client client{the_server()};
+
+ auto surface = client.create_visible_surface(100, 100);
+ auto const wl_surface = static_cast<struct wl_surface*>(surface);
+
+ /* Set up the pointer outside the surface */
+ the_server().move_surface_to(surface, 0, 0);
+ pointer.move_to(500, 500);
+
+ client.roundtrip();
+
+ EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface));
+
+ /* move the surface so that it is under the pointer */
+ the_server().move_surface_to(surface, 450, 450);
+
+ client.dispatch_until(
+ [wl_surface, &client]()
+ {
+ return client.window_under_cursor() == wl_surface;
+ });
+
+ EXPECT_THAT(client.window_under_cursor(), Eq(wl_surface));
+ EXPECT_THAT(client.pointer_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(50),
+ wl_fixed_from_int(50))));
+}
+
+TEST_F(ClientSurfaceEventsTest, surface_moves_over_surface_under_pointer)
+{
+ using namespace testing;
+
+ auto pointer = the_server().create_pointer();
+
+ wlcs::Client client{the_server()};
+
+ auto first_surface = client.create_visible_surface(100, 100);
+ auto second_surface = client.create_visible_surface(100, 100);
+
+ /* Set up the pointer outside the surface */
+ the_server().move_surface_to(first_surface, 0, 0);
+ the_server().move_surface_to(second_surface, 0, 0);
+ pointer.move_to(500, 500);
+
+ client.roundtrip();
+
+ /* move the first surface so that it is under the pointer */
+ the_server().move_surface_to(first_surface, 450, 450);
+
+ bool first_surface_focused{false};
+ client.add_pointer_enter_notification(
+ [&first_surface_focused, &first_surface](wl_surface* surf, auto, auto)
+ {
+ if (surf == first_surface)
+ {
+ first_surface_focused = true;
+ }
+ return false;
+ });
+
+ // Wait until the first surface is focused
+ client.dispatch_until(
+ [&first_surface_focused]()
+ {
+ return first_surface_focused;
+ });
+
+ client.add_pointer_leave_notification(
+ [&first_surface_focused, &first_surface](wl_surface* surf)
+ {
+ if (surf == first_surface)
+ {
+ first_surface_focused = false;
+ }
+ return false;
+ });
+
+ bool second_surface_focused{false};
+ client.add_pointer_enter_notification(
+ [&first_surface_focused, &second_surface_focused, &second_surface](auto surf, auto x, auto y)
+ {
+ if (surf == second_surface)
+ {
+ /*
+ * Protocol requires that the pointer-leave event is sent before pointer-enter
+ */
+ EXPECT_FALSE(first_surface_focused);
+ second_surface_focused = true;
+ EXPECT_THAT(x, Eq(wl_fixed_from_int(50)));
+ EXPECT_THAT(y, Eq(wl_fixed_from_int(50)));
+ }
+ return false;
+ });
+
+ the_server().move_surface_to(second_surface, 450, 450);
+
+ client.dispatch_until(
+ [&second_surface_focused]()
+ {
+ return second_surface_focused;
+ });
+}
+
+TEST_F(ClientSurfaceEventsTest, surface_resizes_under_pointer)
+{
+ using namespace testing;
+
+ auto pointer = the_server().create_pointer();
+
+ wlcs::Client client{the_server()};
+
+ auto surface = client.create_visible_surface(100, 100);
+
+ /* Set up the pointer outside the surface */
+ the_server().move_surface_to(surface, 400, 400);
+ pointer.move_to(500, 500);
+
+ client.roundtrip();
+
+ ASSERT_THAT(client.window_under_cursor(), Ne(static_cast<wl_surface*>(surface)));
+
+ bool surface_entered{false};
+ client.add_pointer_enter_notification(
+ [&surface_entered, &surface](wl_surface* entered_surf, wl_fixed_t x, wl_fixed_t y)
+ {
+ EXPECT_THAT(surface, Eq(entered_surf));
+ EXPECT_THAT(x, Eq(wl_fixed_from_int(100)));
+ EXPECT_THAT(y, Eq(wl_fixed_from_int(100)));
+ surface_entered = true;
+ return false;
+ });
+ client.add_pointer_leave_notification(
+ [&surface_entered, &surface](wl_surface* left_surf)
+ {
+ EXPECT_THAT(surface, Eq(left_surf));
+ surface_entered = false;
+ return false;
+ });
+
+ auto larger_buffer = wlcs::ShmBuffer{client, 200, 200};
+ auto smaller_buffer = wlcs::ShmBuffer{client, 50, 50};
+
+ // Resize the surface so that the pointer is now over the top...
+ wl_surface_attach(surface, larger_buffer, 0, 0);
+ wl_surface_commit(surface);
+
+ client.dispatch_until(
+ [&surface_entered]()
+ {
+ return surface_entered;
+ });
+
+ // Resize the surface so that the pointer is no longer over the top...
+ wl_surface_attach(surface, smaller_buffer, 0, 0);
+ wl_surface_commit(surface);
+
+ client.dispatch_until(
+ [&surface_entered]()
+ {
+ return !surface_entered;
+ });
+}
+
+TEST_F(ClientSurfaceEventsTest, surface_moves_while_under_pointer)
+{
+ using namespace testing;
+
+ auto pointer = the_server().create_pointer();
+
+ wlcs::Client client{the_server()};
+
+ auto surface = client.create_visible_surface(100, 100);
+
+ the_server().move_surface_to(surface, 450, 450);
+ pointer.move_to(500, 500);
+
+ std::deque<std::pair<int, int>> surface_movements = {
+ std::make_pair(445, 455),
+ std::make_pair(460, 405),
+ std::make_pair(420, 440),
+ std::make_pair(430, 460),
+ std::make_pair(0, 0) // The last motion is not checked for
+ };
+
+ client.dispatch_until(
+ [&client, &surface]()
+ {
+ if (client.window_under_cursor() == surface)
+ {
+ EXPECT_THAT(client.pointer_position().first, Eq(wl_fixed_from_int(50)));
+ EXPECT_THAT(client.pointer_position().second, Eq(wl_fixed_from_int(50)));
+ return true;
+ }
+ return false;
+ });
+
+ int expected_x{55}, expected_y{45};
+ client.add_pointer_motion_notification(
+ [this, &surface, &surface_movements, &expected_x, &expected_y]
+ (wl_fixed_t x, wl_fixed_t y)
+ {
+ EXPECT_THAT(x, Eq(wl_fixed_from_int(expected_x)));
+ EXPECT_THAT(y, Eq(wl_fixed_from_int(expected_y)));
+
+ auto next_movement = surface_movements.front();
+ surface_movements.pop_front();
+
+ the_server().move_surface_to(
+ surface,
+ next_movement.first,
+ next_movement.second);
+
+ expected_x = 500 - next_movement.first;
+ expected_y = 500 - next_movement.second;
+ return true;
+ });
+
+ // Do the initial surface move
+ the_server().move_surface_to(
+ surface,
+ surface_movements.front().first,
+ surface_movements.front().second);
+ surface_movements.pop_front();
+
+ client.dispatch_until(
+ [&surface_movements]()
+ {
+ return surface_movements.empty();
+ });
+}
+
+TEST_F(ClientSurfaceEventsTest, frame_timestamp_increases)
+{
+ using namespace testing;
+
+ wlcs::Client client{the_server()};
+
+ auto surface = client.create_visible_surface(100, 100);
+
+ std::array<wlcs::ShmBuffer, 3> buffers = {{
+ wlcs::ShmBuffer{client, 100, 100},
+ wlcs::ShmBuffer{client, 100, 100},
+ wlcs::ShmBuffer{client, 100, 100}
+ }};
+
+ /*
+ * The first buffer must never be released, since it is replaced before
+ * it is committed, therefore it never becomes busy.
+ */
+ wl_surface_attach(surface, buffers[0], 0, 0);
+ wl_surface_attach(surface, buffers[1], 0, 0);
+
+ int prev_frame_time = 0;
+ int frame_callback_count = 0;
+ surface.add_frame_callback(
+ [&](int time)
+ {
+ EXPECT_THAT(time, Gt(prev_frame_time));
+ prev_frame_time = time;
+ frame_callback_count++;
+ });
+ wl_surface_commit(surface);
+
+ /**
+ * We need to sleep for multiple miliseconds to make sure the timestamp
+ * really does go up
+ */
+ usleep(10000);
+
+ wl_surface_attach(surface, buffers[2], 0, 0);
+ wl_surface_commit(surface);
+
+ client.dispatch_until([&frame_callback_count]()
+ {
+ return frame_callback_count >= 2;
+ });
+}
+
+TEST_F(ClientSurfaceEventsTest, surface_enters_output)
+{
+ using namespace testing;
+
+ wlcs::Client client{the_server()};
+
+ auto surface = client.create_visible_surface(100, 100);
+ client.roundtrip();
+
+ EXPECT_THAT(surface.current_outputs(), Not(IsEmpty())) << "Surface did not initially enter output";
+}
+
+// TODO: test surfaces can leave outputs once we can create multiple outputs
+
+// TODO: make parameterized for different types of shell surfaces
--- /dev/null
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+#include "surface_builder.h"
+
+#include <gmock/gmock.h>
+
+#include <vector>
+#include <memory>
+
+using namespace testing;
+
+class TouchTest:
+ public wlcs::InProcessServer,
+ public testing::WithParamInterface<std::shared_ptr<wlcs::SurfaceBuilder>>
+{
+};
+
+TEST_P(TouchTest, touch_on_surface_seen)
+{
+ int const window_width = 300, window_height = 300;
+ int const window_top_left_x = 64, window_top_left_y = 7;
+
+ wlcs::Client client{the_server()};
+ auto const surface = GetParam()->build(
+ the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height});
+ auto const wl_surface = static_cast<struct wl_surface*>(*surface);
+
+ auto touch = the_server().create_touch();
+ int touch_x = window_top_left_x + 27;
+ int touch_y = window_top_left_y + 8;
+
+ touch.down_at(touch_x, touch_y);
+ client.roundtrip();
+ ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface";
+ ASSERT_THAT(client.touch_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(touch_x - window_top_left_x),
+ wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch came down in the wrong place";
+ touch.up();
+ client.roundtrip();
+}
+
+TEST_P(TouchTest, touch_and_drag_on_surface_seen)
+{
+ int const window_width = 300, window_height = 300;
+ int const window_top_left_x = 64, window_top_left_y = 12;
+ int const touch_x = window_top_left_x + 27, touch_y = window_top_left_y + 140;
+ int const dx = 37, dy = -52;
+
+ wlcs::Client client{the_server()};
+ auto const surface = GetParam()->build(
+ the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height});
+ auto const wl_surface = static_cast<struct wl_surface*>(*surface);
+
+ auto touch = the_server().create_touch();
+
+ touch.down_at(touch_x, touch_y);
+ client.roundtrip();
+ ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface";
+ ASSERT_THAT(client.touch_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(touch_x - window_top_left_x),
+ wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch came down in the wrong place";
+
+ touch.move_to(touch_x + dx, touch_y + dy);
+ client.roundtrip();
+ ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "surface was unfocused when it shouldn't have been";
+ ASSERT_THAT(client.touch_position(),
+ Ne(std::make_pair(
+ wl_fixed_from_int(touch_x - window_top_left_x),
+ wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch did not move";
+ ASSERT_THAT(client.touch_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(touch_x - window_top_left_x + dx),
+ wl_fixed_from_int(touch_y - window_top_left_y + dy)))) << "touch did not end up in the right place";
+
+ touch.up();
+ client.roundtrip();
+}
+
+TEST_P(TouchTest, touch_drag_outside_of_surface_and_back_not_lost)
+{
+ int const window_width = 300, window_height = 300;
+ int const window_top_left_x = 64, window_top_left_y = 12;
+ int const touch_a_x = window_top_left_x + 27, touch_a_y = window_top_left_y + 12;
+ int const touch_b_x = window_top_left_x - 6, touch_b_y = window_top_left_y + window_width + 8;
+
+ wlcs::Client client{the_server()};
+ auto const surface = GetParam()->build(
+ the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height});
+ auto const wl_surface = static_cast<struct wl_surface*>(*surface);
+
+ auto touch = the_server().create_touch();
+
+ touch.down_at(touch_a_x, touch_a_y);
+ client.roundtrip();
+ ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface";
+ ASSERT_THAT(client.touch_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(touch_a_x - window_top_left_x),
+ wl_fixed_from_int(touch_a_y - window_top_left_y)))) << "touch came down in the wrong place";
+
+ touch.move_to(touch_b_x, touch_b_y);
+ client.roundtrip();
+ EXPECT_THAT(client.touched_window(), Eq(wl_surface)) << "touch was lost when it moved out of the surface";
+ if (client.touched_window() == wl_surface)
+ {
+ EXPECT_THAT(client.touch_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(touch_b_x - window_top_left_x),
+ wl_fixed_from_int(touch_b_y - window_top_left_y)))) << "touch did not end up in the right place outside of the surface";
+ }
+
+ touch.move_to(touch_a_x, touch_a_y);
+ client.roundtrip();
+ EXPECT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not come back onto surface";
+ EXPECT_THAT(client.touch_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(touch_a_x - window_top_left_x),
+ wl_fixed_from_int(touch_a_y - window_top_left_y)))) << "touch came back in the wrong place";
+
+ touch.up();
+ client.roundtrip();
+}
+
+TEST_P(TouchTest, sends_touch_up_on_surface_destroy)
+{
+ int const window_width = 300, window_height = 300;
+ int const window_top_left_x = 64, window_top_left_y = 7;
+
+ wlcs::Client client{the_server()};
+ auto surface = GetParam()->build(
+ the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height});
+
+ auto touch = the_server().create_touch();
+ int touch_x = window_top_left_x + 27;
+ int touch_y = window_top_left_y + 8;
+ touch.down_at(touch_x, touch_y);
+ client.roundtrip();
+
+ surface.reset();
+ client.roundtrip();
+
+ ASSERT_THAT(client.touched_window(), Eq(nullptr)) << "Touch did not leave surface when surface was destroyed";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AllSurfaceTypes,
+ TouchTest,
+ ValuesIn(wlcs::SurfaceBuilder::all_surface_types()),
+ wlcs::SubsurfaceBuilder::surface_builder_to_string);
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+using namespace testing;
+using namespace wlcs;
+
+using WlOutputTest = wlcs::InProcessServer;
+
+TEST_F(WlOutputTest, wl_output_properties_set)
+{
+ wlcs::Client client{the_server()};
+
+ ASSERT_THAT(client.output_count(), Ge(1u));
+ auto output = client.output_state(0);
+
+ EXPECT_THAT((bool)output.geometry_position, Eq(true));
+ EXPECT_THAT((bool)output.mode_size, Eq(true));
+ EXPECT_THAT((bool)output.scale, Eq(true));
+}
+
+TEST_F(WlOutputTest, wl_output_release)
+{
+ wlcs::Client client{the_server()};
+
+ {
+ // Acquire *any* wl_output; we don't care which
+ auto const output =
+ client.bind_if_supported<wl_output>(wlcs::AtLeastVersion{WL_OUTPUT_RELEASE_SINCE_VERSION});
+ client.roundtrip();
+ }
+ // output is now released
+
+ client.roundtrip();
+}
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "version_specifier.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+#include "generated/wlr-foreign-toplevel-management-unstable-v1-client.h"
+
+#include <gmock/gmock.h>
+#include <boost/throw_exception.hpp>
+#include <algorithm>
+
+using namespace testing;
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_foreign_toplevel_manager_v1)
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_foreign_toplevel_handle_v1)
+}
+
+namespace
+{
+int const w = 10, h = 15;
+
+class ForeignToplevelHandle
+{
+public:
+ ForeignToplevelHandle(zwlr_foreign_toplevel_handle_v1* handle);
+ ForeignToplevelHandle(ForeignToplevelHandle const&) = delete;
+ ForeignToplevelHandle& operator=(ForeignToplevelHandle const&) = delete;
+
+ auto is_dirty() const { return dirty_; }
+ auto title() const { return title_; }
+ auto app_id() const { return app_id_; }
+ auto outputs() const -> std::vector<wl_output*> const& { return outputs_; }
+ auto maximized() const { return maximized_; }
+ auto minimized() const { return minimized_; }
+ auto activated() const { return activated_; }
+ auto fullscreen() const { return fullscreen_; }
+ auto destroyed() const { return destroyed_; }
+
+ operator zwlr_foreign_toplevel_handle_v1*() const { return handle; }
+ wlcs::WlHandle<zwlr_foreign_toplevel_handle_v1> const handle;
+
+private:
+ static auto get_self(void* data) -> ForeignToplevelHandle*
+ {
+ return static_cast<ForeignToplevelHandle*>(data);
+ }
+
+ bool dirty_{false};
+ std::experimental::optional<std::string> title_;
+ std::experimental::optional<std::string> app_id_;
+ std::vector<wl_output*> outputs_;
+ bool maximized_{false}, minimized_{false}, activated_{false}, fullscreen_{false};
+
+ bool destroyed_{false};
+};
+
+ForeignToplevelHandle::ForeignToplevelHandle(zwlr_foreign_toplevel_handle_v1* handle)
+ : handle{handle}
+{
+ static zwlr_foreign_toplevel_handle_v1_listener const listener = {
+ [] /* title */ (
+ void* data,
+ zwlr_foreign_toplevel_handle_v1*,
+ char const* title)
+ {
+ auto self = get_self(data);
+ self->title_ = title;
+ self->dirty_ = true;
+ },
+ [] /* app_id */ (
+ void* data,
+ zwlr_foreign_toplevel_handle_v1*,
+ char const* app_id)
+ {
+ auto self = get_self(data);
+ self->app_id_ = app_id;
+ self->dirty_ = true;
+ },
+ [] /* output_enter */ (
+ void* data,
+ zwlr_foreign_toplevel_handle_v1*,
+ wl_output* output)
+ {
+ auto self = get_self(data);
+ self->outputs_.push_back(output);
+ self->dirty_ = true;
+ },
+ [] /* output_leave */ (
+ void* data,
+ zwlr_foreign_toplevel_handle_v1*,
+ wl_output* output)
+ {
+ auto self = get_self(data);
+ std::remove(
+ self->outputs_.begin(),
+ self->outputs_.end(),
+ output);
+ self->dirty_ = true;
+ },
+ [] /*state */ (
+ void* data,
+ zwlr_foreign_toplevel_handle_v1*,
+ wl_array* state)
+ {
+ auto self = get_self(data);
+
+ self->maximized_ = false;
+ self->minimized_ = false;
+ self->activated_ = false;
+ self->fullscreen_ = false;
+
+ for (auto item = static_cast<zwlr_foreign_toplevel_handle_v1_state*>(state->data);
+ (char*)item < static_cast<char*>(state->data) + state->size;
+ item++)
+ {
+ switch (*item)
+ {
+ case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED:
+ self->maximized_ = true;
+ break;
+
+ case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED:
+ self->minimized_ = true;
+ break;
+
+ case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED:
+ self->activated_ = true;
+ break;
+
+ case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN:
+ self->fullscreen_ = true;
+ break;
+ }
+ }
+ self->dirty_ = true;
+ },
+ [] /* done */ (
+ void* data,
+ zwlr_foreign_toplevel_handle_v1*)
+ {
+ auto self = get_self(data);
+ self->dirty_ = false;
+ },
+ [] /* closed */ (
+ void* data,
+ zwlr_foreign_toplevel_handle_v1*)
+ {
+ auto self = get_self(data);
+ self->destroyed_ = true;
+ self->dirty_ = false;
+ }
+ };
+ zwlr_foreign_toplevel_handle_v1_add_listener(handle, &listener, this);
+}
+
+class ForeignToplevelManager
+{
+public:
+ ForeignToplevelManager(wlcs::Client& client);
+
+ auto toplevels() -> std::vector<std::unique_ptr<ForeignToplevelHandle>> const&
+ {
+ toplevels_.erase(
+ std::remove_if(
+ toplevels_.begin(),
+ toplevels_.end(),
+ [](auto const& toplevel) { return toplevel->destroyed(); }),
+ toplevels_.end());
+
+ return toplevels_;
+ }
+
+ wlcs::WlHandle<zwlr_foreign_toplevel_manager_v1> const manager;
+
+private:
+ std::vector<std::unique_ptr<ForeignToplevelHandle>> toplevels_;
+};
+
+ForeignToplevelManager::ForeignToplevelManager(wlcs::Client& client)
+ : manager{client.bind_if_supported<zwlr_foreign_toplevel_manager_v1>(wlcs::AnyVersion)}
+{
+ static zwlr_foreign_toplevel_manager_v1_listener const listener = {
+ +[] /* toplevel */ (
+ void* data,
+ zwlr_foreign_toplevel_manager_v1*,
+ zwlr_foreign_toplevel_handle_v1* toplevel)
+ {
+ auto self = static_cast<ForeignToplevelManager*>(data);
+ auto handle = std::make_unique<ForeignToplevelHandle>(toplevel);
+ self->toplevels_.push_back(std::move(handle));
+ },
+ +[] /* finished */ (
+ void* /*data*/,
+ zwlr_foreign_toplevel_manager_v1 *)
+ {
+ }
+ };
+ zwlr_foreign_toplevel_manager_v1_add_listener(manager, &listener, this);
+}
+
+class ForeignToplevelManagerTest
+ : public wlcs::StartedInProcessServer
+{
+};
+
+class ForeignToplevelHandleTest
+ : public wlcs::StartedInProcessServer
+{
+public:
+ ForeignToplevelHandleTest()
+ : client{the_server()},
+ manager{client},
+ surface{client},
+ xdg_surface{client, surface},
+ xdg_toplevel{xdg_surface}
+ {
+ }
+
+ auto toplevel() -> ForeignToplevelHandle const&
+ {
+ if (manager.toplevels().empty())
+ BOOST_THROW_EXCEPTION(std::runtime_error("Manager does not know about any toplevels"));
+ if (manager.toplevels().size() > 1u)
+ BOOST_THROW_EXCEPTION(std::runtime_error(
+ "Manager knows about " + std::to_string(manager.toplevels().size()) + " toplevels"));
+ if (manager.toplevels()[0]->is_dirty())
+ BOOST_THROW_EXCEPTION(std::runtime_error("Toplevel has pending updates"));
+ return *manager.toplevels()[0];
+ }
+
+ auto toplevel(std::string const& app_id) -> ForeignToplevelHandle const&
+ {
+ std::experimental::optional<ForeignToplevelHandle const*> match;
+ for (auto const& i : manager.toplevels())
+ {
+ if (i->app_id() == app_id)
+ {
+ if (match)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Multiple toplevels have the same app ID " + app_id));
+ else
+ match = i.get();
+ }
+ }
+ if (!match)
+ BOOST_THROW_EXCEPTION(std::runtime_error("No toplevels have the app ID " + app_id));
+ if (match.value()->is_dirty())
+ BOOST_THROW_EXCEPTION(std::runtime_error("Toplevel has pending updates"));
+ return *match.value();
+ }
+
+ wlcs::Client client;
+ ForeignToplevelManager manager;
+ wlcs::Surface surface;
+ wlcs::XdgSurfaceStable xdg_surface;
+ wlcs::XdgToplevelStable xdg_toplevel;
+};
+}
+
+TEST_F(ForeignToplevelManagerTest, does_not_detect_toplevels_when_test_creates_none)
+{
+ wlcs::Client client{the_server()};
+ ForeignToplevelManager manager{client};
+ client.roundtrip();
+ ASSERT_THAT(manager.toplevels().size(), Eq(0u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_toplevel_from_same_client)
+{
+ wlcs::Client client{the_server()};
+
+ auto surface{client.create_visible_surface(w, h)};
+
+ ForeignToplevelManager manager{client};
+ client.roundtrip();
+ ASSERT_THAT(manager.toplevels().size(), Eq(1u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_toplevel_from_different_client)
+{
+ wlcs::Client foreign_client{the_server()};
+ wlcs::Client observer_client{the_server()};
+
+ auto surface{foreign_client.create_visible_surface(w, h)};
+
+ ForeignToplevelManager manager{observer_client};
+ observer_client.roundtrip();
+ ASSERT_THAT(manager.toplevels().size(), Eq(1u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_toplevel_created_after_manager)
+{
+ wlcs::Client client{the_server()};
+
+ ForeignToplevelManager manager{client};
+ client.roundtrip();
+ ASSERT_THAT(manager.toplevels().size(), Eq(0u));
+
+ auto surface{client.create_visible_surface(w, h)};
+
+ client.roundtrip();
+ ASSERT_THAT(manager.toplevels().size(), Eq(1u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_multiple_toplevels_from_multiple_clients)
+{
+ wlcs::Client foreign_client{the_server()};
+ wlcs::Client observer_client{the_server()};
+
+ auto foreign_surface{foreign_client.create_visible_surface(w, h)};
+ auto observer_surface{observer_client.create_visible_surface(w, h)};
+
+ ForeignToplevelManager manager{observer_client};
+ observer_client.roundtrip();
+ ASSERT_THAT(manager.toplevels().size(), Eq(2u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_toplevel_closed)
+{
+ wlcs::Client client{the_server()};
+
+ ForeignToplevelManager manager{client};
+ client.roundtrip();
+ EXPECT_THAT(manager.toplevels().size(), Eq(0u));
+
+ {
+ wlcs::Surface other{client};
+ wlcs::XdgSurfaceStable other_xdg{client, other};
+ wlcs::XdgToplevelStable other_toplevel{other_xdg};
+ other.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(manager.toplevels().size(), Eq(1u));
+ }
+
+ client.roundtrip();
+ ASSERT_THAT(manager.toplevels().size(), Eq(0u));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_title)
+{
+ std::string title = "Test Title @!\\-";
+
+ xdg_toplevel_set_title(xdg_toplevel, title.c_str());
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT((bool)toplevel().title(), Eq(true));
+ EXPECT_THAT(toplevel().title().value(), Eq(title));
+}
+
+TEST_F(ForeignToplevelHandleTest, title_gets_updated)
+{
+ std::string title_a = "Test Title @!\\-";
+ std::string title_b = "Title 2";
+
+ xdg_toplevel_set_title(xdg_toplevel, title_a.c_str());
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT((bool)toplevel().title(), Eq(true));
+ ASSERT_THAT(toplevel().title().value(), Eq(title_a));
+
+ xdg_toplevel_set_title(xdg_toplevel, title_b.c_str());
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT((bool)toplevel().title(), Eq(true));
+ EXPECT_THAT(toplevel().title().value(), Eq(title_b));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_app_id)
+{
+ std::string app_id = "fake.wlcs.app.id";
+
+ xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT((bool)toplevel().app_id(), Eq(true));
+ EXPECT_THAT(toplevel().app_id().value(), Eq(app_id));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_maximized)
+{
+ xdg_toplevel_set_maximized(xdg_toplevel);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().maximized(), Eq(true));
+
+ xdg_toplevel_unset_maximized(xdg_toplevel);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().maximized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_minimized)
+{
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().minimized(), Eq(false));
+
+ xdg_toplevel_set_minimized(xdg_toplevel);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().minimized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_fullscreen)
+{
+ xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().fullscreen(), Eq(true));
+
+ xdg_toplevel_unset_fullscreen(xdg_toplevel);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().maximized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_activated)
+{
+ std::string app_id = "fake.wlcs.app.id";
+ xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().activated(), Eq(true));
+
+ std::string other_app_id = "other.app.id";
+ wlcs::Surface other{client};
+ wlcs::XdgSurfaceStable other_xdg{client, other};
+ wlcs::XdgToplevelStable other_toplevel{other_xdg};
+ xdg_toplevel_set_app_id(other_toplevel, other_app_id.c_str());
+ other.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel(app_id).activated(), Eq(false));
+ EXPECT_THAT(toplevel(other_app_id).activated(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_maximize_foreign)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ xdg_toplevel_unset_maximized(xdg_toplevel);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(toplevel().maximized(), Eq(false));
+
+ zwlr_foreign_toplevel_handle_v1_set_maximized(toplevel());
+ client.roundtrip();
+
+ EXPECT_THAT(state.maximized, Eq(true));
+ EXPECT_THAT(toplevel().maximized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unmaximize_foreign)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ xdg_toplevel_set_maximized(xdg_toplevel);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(state.maximized, Eq(true));
+ EXPECT_THAT(toplevel().maximized(), Eq(true));
+
+ zwlr_foreign_toplevel_handle_v1_unset_maximized(toplevel());
+ client.roundtrip();
+
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(toplevel().maximized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_fullscreen_foreign)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(toplevel().fullscreen(), Eq(false));
+
+ zwlr_foreign_toplevel_handle_v1_set_fullscreen(toplevel(), nullptr);
+ client.roundtrip();
+
+ EXPECT_THAT(state.fullscreen, Eq(true));
+ EXPECT_THAT(toplevel().fullscreen(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unfullscreen_foreign)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(state.fullscreen, Eq(true));
+ EXPECT_THAT(toplevel().fullscreen(), Eq(true));
+
+ zwlr_foreign_toplevel_handle_v1_unset_fullscreen(toplevel());
+ client.roundtrip();
+
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(toplevel().fullscreen(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_minimize_foreign)
+{
+ std::string app_id = "fake.wlcs.app.id";
+
+ wlcs::Surface below_surface{client.create_visible_surface(w, h)};
+ the_server().move_surface_to(below_surface, 0, 0);
+ client.roundtrip();
+
+ xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+ surface.attach_visible_buffer(w, h);
+ the_server().move_surface_to(surface, 0, 0);
+ client.roundtrip();
+
+ auto pointer = the_server().create_pointer();
+ pointer.move_to(1, 1);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel(app_id).minimized(), Eq(false)) << "precondition failed";
+ ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)surface)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_set_minimized(toplevel(app_id));
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel(app_id).minimized(), Eq(true));
+
+ pointer.move_to(2, 2);
+ client.roundtrip();
+
+ EXPECT_THAT(client.window_under_cursor(), Ne((wl_surface*)surface))
+ << "surface under pointer when it should have been minimized";
+ EXPECT_THAT(client.window_under_cursor(), Eq((wl_surface*)below_surface))
+ << "surface under pointer not correct";
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unminimize_foreign)
+{
+ std::string app_id = "fake.wlcs.app.id";
+
+ wlcs::Surface below_surface{client.create_visible_surface(w, h)};
+ the_server().move_surface_to(below_surface, 0, 0);
+
+ xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+ surface.attach_visible_buffer(w, h);
+ the_server().move_surface_to(surface, 0, 0);
+ client.roundtrip();
+
+ xdg_toplevel_set_minimized(xdg_toplevel);
+ client.roundtrip();
+
+ auto pointer = the_server().create_pointer();
+ pointer.move_to(1, 1);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel(app_id).minimized(), Eq(true)) << "precondition failed";
+ ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)below_surface)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel(app_id));
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel(app_id).minimized(), Eq(false));
+
+ pointer.move_to(2, 2);
+ client.roundtrip();
+
+ EXPECT_THAT(client.window_under_cursor(), Ne((wl_surface*)below_surface))
+ << "surface under pointer when it should have been occluded by unminimized surface";
+ EXPECT_THAT(client.window_under_cursor(), Eq((wl_surface*)surface))
+ << "surface under pointer not correct";
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unminimize_foreign_to_restored)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ surface.attach_visible_buffer(w, h);
+ xdg_toplevel_unset_maximized(xdg_toplevel);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().maximized(), Eq(false)) << "precondition failed";
+
+ xdg_toplevel_set_minimized(xdg_toplevel);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel());
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().minimized(), Eq(false));
+ EXPECT_THAT(toplevel().maximized(), Eq(false));
+ EXPECT_THAT(state.maximized, Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unminimize_foreign_to_maximized)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ surface.attach_visible_buffer(w, h);
+ xdg_toplevel_set_maximized(xdg_toplevel);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().maximized(), Eq(true)) << "precondition failed";
+
+ xdg_toplevel_set_minimized(xdg_toplevel);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel());
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().minimized(), Eq(false));
+ EXPECT_THAT(toplevel().maximized(), Eq(true));
+ EXPECT_THAT(state.maximized, Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_activated_when_unminimized)
+{
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(false)) << "precondition failed";
+
+ xdg_toplevel_set_minimized(xdg_toplevel);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel());
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().activated(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_unminimized_when_activated)
+{
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(false)) << "precondition failed";
+
+ xdg_toplevel_set_minimized(xdg_toplevel);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_activate(toplevel(), client.seat());
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().minimized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_unminimized_when_maximized)
+{
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(false)) << "precondition failed";
+
+ xdg_toplevel_set_minimized(xdg_toplevel);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_set_maximized(toplevel());
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().minimized(), Eq(false));
+ EXPECT_THAT(toplevel().maximized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_unminimized_when_fullscreened)
+{
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(false)) << "precondition failed";
+
+ xdg_toplevel_set_minimized(xdg_toplevel);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_set_fullscreen(toplevel(), nullptr);
+ client.roundtrip();
+
+ EXPECT_THAT(toplevel().minimized(), Eq(false));
+ EXPECT_THAT(toplevel().fullscreen(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unfullscreen_foreign_to_restored)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ xdg_toplevel_unset_maximized(xdg_toplevel);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT(state.maximized, Eq(false)) << "precondition failed";
+ ASSERT_THAT(toplevel().maximized(), Eq(false)) << "precondition failed";
+
+ xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(state.fullscreen, Eq(true)) << "precondition failed";
+ ASSERT_THAT(toplevel().fullscreen(), Eq(true)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_unset_fullscreen(toplevel());
+ client.roundtrip();
+
+ ASSERT_THAT(state.fullscreen, Eq(false)) << "precondition failed";
+ ASSERT_THAT(toplevel().fullscreen(), Eq(false)) << "precondition failed";
+
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(toplevel().maximized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unfullscreen_foreign_to_maximized)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ xdg_toplevel_set_maximized(xdg_toplevel);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT(state.maximized, Eq(true)) << "precondition failed";
+ ASSERT_THAT(toplevel().maximized(), Eq(true)) << "precondition failed";
+
+ xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(state.fullscreen, Eq(true)) << "precondition failed";
+ ASSERT_THAT(toplevel().fullscreen(), Eq(true)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_unset_fullscreen(toplevel());
+ client.roundtrip();
+
+ ASSERT_THAT(state.fullscreen, Eq(false)) << "precondition failed";
+ ASSERT_THAT(toplevel().fullscreen(), Eq(false)) << "precondition failed";
+
+ EXPECT_THAT(state.maximized, Eq(true));
+ EXPECT_THAT(toplevel().maximized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_maximize_foreign_while_fullscreen)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ xdg_toplevel_unset_maximized(xdg_toplevel);
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ ASSERT_THAT(state.maximized, Eq(false)) << "precondition failed";
+ ASSERT_THAT(toplevel().maximized(), Eq(false)) << "precondition failed";
+
+ xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(state.fullscreen, Eq(true)) << "precondition failed";
+ ASSERT_THAT(toplevel().fullscreen(), Eq(true)) << "precondition failed";
+
+ zwlr_foreign_toplevel_handle_v1_set_maximized(toplevel());
+ client.roundtrip();
+
+ EXPECT_THAT(state.fullscreen, Eq(true)) << "XDG toplevel became not fullscreen after requesting maximized";
+ EXPECT_THAT(toplevel().fullscreen(), Eq(true)) << "foreign toplevel became not fullscreen after maximize";
+
+ zwlr_foreign_toplevel_handle_v1_unset_fullscreen(toplevel());
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ ASSERT_THAT(state.fullscreen, Eq(false)) << "precondition failed";
+ ASSERT_THAT(toplevel().fullscreen(), Eq(false)) << "precondition failed";
+
+ EXPECT_THAT(state.maximized, Eq(true));
+ EXPECT_THAT(toplevel().maximized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_activate_foreign)
+{
+ wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+ xdg_toplevel.add_configure_notification(
+ [&state](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+ std::string app_id = "fake.wlcs.app.id";
+ xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ std::string other_app_id = "other.app.id";
+ wlcs::Surface other{client};
+ wlcs::XdgSurfaceStable other_xdg{client, other};
+ wlcs::XdgToplevelStable other_toplevel{other_xdg};
+ xdg_toplevel_set_app_id(other_toplevel, other_app_id.c_str());
+ other.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(state.activated, Eq(false));
+ EXPECT_THAT(toplevel(app_id).activated(), Eq(false));
+ EXPECT_THAT(toplevel(other_app_id).activated(), Eq(true));
+
+ zwlr_foreign_toplevel_handle_v1_activate(toplevel(app_id), client.seat());
+ client.roundtrip();
+
+ EXPECT_THAT(state.activated, Eq(true));
+ EXPECT_THAT(toplevel(app_id).activated(), Eq(true));
+ EXPECT_THAT(toplevel(other_app_id).activated(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_close_foreign)
+{
+ bool should_close{false};
+ xdg_toplevel.add_close_notification(
+ [&should_close]()
+ {
+ should_close = true;
+ });
+ surface.attach_visible_buffer(w, h);
+ client.roundtrip();
+
+ EXPECT_THAT(should_close, Eq(false));
+
+ zwlr_foreign_toplevel_handle_v1_close(toplevel());
+ client.roundtrip();
+
+ EXPECT_THAT(should_close, Eq(true));
+}
+
+// TODO: test toplevel outputs
+// TODO: test fullscreening toplevel on a specific output
+// TODO: test popup does not come up as toplevel
+// TODO: test zwlr_foreign_toplevel_handle_v1_set_rectangle somehow
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "layer_shell_v1.h"
+#include "xdg_shell_stable.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+
+using Vec2 = std::pair<int, int>;
+using Rect = std::pair<Vec2, Vec2>;
+
+namespace
+{
+class LayerSurfaceTest: public wlcs::StartedInProcessServer
+{
+public:
+ LayerSurfaceTest()
+ : client{the_server()},
+ surface{client},
+ layer_surface{client, surface}
+ {
+ }
+
+ void commit_and_wait_for_configure()
+ {
+ wl_surface_commit(surface);
+ layer_surface.dispatch_until_configure();
+ }
+
+ void expect_surface_is_at_position(std::pair<int, int> pos, wlcs::Surface const& expected)
+ {
+ auto pointer = the_server().create_pointer();
+ pointer.move_to(pos.first + 10, pos.second + 10);
+ client.roundtrip();
+
+ EXPECT_THAT(client.window_under_cursor(), Eq((wl_surface*)expected));
+ if (client.window_under_cursor() == expected)
+ {
+ EXPECT_THAT(wl_fixed_to_int(client.pointer_position().first), Eq(10));
+ EXPECT_THAT(wl_fixed_to_int(client.pointer_position().second), Eq(10));
+ }
+ }
+
+ void expect_surface_is_at_position(std::pair<int, int> pos)
+ {
+ expect_surface_is_at_position(pos, surface);
+ }
+
+ auto output_rect() const -> Rect
+ {
+ EXPECT_THAT(client.output_count(), Ge(1u)) << "There are no outputs to get a size from";
+ EXPECT_THAT(client.output_count(), Eq(1u)) << "Unclear which output the layer shell surface will be placed on";
+ auto output_state = client.output_state(0);
+ EXPECT_THAT((bool)output_state.mode_size, Eq(true)) << "Output has no size";
+ EXPECT_THAT((bool)output_state.geometry_position, Eq(true)) << "Output has no position";
+ auto size = output_state.mode_size.value();
+ if (output_state.scale)
+ {
+ size.first /= output_state.scale.value();
+ size.second /= output_state.scale.value();
+ }
+ return std::make_pair(output_state.geometry_position.value(), size);
+ }
+
+ auto configured_size() const -> Vec2
+ {
+ return std::make_pair(layer_surface.last_width(), layer_surface.last_height());
+ }
+
+ wlcs::Client client;
+ wlcs::Surface surface;
+ wlcs::LayerSurfaceV1 layer_surface;
+
+ int static const default_width = 200;
+ int static const default_height = 300;
+};
+
+struct LayerSurfaceLayout
+{
+ template<typename T>
+ struct Sides
+ {
+ T left, right, top, bottom;
+ };
+
+ static auto get_all() -> std::vector<LayerSurfaceLayout>
+ {
+ bool const range[]{false, true};
+ std::vector<LayerSurfaceLayout> result;
+ for (auto left: range)
+ {
+ for (auto right: range)
+ {
+ for (auto top: range)
+ {
+ for (auto bottom: range)
+ {
+ result.emplace_back(Sides<bool>{left, right, top, bottom});
+ result.emplace_back(Sides<bool>{left, right, top, bottom}, Sides<int>{6, 9, 12, 15});
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ LayerSurfaceLayout(Sides<bool> anchor)
+ : anchor{anchor},
+ margin{0, 0, 0, 0}
+ {
+ }
+
+ LayerSurfaceLayout(Sides<bool> anchor, Sides<int> margin)
+ : anchor{anchor},
+ margin{margin}
+ {
+ }
+
+ operator uint32_t() const
+ {
+ return
+ (anchor.left ? ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT : 0) |
+ (anchor.right ? ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT : 0) |
+ (anchor.top ? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP : 0) |
+ (anchor.bottom ? ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM : 0);
+ }
+
+ auto h_expand() const -> bool { return anchor.left && anchor.right; }
+ auto v_expand() const -> bool { return anchor.top && anchor.bottom; }
+
+ auto placement_rect(Rect const& output) const -> Rect
+ {
+ int const output_width = output.second.first;
+ int const output_height = output.second.second;
+ int const output_x = output.first.first;
+ int const output_y = output.first.second;
+ auto const config_size = configure_size(output);
+ int const width = config_size.first ? config_size.first : LayerSurfaceTest::default_width;
+ int const height = config_size.second ? config_size.second : LayerSurfaceTest::default_height;
+ int const x =
+ (anchor.left ?
+ output_x + margin.left :
+ (anchor.right ?
+ (output_x + output_width - width - margin.right) :
+ (output_x + (output_width - width) / 2)
+ )
+ );
+ int const y =
+ (anchor.top ?
+ output_y + margin.top :
+ (anchor.bottom ?
+ (output_y + output_height - height - margin.bottom) :
+ (output_y + (output_height - height) / 2)
+ )
+ );
+ return std::make_pair(std::make_pair(x, y), std::make_pair(width, height));;
+ }
+
+ auto request_size() const -> Vec2
+ {
+ return std::make_pair(
+ h_expand() ? 0 : LayerSurfaceTest::default_width,
+ v_expand() ? 0 : LayerSurfaceTest::default_height);
+ }
+
+ auto configure_size(Rect const& output) const -> Vec2
+ {
+ int const configure_width = h_expand() ? output.second.first - margin.left - margin.right : 0;
+ int const configure_height = v_expand() ? output.second.second - margin.top - margin.bottom : 0;
+ return std::make_pair(configure_width, configure_height);
+ }
+
+ // Will always either return 0, or a single enum value
+ auto attached_edge() const -> zwlr_layer_surface_v1_anchor
+ {
+ if (anchor.top == anchor.bottom)
+ {
+ if (anchor.left && !anchor.right)
+ return ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
+ else if (anchor.right && !anchor.left)
+ return ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
+ }
+ else if (anchor.left == anchor.right)
+ {
+ if (anchor.top && !anchor.bottom)
+ return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
+ else if (anchor.bottom && !anchor.top)
+ return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
+ }
+ return (zwlr_layer_surface_v1_anchor)0;
+ }
+
+ Sides<bool> const anchor;
+ Sides<int> const margin;
+};
+
+static void invoke_zwlr_layer_surface_v1_set_margin(
+ zwlr_layer_surface_v1* layer_surface,
+ LayerSurfaceLayout::Sides<int> const& margin)
+{
+ zwlr_layer_surface_v1_set_margin(layer_surface, margin.top, margin.right, margin.bottom, margin.left);
+}
+
+std::ostream& operator<<(std::ostream& os, const LayerSurfaceLayout& layout)
+{
+ std::vector<std::string> strs;
+ if (layout.anchor.left)
+ strs.emplace_back("left");
+ if (layout.anchor.right)
+ strs.emplace_back("right");
+ if (layout.anchor.top)
+ strs.emplace_back("top");
+ if (layout.anchor.bottom)
+ strs.emplace_back("bottom");
+ if (strs.empty())
+ strs.emplace_back("none");
+ os << "Anchor{";
+ for (auto i = 0u; i < strs.size(); i++)
+ {
+ if (i > 0)
+ os << " | ";
+ os << strs[i];
+ }
+ os << "}, Margin{";
+ if (layout.margin.left || layout.margin.right || layout.margin.top || layout.margin.bottom)
+ {
+ os << "l: " << layout.margin.left << ", ";
+ os << "r: " << layout.margin.right << ", ";
+ os << "t: " << layout.margin.top << ", ";
+ os << "b: " << layout.margin.bottom;
+ }
+ os << "}";
+ return os;
+}
+
+class LayerSurfaceLayoutTest:
+ public LayerSurfaceTest,
+ public testing::WithParamInterface<LayerSurfaceLayout>
+{
+};
+
+struct LayerLayerParams
+{
+ std::experimental::optional<zwlr_layer_shell_v1_layer> below;
+ std::experimental::optional<zwlr_layer_shell_v1_layer> above;
+};
+
+std::ostream& operator<<(std::ostream& os, const LayerLayerParams& layer)
+{
+ os << "layers:";
+ for (auto const& i : {layer.below, layer.above})
+ {
+ os << " ";
+ os << "Layer{";
+ if (i)
+ {
+ switch (auto const value = i.value())
+ {
+ case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND:
+ os << "background";
+ break;
+ case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM:
+ os << "bottom";
+ break;
+ case ZWLR_LAYER_SHELL_V1_LAYER_TOP:
+ os << "top";
+ break;
+ case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY:
+ os << "overlay";
+ break;
+ default:
+ os << "INVALID(" << value << ")";
+ }
+ }
+ else
+ {
+ os << "none";
+ }
+ os << "}";
+ }
+ return os;
+}
+
+class LayerSurfaceLayerTest:
+ public wlcs::StartedInProcessServer,
+ public testing::WithParamInterface<LayerLayerParams>
+{
+public:
+ struct SurfaceOnLayer
+ {
+ SurfaceOnLayer(
+ wlcs::Client& client,
+ std::experimental::optional<zwlr_layer_shell_v1_layer> layer)
+ : surface{layer ? wlcs::Surface{client} : client.create_visible_surface(width, height)}
+ {
+ if (layer)
+ {
+ layer_surface.emplace(client, surface, layer.value());
+ zwlr_layer_surface_v1_set_size(layer_surface.value(), width, height);
+ surface.attach_visible_buffer(width, height);
+ }
+ }
+
+ wlcs::Surface surface;
+ std::experimental::optional<wlcs::LayerSurfaceV1> layer_surface;
+ };
+
+ LayerSurfaceLayerTest()
+ : client(the_server())
+ {
+ }
+
+ static const int width{200}, height{100};
+ wlcs::Client client;
+};
+
+struct SizeAndAnchors
+{
+ uint32_t width, height;
+ LayerSurfaceLayout anchors;
+};
+
+class LayerSurfaceErrorsTest:
+ public LayerSurfaceTest,
+ public testing::WithParamInterface<SizeAndAnchors>
+{
+};
+
+}
+
+TEST_F(LayerSurfaceTest, specifying_no_size_without_anchors_is_an_error)
+{
+ try
+ {
+ // Protocol specifies that a size of (0,0) is the default
+ commit_and_wait_for_configure();
+ }
+ catch (wlcs::ProtocolError const& err)
+ {
+ EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface));
+ // The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate
+ EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE));
+ return;
+ }
+ FAIL() << "Expected protocol error not raised";
+}
+
+TEST_F(LayerSurfaceTest, specifying_zero_size_without_anchors_is_an_error)
+{
+ try
+ {
+ zwlr_layer_surface_v1_set_size(layer_surface, 0, 0);
+ commit_and_wait_for_configure();
+ }
+ catch (wlcs::ProtocolError const& err)
+ {
+ EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface));
+ // The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate
+ EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE));
+ return;
+ }
+ FAIL() << "Expected protocol error not raised";
+}
+
+TEST_P(LayerSurfaceErrorsTest, specifying_zero_size_without_corresponding_anchors_is_an_error)
+{
+ try
+ {
+ zwlr_layer_surface_v1_set_size(layer_surface, GetParam().width, GetParam().height);
+ zwlr_layer_surface_v1_set_anchor(layer_surface, GetParam().anchors);
+ commit_and_wait_for_configure();
+ }
+ catch (wlcs::ProtocolError const& err)
+ {
+ EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface));
+ // The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate
+ EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE));
+ return;
+ }
+ FAIL() << "Expected protocol error not raised";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Anchors,
+ LayerSurfaceErrorsTest,
+ testing::Values(
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, false, false}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, false, false}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, false, false}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, false, false}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, true, false}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, true, false}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, true, false}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, true, false}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, false, true}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, false, true}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, false, true}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, false, true}}},
+ SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, true, true}}},
+ SizeAndAnchors{200, 0, LayerSurfaceLayout{{false, false, false, true}}},
+ SizeAndAnchors{200, 0, LayerSurfaceLayout{{false, false, true, false}}},
+ SizeAndAnchors{0, 200, LayerSurfaceLayout{{true, false, false, true}}},
+ SizeAndAnchors{0, 200, LayerSurfaceLayout{{false, true, true, false}}}
+ ));
+
+TEST_F(LayerSurfaceTest, can_open_layer_surface)
+{
+ zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
+ commit_and_wait_for_configure();
+}
+
+TEST_F(LayerSurfaceTest, gets_configured_with_supplied_size_when_set)
+{
+ int width = 123, height = 546;
+ zwlr_layer_surface_v1_set_size(layer_surface, width, height);
+ commit_and_wait_for_configure();
+ EXPECT_THAT(configured_size(), Eq(std::make_pair(width, height)));
+}
+
+TEST_F(LayerSurfaceTest, gets_configured_with_supplied_size_even_when_anchored_to_edges)
+{
+ int width = 321, height = 218;
+ zwlr_layer_surface_v1_set_anchor(layer_surface, LayerSurfaceLayout({true, true, true, true}));
+ zwlr_layer_surface_v1_set_size(layer_surface, width, height);
+ commit_and_wait_for_configure();
+ EXPECT_THAT(configured_size(), Eq(std::make_pair(width, height)));
+}
+
+TEST_F(LayerSurfaceTest, when_anchored_to_all_edges_gets_configured_with_output_size)
+{
+ zwlr_layer_surface_v1_set_anchor(layer_surface, LayerSurfaceLayout({true, true, true, true}));
+ commit_and_wait_for_configure();
+ auto const size = output_rect().second;
+ ASSERT_THAT(configured_size(), Eq(size));
+}
+
+TEST_F(LayerSurfaceTest, gets_configured_after_anchor_change)
+{
+ zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
+ commit_and_wait_for_configure();
+ zwlr_layer_surface_v1_set_size(layer_surface, 0, 0);
+ zwlr_layer_surface_v1_set_anchor(layer_surface, LayerSurfaceLayout({true, true, true, true}));
+ commit_and_wait_for_configure();
+ EXPECT_THAT(configured_size().first, Gt(0));
+ EXPECT_THAT(configured_size().second, Gt(0));
+}
+
+TEST_F(LayerSurfaceTest, destroy_request_supported)
+{
+ wlcs::Client client{the_server()};
+
+ {
+ auto const layer_shell = client.bind_if_supported<zwlr_layer_shell_v1>(
+ wlcs::AtLeastVersion{ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION});
+ client.roundtrip();
+ }
+ // layer_shell is now destroyed
+
+ client.roundtrip();
+}
+
+TEST_F(LayerSurfaceTest, destroy_request_not_sent_when_not_supported)
+{
+ wlcs::Client client{the_server()};
+
+ {
+ auto const layer_shell = client.bind_if_supported<zwlr_layer_shell_v1>(
+ wlcs::ExactlyVersion{ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION - 1});
+ client.roundtrip();
+ }
+ // layer_shell is now destroyed
+
+ client.roundtrip();
+}
+
+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, is_positioned_correctly_after_multiple_changes)
+{
+ 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, default_height);
+ 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
+
+ zwlr_layer_surface_v1_set_anchor(layer_surface, 0);
+ zwlr_layer_surface_v1_set_margin(layer_surface, 0, 0, 0, 0);
+ zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
+ surface.attach_visible_buffer(default_width, default_height);
+ wl_surface_commit(surface);
+ client.roundtrip(); // Sometimes we get a configure, sometimes we don't
+
+ EXPECT_THAT(configured_size().first, Eq(default_width));
+ EXPECT_THAT(configured_size().second, Eq(default_height));
+ auto expected_top_left = output.first;
+ expected_top_left.first += (output.second.first - default_width) / 2;
+ expected_top_left.second += (output.second.second - default_height) / 2;
+ expect_surface_is_at_position(expected_top_left);
+}
+
+TEST_P(LayerSurfaceLayoutTest, is_positioned_to_accommodate_other_surfaces_exclusive_zone)
+{
+ auto const layout = GetParam();
+ auto const request_size = layout.request_size();
+ auto const initial_rect = layout.placement_rect(output_rect());
+ auto const exclusive = 12;
+
+ 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_rect.second.first, initial_rect.second.second);
+
+ // Create layer surfaces with exclusive zones on the top and left of the output to push our surface out of the way
+
+ wlcs::Surface top_surface{client};
+ wlcs::LayerSurfaceV1 top_layer_surface{client, top_surface};
+ zwlr_layer_surface_v1_set_anchor(top_layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP);
+ zwlr_layer_surface_v1_set_exclusive_zone(top_layer_surface, exclusive);
+ zwlr_layer_surface_v1_set_size(top_layer_surface, exclusive, exclusive);
+ wl_surface_commit(top_surface);
+ top_layer_surface.dispatch_until_configure();
+ top_surface.attach_visible_buffer(exclusive, exclusive);
+ wl_surface_commit(top_surface);
+
+ wlcs::Surface left_surface{client};
+ wlcs::LayerSurfaceV1 left_layer_surface{client, left_surface};
+ zwlr_layer_surface_v1_set_anchor(left_layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
+ zwlr_layer_surface_v1_set_exclusive_zone(left_layer_surface, exclusive);
+ zwlr_layer_surface_v1_set_size(left_layer_surface, exclusive, exclusive);
+ wl_surface_commit(left_surface);
+ left_layer_surface.dispatch_until_configure();
+ left_surface.attach_visible_buffer(exclusive, exclusive);
+ wl_surface_commit(left_surface);
+
+ client.roundtrip();
+
+ Rect non_exlusive_zone = output_rect();
+ non_exlusive_zone.first.first += exclusive; // left
+ non_exlusive_zone.first.second += exclusive; // top
+ non_exlusive_zone.second.first -= exclusive; // width
+ non_exlusive_zone.second.second -= exclusive; // height
+
+ auto expected_config_size = layout.configure_size(non_exlusive_zone);
+ if (expected_config_size.first)
+ {
+ EXPECT_THAT(configured_size().first, Eq(expected_config_size.first));
+ }
+ if (expected_config_size.second)
+ {
+ EXPECT_THAT(configured_size().second, Eq(expected_config_size.second));
+ }
+
+ auto const expected_placement = layout.placement_rect(non_exlusive_zone);
+ surface.attach_visible_buffer(expected_placement.second.first, expected_placement.second.second);
+ expect_surface_is_at_position(expected_placement.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 wl_surface 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 wl_surface was on top";
+ ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)above.surface))
+ << "Correct surface was not on top";
+}
+
+TEST_P(LayerSurfaceLayerTest, surface_can_be_moved_to_layer)
+{
+ client.bind_if_supported<zwlr_layer_shell_v1>(wlcs::AtLeastVersion{ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION});
+ auto const& param = GetParam();
+
+ auto initial_below{param.below}, initial_above{param.above};
+ if (initial_below)
+ {
+ initial_below = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
+ }
+ if (initial_above)
+ {
+ initial_above = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
+ }
+ SurfaceOnLayer above{client, initial_above};
+ SurfaceOnLayer below{client, initial_below};
+
+ client.roundtrip();
+
+ if (above.layer_surface)
+ {
+ zwlr_layer_surface_v1_set_layer(above.layer_surface.value(), param.above.value());
+ }
+ if (below.layer_surface)
+ {
+ zwlr_layer_surface_v1_set_layer(below.layer_surface.value(), param.below.value());
+ }
+
+ wl_surface_commit(above.surface);
+ wl_surface_commit(below.surface);
+
+ auto pointer = the_server().create_pointer();
+ 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 wl_surface 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
--- /dev/null
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "in_process_server.h"
+#include "xdg_output_v1.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using namespace wlcs;
+
+using XdgOutputV1Test = wlcs::InProcessServer;
+
+TEST_F(XdgOutputV1Test, xdg_output_properties_set)
+{
+ Client client{the_server()};
+ ASSERT_THAT(client.output_count(), Ge(1u));
+
+ XdgOutputManagerV1 xdg_output_manager{client};
+ XdgOutputV1 xdg_output{xdg_output_manager, 0};
+ client.roundtrip();
+
+ auto const& state = xdg_output.state();
+ ASSERT_THAT((bool)state.logical_position, true);
+ ASSERT_THAT((bool)state.logical_size, true);
+ ASSERT_THAT((bool)state.name, true);
+ // Description is optional
+}
--- /dev/null
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+#include "xdg_shell_v6.h"
+#include "layer_shell_v1.h"
+
+#include <gmock/gmock.h>
+
+#include <experimental/optional>
+
+using namespace testing;
+
+int const window_width = 400, window_height = 500;
+int const popup_width = 60, popup_height = 40;
+
+namespace
+{
+uint32_t anchor_stable_to_v6(xdg_positioner_anchor anchor)
+{
+ switch (anchor)
+ {
+ case XDG_POSITIONER_ANCHOR_NONE:
+ return ZXDG_POSITIONER_V6_ANCHOR_NONE;
+ case XDG_POSITIONER_ANCHOR_TOP:
+ return ZXDG_POSITIONER_V6_ANCHOR_TOP;
+ case XDG_POSITIONER_ANCHOR_BOTTOM:
+ return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
+ case XDG_POSITIONER_ANCHOR_LEFT:
+ return ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+ case XDG_POSITIONER_ANCHOR_RIGHT:
+ return ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+ case XDG_POSITIONER_ANCHOR_TOP_LEFT:
+ return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+ case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT:
+ return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+ case XDG_POSITIONER_ANCHOR_TOP_RIGHT:
+ return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+ case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT:
+ return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+ default:
+ return ZXDG_POSITIONER_V6_ANCHOR_NONE;
+ }
+}
+
+uint32_t gravity_stable_to_v6(xdg_positioner_gravity gravity)
+{
+ switch (gravity)
+ {
+ case XDG_POSITIONER_GRAVITY_NONE:
+ return ZXDG_POSITIONER_V6_GRAVITY_NONE;
+ case XDG_POSITIONER_GRAVITY_TOP:
+ return ZXDG_POSITIONER_V6_GRAVITY_TOP;
+ case XDG_POSITIONER_GRAVITY_BOTTOM:
+ return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
+ case XDG_POSITIONER_GRAVITY_LEFT:
+ return ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+ case XDG_POSITIONER_GRAVITY_RIGHT:
+ return ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+ case XDG_POSITIONER_GRAVITY_TOP_LEFT:
+ return ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+ case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT:
+ return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+ case XDG_POSITIONER_GRAVITY_TOP_RIGHT:
+ return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+ case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT:
+ return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+ default:
+ return ZXDG_POSITIONER_V6_GRAVITY_NONE;
+ }
+}
+
+uint32_t constraint_adjustment_stable_to_v6(xdg_positioner_constraint_adjustment ca)
+{
+ return ca; // the two enums have the same values
+}
+
+struct PositionerParams
+{
+ PositionerParams()
+ : popup_size{popup_width, popup_height},
+ anchor_rect{{0, 0}, {window_width, window_height}}
+ {
+ }
+
+ auto with_size(int x, int y) -> PositionerParams& { popup_size = {x, y}; return *this; }
+ auto with_anchor_rect(int x, int y, int w, int h) -> PositionerParams& { anchor_rect = {{x, y}, {w, h}}; return *this; }
+ auto with_anchor(xdg_positioner_anchor value) -> PositionerParams& { anchor_stable = {value}; return *this; }
+ auto with_gravity(xdg_positioner_gravity value) -> PositionerParams& { gravity_stable = {value}; return *this; }
+ auto with_constraint_adjustment(xdg_positioner_constraint_adjustment value) -> PositionerParams& { constraint_adjustment_stable = {value}; return *this; }
+ auto with_offset(int x, int y) -> PositionerParams& { offset = {{x, y}}; return *this; }
+ auto with_grab(bool enable = true) -> PositionerParams& { grab = enable; return *this; }
+
+ std::pair<int, int> popup_size; // will default to XdgPopupStableTestBase::popup_(width|height) if nullopt
+ std::pair<std::pair<int, int>, std::pair<int, int>> anchor_rect; // will default to the full window rect
+ std::experimental::optional<xdg_positioner_anchor> anchor_stable;
+ std::experimental::optional<xdg_positioner_gravity> gravity_stable;
+ std::experimental::optional<xdg_positioner_constraint_adjustment> constraint_adjustment_stable;
+ std::experimental::optional<std::pair<int, int>> offset;
+ bool grab{false};
+};
+
+struct PositionerTestParams
+{
+ PositionerTestParams(std::string name, int expected_x, int expected_y, PositionerParams const& positioner)
+ : name{name},
+ expected_positon{expected_x, expected_y},
+ positioner{positioner}
+ {
+ }
+
+ std::string name;
+ std::pair<int, int> expected_positon;
+ PositionerParams positioner;
+};
+
+std::ostream& operator<<(std::ostream& out, PositionerTestParams const& param)
+{
+ return out << param.name;
+}
+
+class XdgPopupManagerBase
+{
+public:
+ struct State
+ {
+ int x;
+ int y;
+ int width;
+ int height;
+ };
+
+ static int const window_x = 500, window_y = 500;
+
+ XdgPopupManagerBase(wlcs::InProcessServer* const in_process_server)
+ : the_server{in_process_server->the_server()},
+ client{the_server},
+ surface{client}
+ {
+ surface.add_frame_callback([this](auto) { surface_rendered = true; });
+ }
+
+ virtual ~XdgPopupManagerBase() = default;
+
+ void wait_for_frame_to_render()
+ {
+ surface.attach_buffer(window_width, window_height);
+ surface_rendered = false;
+ wl_surface_commit(surface);
+ client.dispatch_until([this]() { return surface_rendered; });
+ the_server.move_surface_to(surface, window_x, window_y);
+ }
+
+ void map_popup(PositionerParams const& params)
+ {
+ popup_surface.emplace(client);
+ setup_popup(params);
+ wl_surface_commit(popup_surface.value());
+ dispatch_until_popup_configure();
+ popup_surface.value().attach_buffer(params.popup_size.first, params.popup_size.second);
+ bool surface_rendered{false};
+ popup_surface.value().add_frame_callback([&surface_rendered](auto) { surface_rendered = true; });
+ wl_surface_commit(popup_surface.value());
+ client.dispatch_until([&surface_rendered]() { return surface_rendered; });
+ }
+
+ void unmap_popup()
+ {
+ clear_popup();
+ popup_surface = std::experimental::nullopt;
+ }
+
+ virtual auto popup_position() const -> std::experimental::optional<std::pair<int, int>> = 0;
+ virtual void dispatch_until_popup_configure() = 0;
+
+ MOCK_METHOD0(popup_done, void());
+
+ wlcs::Server& the_server;
+
+ wlcs::Client client;
+ wlcs::Surface surface;
+ std::experimental::optional<wlcs::Surface> popup_surface;
+
+ std::experimental::optional<State> state;
+
+protected:
+ virtual void setup_popup(PositionerParams const& params) = 0;
+ virtual void clear_popup() = 0;
+
+ bool surface_rendered{true};
+};
+
+class XdgPopupStableManager : public XdgPopupManagerBase
+{
+public:
+ XdgPopupStableManager(wlcs::InProcessServer* const in_process_server)
+ : XdgPopupManagerBase{in_process_server},
+ xdg_shell_surface{client, surface},
+ toplevel{xdg_shell_surface}
+ {
+ wait_for_frame_to_render();
+ }
+
+ void dispatch_until_popup_configure() override
+ {
+ client.dispatch_until(
+ [prev_count = popup_surface_configure_count, ¤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);
+ if (param.grab)
+ {
+ if (!client.latest_serial())
+ {
+ BOOST_THROW_EXCEPTION(std::runtime_error("client does not have a serial"));
+ }
+ xdg_popup_grab(popup.value().popup, client.seat(), client.latest_serial().value());
+ }
+
+ popup.value().add_close_notification([this](){ popup_done(); });
+ popup_xdg_surface.value().add_configure_notification([&](uint32_t serial)
+ {
+ xdg_surface_ack_configure(popup_xdg_surface.value(), serial);
+ popup_surface_configure_count++;
+ });
+
+ popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height)
+ {
+ state = State{x, y, width, height};
+ });
+ }
+
+ void clear_popup() override
+ {
+ popup = std::experimental::nullopt;
+ popup_xdg_surface = std::experimental::nullopt;
+ }
+
+ auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override
+ {
+ return std::make_pair(state.value().x, state.value().y);
+ }
+
+ wlcs::XdgSurfaceStable xdg_shell_surface;
+ wlcs::XdgToplevelStable toplevel;
+
+ std::experimental::optional<wlcs::XdgSurfaceStable> popup_xdg_surface;
+ std::experimental::optional<wlcs::XdgPopupStable> popup;
+
+ int popup_surface_configure_count{0};
+};
+
+class XdgPopupV6Manager : public XdgPopupManagerBase
+{
+public:
+ XdgPopupV6Manager(wlcs::InProcessServer* const in_process_server)
+ : XdgPopupManagerBase{in_process_server},
+ xdg_shell_surface{client, surface},
+ toplevel{xdg_shell_surface}
+ {
+ wait_for_frame_to_render();
+ }
+
+ void dispatch_until_popup_configure() override
+ {
+ client.dispatch_until(
+ [prev_count = popup_surface_configure_count, ¤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);
+ if (param.grab)
+ {
+ if (!client.latest_serial())
+ {
+ BOOST_THROW_EXCEPTION(std::runtime_error("client does not have a serial"));
+ }
+ zxdg_popup_v6_grab(popup.value().popup, client.seat(), client.latest_serial().value());
+ }
+
+ popup.value().add_close_notification([this](){ popup_done(); });
+ popup_xdg_surface.value().add_configure_notification([&](uint32_t serial)
+ {
+ zxdg_surface_v6_ack_configure(popup_xdg_surface.value(), serial);
+ popup_surface_configure_count++;
+ });
+
+ popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height)
+ {
+ state = State{x, y, width, height};
+ });
+ }
+
+ void clear_popup() override
+ {
+ popup = std::experimental::nullopt;
+ popup_xdg_surface = std::experimental::nullopt;
+ }
+
+ auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override
+ {
+ return std::make_pair(state.value().x, state.value().y);
+ }
+
+ wlcs::XdgSurfaceV6 xdg_shell_surface;
+ wlcs::XdgToplevelV6 toplevel;
+
+ std::experimental::optional<wlcs::XdgSurfaceV6> popup_xdg_surface;
+ std::experimental::optional<wlcs::XdgPopupV6> popup;
+
+ int popup_surface_configure_count{0};
+};
+
+class LayerV1PopupManager : public XdgPopupManagerBase
+{
+public:
+ LayerV1PopupManager(wlcs::InProcessServer* const in_process_server)
+ : XdgPopupManagerBase{in_process_server},
+ layer_surface{client, surface}
+ {
+ zwlr_layer_surface_v1_set_size(layer_surface, window_width, window_height);
+ wait_for_frame_to_render();
+ }
+
+ void dispatch_until_popup_configure() override
+ {
+ client.dispatch_until(
+ [prev_count = popup_surface_configure_count, ¤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());
+ if (param.grab)
+ {
+ if (!client.latest_serial())
+ {
+ BOOST_THROW_EXCEPTION(std::runtime_error("client does not have a serial"));
+ }
+ xdg_popup_grab(popup.value().popup, client.seat(), client.latest_serial().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_close_notification([this](){ popup_done(); });
+ popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height)
+ {
+ state = State{x, y, width, height};
+ });
+ }
+
+ void clear_popup() override
+ {
+ popup = std::experimental::nullopt;
+ popup_xdg_surface = std::experimental::nullopt;
+ }
+
+ auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override
+ {
+ return std::make_pair(state.value().x, state.value().y);
+ }
+
+ wlcs::LayerSurfaceV1 layer_surface;
+
+ std::experimental::optional<wlcs::XdgSurfaceStable> popup_xdg_surface;
+ std::experimental::optional<wlcs::XdgPopupStable> popup;
+
+ int popup_surface_configure_count{0};
+};
+
+}
+
+class XdgPopupPositionerTest:
+ public wlcs::StartedInProcessServer,
+ public testing::WithParamInterface<PositionerTestParams>
+{
+};
+
+TEST_P(XdgPopupPositionerTest, xdg_shell_stable_popup_placed_correctly)
+{
+ auto manager = std::make_unique<XdgPopupStableManager>(this);
+ auto const& param = GetParam();
+
+ manager->map_popup(param.positioner);
+
+ ASSERT_THAT(
+ manager->popup_position(),
+ Ne(std::experimental::nullopt)) << "popup configure event not sent";
+
+ ASSERT_THAT(
+ manager->popup_position(),
+ Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position";
+}
+
+TEST_P(XdgPopupPositionerTest, xdg_shell_unstable_v6_popup_placed_correctly)
+{
+ auto manager = std::make_unique<XdgPopupV6Manager>(this);
+ auto const& param = GetParam();
+
+ manager->map_popup(param.positioner);
+
+ ASSERT_THAT(
+ manager->popup_position(),
+ Ne(std::experimental::nullopt)) << "popup configure event not sent";
+
+ ASSERT_THAT(
+ manager->popup_position(),
+ Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position";
+}
+
+TEST_P(XdgPopupPositionerTest, layer_shell_popup_placed_correctly)
+{
+ auto manager = std::make_unique<LayerV1PopupManager>(this);
+ auto const& param = GetParam();
+
+ manager->map_popup(param.positioner);
+
+ ASSERT_THAT(
+ manager->popup_position(),
+ Ne(std::experimental::nullopt)) << "popup configure event not sent";
+
+ ASSERT_THAT(
+ manager->popup_position(),
+ Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Default,
+ XdgPopupPositionerTest,
+ testing::Values(
+ PositionerTestParams{"default values", (window_width - popup_width) / 2, (window_height - popup_height) / 2, PositionerParams()}
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ Anchor,
+ XdgPopupPositionerTest,
+ testing::Values(
+ PositionerTestParams{"anchor left", -popup_width / 2, (window_height - popup_height) / 2,
+ PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_LEFT)},
+
+ PositionerTestParams{"anchor right", window_width - popup_width / 2, (window_height - popup_height) / 2,
+ PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_RIGHT)},
+
+ PositionerTestParams{"anchor top", (window_width - popup_width) / 2, -popup_height / 2,
+ PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP)},
+
+ PositionerTestParams{"anchor bottom", (window_width - popup_width) / 2, window_height - popup_height / 2,
+ PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM)},
+
+ PositionerTestParams{"anchor top left", -popup_width / 2, -popup_height / 2,
+ PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)},
+
+ PositionerTestParams{"anchor top right", window_width - popup_width / 2, -popup_height / 2,
+ PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT)},
+
+ PositionerTestParams{"anchor bottom left", -popup_width / 2, window_height - popup_height / 2,
+ PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT)},
+
+ PositionerTestParams{"anchor bottom right", window_width - popup_width / 2, window_height - popup_height / 2,
+ PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)}
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ Gravity,
+ XdgPopupPositionerTest,
+ testing::Values(
+ PositionerTestParams{"gravity none", (window_width - popup_width) / 2, (window_height - popup_height) / 2,
+ PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_NONE)},
+
+ PositionerTestParams{"gravity left", window_width / 2 - popup_width, (window_height - popup_height) / 2,
+ PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_LEFT)},
+
+ PositionerTestParams{"gravity right", window_width / 2, (window_height - popup_height) / 2,
+ PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_RIGHT)},
+
+ PositionerTestParams{"gravity top", (window_width - popup_width) / 2, window_height / 2 - popup_height,
+ PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP)},
+
+ PositionerTestParams{"gravity bottom", (window_width - popup_width) / 2, window_height / 2,
+ PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM)},
+
+ PositionerTestParams{"gravity top left", window_width / 2 - popup_width, window_height / 2 - popup_height,
+ PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)},
+
+ PositionerTestParams{"gravity top right", window_width / 2, window_height / 2 - popup_height,
+ PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT)},
+
+ PositionerTestParams{"gravity bottom left", window_width / 2 - popup_width, window_height / 2,
+ PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT)},
+
+ PositionerTestParams{"gravity bottom right", window_width / 2, window_height / 2,
+ PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)}
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ AnchorRect,
+ XdgPopupPositionerTest,
+ testing::Values(
+ PositionerTestParams{"explicit defaultPositionerParams anchor rect", (window_width - popup_width) / 2, (window_height - popup_height) / 2,
+ PositionerParams().with_anchor_rect(0, 0, window_width, window_height)},
+
+ PositionerTestParams{"upper left anchor rect", (window_width - 40 - popup_width) / 2, (window_height - 30 - popup_height) / 2,
+ PositionerParams().with_anchor_rect(0, 0, window_width - 40, window_height - 30)},
+
+ PositionerTestParams{"upper right anchor rect", (window_width + 40 - popup_width) / 2, (window_height - 30 - popup_height) / 2,
+ PositionerParams().with_anchor_rect(40, 0, window_width - 40, window_height - 30)},
+
+ PositionerTestParams{"lower left anchor rect", (window_width - 40 - popup_width) / 2, (window_height + 30 - popup_height) / 2,
+ PositionerParams().with_anchor_rect(0, 30, window_width - 40, window_height - 30)},
+
+ PositionerTestParams{"lower right anchor rect", (window_width + 40 - popup_width) / 2, (window_height + 30 - popup_height) / 2,
+ PositionerParams().with_anchor_rect(40, 30, window_width - 40, window_height - 30)},
+
+ PositionerTestParams{"offset anchor rect", (window_width - 40 - popup_width) / 2, (window_height - 80 - popup_height) / 2,
+ PositionerParams().with_anchor_rect(20, 20, window_width - 80, window_height - 120)}
+ ));
+
+struct XdgPopupTestParam
+{
+ std::function<std::unique_ptr<XdgPopupManagerBase>(wlcs::InProcessServer* const)> build;
+};
+
+std::ostream& operator<<(std::ostream& out, XdgPopupTestParam const&) { return out; }
+
+class XdgPopupTest:
+ public wlcs::StartedInProcessServer,
+ public testing::WithParamInterface<XdgPopupTestParam>
+{
+};
+
+TEST_P(XdgPopupTest, pointer_focus_goes_to_popup)
+{
+ auto const& param = GetParam();
+ auto manager = param.build(this);
+ auto pointer = the_server().create_pointer();
+ pointer.move_to(manager->window_x + 1, manager->window_y + 1);
+ manager->client.roundtrip();
+
+ EXPECT_THAT(manager->client.window_under_cursor(), Eq((wl_surface*)manager->surface));
+
+ auto positioner = PositionerParams{}
+ .with_size(30, 30)
+ .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
+ .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+ manager->map_popup(positioner);
+ manager->client.roundtrip();
+
+ pointer.move_to(manager->window_x + 2, manager->window_y + 1);
+ manager->client.roundtrip();
+
+ EXPECT_THAT(manager->client.window_under_cursor(), Eq((wl_surface*)manager->popup_surface.value()));
+}
+
+TEST_P(XdgPopupTest, popup_gives_up_pointer_focus_when_gone)
+{
+ auto const& param = GetParam();
+ auto manager = param.build(this);
+ auto pointer = the_server().create_pointer();
+
+ auto positioner = PositionerParams{}
+ .with_size(30, 30)
+ .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
+ .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+ manager->map_popup(positioner);
+ manager->client.roundtrip();
+
+ pointer.move_to(manager->window_x + 2, manager->window_y + 1);
+ manager->client.roundtrip();
+
+ EXPECT_THAT(manager->client.window_under_cursor(), Eq((wl_surface*)manager->popup_surface.value()));
+
+ manager->unmap_popup();
+ manager->client.roundtrip();
+ pointer.move_to(manager->window_x + 3, manager->window_y + 1);
+ manager->client.roundtrip();
+
+ EXPECT_THAT(manager->client.window_under_cursor(), Eq((wl_surface*)manager->surface));
+}
+
+TEST_P(XdgPopupTest, grabbed_popup_gets_done_event_when_new_toplevel_created)
+{
+ auto const& param = GetParam();
+ auto manager = param.build(this);
+ auto pointer = the_server().create_pointer();
+
+ // This is needed to get a serial, which will be used later on
+ pointer.move_to(manager->window_x + 2, manager->window_y + 2);
+ pointer.left_click();
+ manager->client.roundtrip();
+
+ auto positioner = PositionerParams{}
+ .with_size(30, 30)
+ .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
+ .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
+ .with_grab();
+ manager->map_popup(positioner);
+ manager->client.roundtrip();
+
+ EXPECT_CALL(*manager, popup_done());
+
+ manager->client.create_visible_surface(window_width, window_height);
+}
+
+TEST_P(XdgPopupTest, grabbed_popup_does_not_get_keyboard_focus)
+{
+ auto const& param = GetParam();
+ auto manager = param.build(this);
+ auto pointer = the_server().create_pointer();
+
+ // This is needed to get a serial, which will be used later on
+ pointer.move_to(manager->window_x + 2, manager->window_y + 2);
+ pointer.left_click();
+ manager->client.roundtrip();
+
+ auto positioner = PositionerParams{}
+ .with_size(30, 30)
+ .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
+ .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
+ .with_grab();
+ manager->map_popup(positioner);
+ manager->client.roundtrip();
+
+ EXPECT_THAT(
+ manager->client.keyboard_focused_window(),
+ Ne(static_cast<wl_surface*>(manager->popup_surface.value())))
+ << "popup given keyboard focus";
+ EXPECT_THAT(
+ manager->client.keyboard_focused_window(),
+ Eq(static_cast<wl_surface*>(manager->surface)));
+}
+
+TEST_P(XdgPopupTest, non_grabbed_popup_does_not_get_keyboard_focus)
+{
+ 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();
+
+ EXPECT_THAT(
+ manager->client.keyboard_focused_window(),
+ Ne(static_cast<wl_surface*>(manager->popup_surface.value())))
+ << "popup given keyboard focus";
+ EXPECT_THAT(
+ manager->client.keyboard_focused_window(),
+ Eq(static_cast<wl_surface*>(manager->surface)));
+}
+
+TEST_P(XdgPopupTest, does_not_get_popup_done_event_before_button_press)
+{
+ auto const& param = GetParam();
+ auto manager = param.build(this);
+ auto pointer = the_server().create_pointer();
+
+ // This is needed to get a serial, which will be used later on
+ pointer.move_to(manager->window_x + 2, manager->window_y + 2);
+ pointer.left_click();
+ manager->client.roundtrip();
+
+ auto positioner = PositionerParams{}
+ .with_size(30, 30)
+ .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
+ .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
+ .with_grab();
+ manager->map_popup(positioner);
+ manager->client.roundtrip();
+
+ // This may or may not be sent, but a button press should not come in after it if it is sent
+ bool got_popup_done = false;
+ EXPECT_CALL(*manager, popup_done()).Times(AnyNumber()).WillRepeatedly([&]()
+ {
+ got_popup_done = true;
+ });
+
+ manager->client.add_pointer_button_notification([&](auto, auto, auto)
+ {
+ EXPECT_THAT(got_popup_done, IsFalse()) << "pointer button sent after popup done";
+ return true;
+ });
+
+ pointer.move_to(manager->window_x + 32, manager->window_y + 32);
+ pointer.left_click();
+ manager->client.roundtrip();
+}
+
+TEST_F(XdgPopupTest, zero_size_anchor_rect_stable)
+{
+ auto manager = std::make_unique<XdgPopupStableManager>(this);
+
+ auto positioner = PositionerParams{}
+ .with_anchor_rect(window_width / 2, window_height / 2, 0, 0);
+
+ manager->map_popup(positioner);
+ manager->client.roundtrip();
+
+ ASSERT_THAT(
+ manager->popup_position(),
+ Eq(std::experimental::make_optional(
+ std::make_pair(
+ (window_width - popup_width) / 2,
+ (window_height - popup_height) / 2)))) << "popup placed in incorrect position";
+}
+
+// regression test for https://github.com/MirServer/mir/issues/836
+TEST_P(XdgPopupTest, popup_configure_is_valid)
+{
+ auto const& param = GetParam();
+ auto manager = param.build(this);
+
+ auto positioner = PositionerParams{}
+ .with_size(30, 30)
+ .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
+ .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+ manager->map_popup(positioner);
+ manager->client.roundtrip();
+
+ ASSERT_THAT(manager->state, Ne(std::experimental::nullopt));
+ EXPECT_THAT(manager->state.value().width, Gt(0));
+ EXPECT_THAT(manager->state.value().height, Gt(0));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ XdgPopupStable,
+ XdgPopupTest,
+ testing::Values(XdgPopupTestParam{
+ [](wlcs::InProcessServer* const server)
+ {
+ return std::make_unique<XdgPopupStableManager>(server);
+ }}));
+
+INSTANTIATE_TEST_SUITE_P(
+ XdgPopupUnstableV6,
+ XdgPopupTest,
+ testing::Values(XdgPopupTestParam{
+ [](wlcs::InProcessServer* const server)
+ {
+ return std::make_unique<XdgPopupV6Manager>(server);
+ }}));
+
+INSTANTIATE_TEST_SUITE_P(
+ LayerShellPopup,
+ XdgPopupTest,
+ testing::Values(XdgPopupTestParam{
+ [](wlcs::InProcessServer* const server)
+ {
+ return std::make_unique<LayerV1PopupManager>(server);
+ }}));
+
+// TODO: test that positioner is always overlapping or adjacent to parent
+// TODO: test that positioner is copied immediately after use
+// TODO: test that error is raised when incomplete positioner is used (positioner without size and anchor rect set)
+// TODO: test set_size
+// TODO: test that set_window_geometry affects anchor rect
+// TODO: test set_constraint_adjustment
+// TODO: test set_offset
+// TODO: test that a zero size anchor rect fails on v6
--- /dev/null
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+#include "wl_handle.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using XdgSurfaceStableTest = wlcs::InProcessServer;
+using wlcs::AnyVersion;
+
+TEST_F(XdgSurfaceStableTest, supports_xdg_shell_stable_protocol)
+{
+ wlcs::Client client{the_server()};
+ ASSERT_THAT(client.xdg_shell_stable(), NotNull());
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceStable xdg_surface{client, surface};
+}
+
+TEST_F(XdgSurfaceStableTest, gets_configure_event)
+{
+ wlcs::Client client{the_server()};
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceStable xdg_surface{client, surface};
+
+ int surface_configure_count{0};
+ xdg_surface.add_configure_notification([&](uint32_t serial)
+ {
+ xdg_surface_ack_configure(xdg_surface, serial);
+ surface_configure_count++;
+ });
+
+ wlcs::XdgToplevelStable toplevel{xdg_surface};
+ surface.attach_buffer(600, 400);
+
+ client.roundtrip();
+
+ EXPECT_THAT(surface_configure_count, Eq(1));
+}
+
+TEST_F(XdgSurfaceStableTest, creating_xdg_surface_from_wl_surface_with_existing_role_is_an_error)
+{
+ wlcs::Client client{the_server()};
+
+ auto const xdg_wm_base = client.bind_if_supported<struct xdg_wm_base>(AnyVersion);
+
+ // We need a parent for the subsurface
+ auto const parent = client.create_visible_surface(300, 300);
+
+ auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor()));
+
+ // We need some way of assigning a role to a wl_surface. wl_subcompositor is as good a way as any.
+ auto const subsurface =
+ wlcs::wrap_wl_object(wl_subcompositor_get_subsurface(client.subcompositor(), surface, parent));
+
+ client.roundtrip();
+
+ try
+ {
+ xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+ client.roundtrip();
+ }
+ catch(wlcs::ProtocolError const& error)
+ {
+ EXPECT_THAT(error.interface(), Eq(&xdg_wm_base_interface));
+ EXPECT_THAT(error.error_code(), Eq(XDG_WM_BASE_ERROR_ROLE));
+ return;
+ }
+
+ FAIL() << "Expected protocol error not received";
+}
+
+
+TEST_F(XdgSurfaceStableTest, creating_xdg_surface_from_wl_surface_with_attached_buffer_is_an_error)
+{
+ wlcs::Client client{the_server()};
+
+ auto const xdg_wm_base = client.bind_if_supported<struct xdg_wm_base>(AnyVersion);
+
+ auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor()));
+ wlcs::ShmBuffer buffer{client, 300, 300};
+ wl_surface_attach(surface, buffer, 0, 0);
+ client.roundtrip();
+
+ try
+ {
+ xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+ client.roundtrip();
+ }
+ catch(wlcs::ProtocolError const& error)
+ {
+ EXPECT_THAT(error.interface(), Eq(&xdg_wm_base_interface));
+ EXPECT_THAT(error.error_code(), Eq(XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE));
+ return;
+ }
+
+ FAIL() << "Expected protocol error not received";
+}
+
+TEST_F(XdgSurfaceStableTest, creating_xdg_surface_from_wl_surface_with_committed_buffer_is_an_error)
+{
+ wlcs::Client client{the_server()};
+
+ auto const xdg_wm_base = client.bind_if_supported<struct xdg_wm_base>(AnyVersion);
+
+ auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor()));
+ wlcs::ShmBuffer buffer{client, 300, 300};
+ wl_surface_attach(surface, buffer, 0, 0);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ try
+ {
+ xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+ client.roundtrip();
+ }
+ catch(wlcs::ProtocolError const& error)
+ {
+ EXPECT_THAT(error.interface(), Eq(&xdg_wm_base_interface));
+ EXPECT_THAT(error.error_code(), Eq(XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE));
+ return;
+ }
+
+ FAIL() << "Expected protocol error not received";
+}
+
+TEST_F(XdgSurfaceStableTest, attaching_buffer_to_unconfigured_xdg_surface_is_an_error)
+{
+ wlcs::Client client{the_server()};
+
+ auto const xdg_wm_base = client.bind_if_supported<struct xdg_wm_base>(AnyVersion);
+
+ auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor()));
+ wlcs::ShmBuffer buffer{client, 300, 300};
+ client.roundtrip();
+
+ try
+ {
+ xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+ wl_surface_attach(surface, buffer, 0, 0);
+ client.roundtrip();
+ }
+ catch(wlcs::ProtocolError const& error)
+ {
+ EXPECT_THAT(error.interface(), Eq(&xdg_surface_interface));
+ EXPECT_THAT(error.error_code(), Eq(XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER));
+ return;
+ }
+
+ FAIL() << "Expected protocol error not received";
+}
--- /dev/null
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_v6.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using XdgSurfaceV6Test = wlcs::InProcessServer;
+
+TEST_F(XdgSurfaceV6Test, supports_xdg_shell_v6_protocol)
+{
+ wlcs::Client client{the_server()};
+ ASSERT_THAT(client.xdg_shell_v6(), NotNull());
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceV6 xdg_surface{client, surface};
+}
+
+TEST_F(XdgSurfaceV6Test, gets_configure_event)
+{
+ wlcs::Client client{the_server()};
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceV6 xdg_surface{client, surface};
+
+ int surface_configure_count{0};
+ xdg_surface.add_configure_notification([&](uint32_t serial)
+ {
+ zxdg_surface_v6_ack_configure(xdg_surface, serial);
+ surface_configure_count++;
+ });
+
+ wlcs::XdgToplevelV6 toplevel{xdg_surface};
+ surface.attach_buffer(600, 400);
+
+ client.roundtrip();
+
+ EXPECT_THAT(surface_configure_count, Eq(1));
+}
--- /dev/null
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+
+#include <gmock/gmock.h>
+
+#include <experimental/optional>
+
+using namespace testing;
+
+namespace
+{
+class ConfigurationWindow
+{
+public:
+ int const window_width = 200, window_height = 320;
+
+ ConfigurationWindow(wlcs::Client& client)
+ : client{client},
+ surface{client},
+ xdg_shell_surface{client, surface},
+ toplevel{xdg_shell_surface}
+ {
+ xdg_shell_surface.add_configure_notification([&](uint32_t serial)
+ {
+ xdg_surface_ack_configure(xdg_shell_surface, serial);
+ surface_configure_count++;
+ });
+
+ toplevel.add_configure_notification([this](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelStable::State{width, height, states};
+ });
+
+ wl_surface_commit(surface);
+ client.roundtrip();
+ surface.attach_buffer(window_width, window_height);
+ wl_surface_commit(surface);
+ dispatch_until_configure();
+ }
+
+ ~ConfigurationWindow()
+ {
+ client.roundtrip();
+ }
+
+ void dispatch_until_configure()
+ {
+ client.dispatch_until(
+ [prev_count = surface_configure_count, ¤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<struct wl_surface*>(surface)));
+ EXPECT_THAT(client.pointer_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(end_x - window_x - dx),
+ wl_fixed_from_int(end_y - window_y - dy))));
+
+ client.roundtrip();
+}
+
+// Tests https://github.com/MirServer/mir/issues/1792
+TEST_F(XdgToplevelStableTest, touch_can_not_steal_pointer_based_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_visible_buffer(window_width, window_height);
+ the_server().move_surface_to(surface, window_x, window_y);
+
+ auto pointer = the_server().create_pointer();
+ auto touch = the_server().create_touch();
+
+ bool button_down{false};
+ uint32_t last_pointer_serial{0};
+
+ client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+ last_pointer_serial = serial;
+ button_down = is_down;
+ return true;
+ });
+
+ pointer.move_to(start_x, start_y);
+ pointer.left_button_down();
+ touch.down_at(start_x, start_y);
+
+ client.dispatch_until([&](){
+ return button_down;
+ });
+
+ xdg_toplevel_move(toplevel, client.seat(), last_pointer_serial);
+ client.roundtrip();
+ pointer.left_button_up();
+ touch.move_to(0, 0);
+ client.roundtrip();
+
+ // The move should have either been ignored entirly or been based on the pointer (which didn't move)
+ // Either way, the window should be in the same place it started
+ EXPECT_THAT(client.window_under_cursor(), Eq(static_cast<struct wl_surface*>(surface)));
+ EXPECT_THAT(client.pointer_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(start_x - window_x),
+ wl_fixed_from_int(start_y - window_y))));
+}
+
+TEST_F(XdgToplevelStableTest, pointer_leaves_surface_during_interactive_move)
+{
+ int window_x = 100, window_y = 100;
+ int window_width = 420, window_height = 390;
+ int start_x = window_x + 5, start_y = window_y + 5;
+
+ wlcs::Client client{the_server()};
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceStable xdg_shell_surface{client, surface};
+ wlcs::XdgToplevelStable toplevel{xdg_shell_surface};
+ surface.attach_buffer(window_width, window_height);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ the_server().move_surface_to(surface, window_x, window_y);
+
+ auto pointer = the_server().create_pointer();
+
+ bool button_down{false};
+ uint32_t last_serial{0};
+
+ client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+ last_serial = serial;
+ button_down = is_down;
+ return true;
+ });
+
+ pointer.move_to(start_x, start_y);
+ pointer.left_button_down();
+
+ client.dispatch_until([&](){
+ return button_down;
+ });
+
+ xdg_toplevel_move(toplevel, client.seat(), last_serial);
+ client.dispatch_until([&](){
+ return !client.window_under_cursor();
+ });
+}
+
+TEST_F(XdgToplevelStableTest, surface_can_be_resized_interactively)
+{
+ int window_x = 100, window_y = 100;
+ int window_width = 420, window_height = 390;
+ int start_x = window_x + 5, start_y = window_y + 5;
+ int dx = 60, dy = -40;
+ int end_x = window_x + dx + 20, end_y = window_y + dy + 20;
+
+ wlcs::Client client{the_server()};
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceStable xdg_shell_surface{client, surface};
+ wlcs::XdgToplevelStable toplevel{xdg_shell_surface};
+ surface.attach_buffer(window_width, window_height);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ the_server().move_surface_to(surface, window_x, window_y);
+
+ auto pointer = the_server().create_pointer();
+
+ bool button_down{false};
+ uint32_t last_serial{0};
+
+ client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+ last_serial = serial;
+ button_down = is_down;
+ return true;
+ });
+
+ pointer.move_to(start_x, start_y);
+ pointer.left_button_down();
+
+ client.dispatch_until([&](){
+ return button_down;
+ });
+
+ xdg_toplevel_resize(toplevel, client.seat(), last_serial, XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT);
+ client.roundtrip();
+ pointer.move_to(start_x + dx, start_x + dy);
+ pointer.left_button_up();
+ client.roundtrip();
+
+ pointer.move_to(end_x, end_y);
+ client.roundtrip();
+
+ EXPECT_THAT(client.window_under_cursor(), Eq(static_cast<struct wl_surface*>(surface)));
+ EXPECT_THAT(client.pointer_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(end_x - window_x - dx),
+ wl_fixed_from_int(end_y - window_y - dy))));
+
+ client.roundtrip();
+}
+
+TEST_F(XdgToplevelStableTest, pointer_leaves_surface_during_interactive_resize)
+{
+ int window_x = 100, window_y = 100;
+ int window_width = 420, window_height = 390;
+ int start_x = window_x + 5, start_y = window_y + 5;
+
+ wlcs::Client client{the_server()};
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceStable xdg_shell_surface{client, surface};
+ wlcs::XdgToplevelStable toplevel{xdg_shell_surface};
+ surface.attach_buffer(window_width, window_height);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ the_server().move_surface_to(surface, window_x, window_y);
+
+ auto pointer = the_server().create_pointer();
+
+ bool button_down{false};
+ uint32_t last_serial{0};
+
+ client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+ last_serial = serial;
+ button_down = is_down;
+ return true;
+ });
+
+ pointer.move_to(start_x, start_y);
+ pointer.left_button_down();
+
+ client.dispatch_until([&](){
+ return button_down;
+ });
+
+ xdg_toplevel_resize(toplevel, client.seat(), last_serial, XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT);
+ client.dispatch_until([&](){
+ return !client.window_under_cursor();
+ });
+}
+
+TEST_F(XdgToplevelStableTest, parent_can_be_set)
+{
+ const int window_pos_x = 200, window_pos_y = 280;
+
+ wlcs::Client client{the_server()};
+
+ ConfigurationWindow parent{client};
+ the_server().move_surface_to(parent.surface, window_pos_x, window_pos_y);
+
+ ConfigurationWindow child{client};
+ the_server().move_surface_to(child.surface, window_pos_x, window_pos_y);
+
+ xdg_toplevel_set_parent(child, parent);
+ wl_surface_commit(child.surface);
+ client.roundtrip();
+}
+
+TEST_F(XdgToplevelStableTest, null_parent_can_be_set)
+{
+ const int window_pos_x = 200, window_pos_y = 280;
+
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ the_server().move_surface_to(window.surface, window_pos_x, window_pos_y);
+
+ xdg_toplevel_set_parent(window, nullptr);
+ wl_surface_commit(window.surface);
+ client.roundtrip();
+}
+
+using XdgToplevelStableConfigurationTest = wlcs::InProcessServer;
+
+TEST_F(XdgToplevelStableConfigurationTest, defaults)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ // default values
+ EXPECT_THAT(state.width, Eq(0));
+ EXPECT_THAT(state.height, Eq(0));
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, window_can_maximize_itself)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ ASSERT_THAT(state.maximized, Eq(false)) << "test could not run as precondition failed";
+
+ xdg_toplevel_set_maximized(window);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.width, Gt(0));
+ EXPECT_THAT(state.height, Gt(0));
+ EXPECT_THAT(state.maximized, Eq(true));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, window_can_unmaximize_itself)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ xdg_toplevel_set_maximized(window);
+ window.dispatch_until_configure();
+
+ ASSERT_THAT(state.maximized, Eq(true)) << "test could not run as precondition failed";
+
+ xdg_toplevel_unset_maximized(window);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, window_can_fullscreen_itself)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ xdg_toplevel_set_fullscreen(window, nullptr);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.width, Gt(0));
+ EXPECT_THAT(state.height, Gt(0));
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(state.fullscreen, Eq(true));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, window_can_unfullscreen_itself)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ xdg_toplevel_set_fullscreen(window, nullptr);
+ window.dispatch_until_configure();
+
+ ASSERT_THAT(state.fullscreen, Eq(true)) << "test could not run as precondition failed";
+
+ xdg_toplevel_unset_fullscreen(window);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, DISABLED_window_stays_maximized_after_fullscreen)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ xdg_toplevel_set_maximized(window);
+ window.dispatch_until_configure();
+
+ ASSERT_THAT(state.maximized, Eq(true)) << "test could not run as precondition failed";
+
+ xdg_toplevel_set_fullscreen(window, nullptr);
+ window.dispatch_until_configure();
+
+ ASSERT_THAT(state.fullscreen, Eq(true)) << "test could not run as precondition failed";
+
+ xdg_toplevel_unset_fullscreen(window);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.width, Gt(0));
+ EXPECT_THAT(state.height, Gt(0));
+ EXPECT_THAT(state.maximized, Eq(true));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, DISABLED_window_can_maximize_itself_while_fullscreen)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ ASSERT_THAT(state.maximized, Eq(false)) << "test could not run as precondition failed";
+
+ xdg_toplevel_set_fullscreen(window, nullptr);
+ window.dispatch_until_configure();
+
+ ASSERT_THAT(state.fullscreen, Eq(true)) << "test could not run as precondition failed";
+
+ xdg_toplevel_set_maximized(window);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.maximized, Eq(true));
+
+ xdg_toplevel_unset_fullscreen(window);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.width, Gt(0));
+ EXPECT_THAT(state.height, Gt(0));
+ EXPECT_THAT(state.maximized, Eq(true));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, activated_state_follows_pointer)
+{
+ wlcs::Client client{the_server()};
+
+ ConfigurationWindow window_a{client};
+ auto& state_a = window_a.state;
+ int const a_x = 12, a_y = 15;
+ the_server().move_surface_to(window_a, a_x, a_y);
+
+ ConfigurationWindow window_b{client};
+ auto& state_b = window_b.state;
+ int const b_x = a_x + window_a.window_width + 27, b_y = 15;
+ the_server().move_surface_to(window_b, b_x, b_y);
+
+ auto pointer = the_server().create_pointer();
+
+ pointer.move_to(a_x + 10, a_y + 10);
+ pointer.left_click();
+ client.roundtrip();
+
+ ASSERT_THAT(state_a.activated, Eq(true));
+ ASSERT_THAT(state_b.activated, Eq(false));
+
+ pointer.move_to(b_x + 10, b_y + 10);
+ pointer.left_click();
+ client.roundtrip();
+
+ EXPECT_THAT(state_a.activated, Eq(false));
+ EXPECT_THAT(state_b.activated, Eq(true));
+}
--- /dev/null
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_v6.h"
+
+#include <gmock/gmock.h>
+
+#include <experimental/optional>
+
+using namespace testing;
+
+namespace
+{
+class ConfigurationWindow
+{
+public:
+ int const window_width = 200, window_height = 320;
+
+ ConfigurationWindow(wlcs::Client& client)
+ : client{client},
+ surface{client},
+ xdg_surface{client, surface},
+ toplevel{xdg_surface}
+ {
+ xdg_surface.add_configure_notification([&](uint32_t serial)
+ {
+ zxdg_surface_v6_ack_configure(xdg_surface, serial);
+ surface_configure_count++;
+ });
+
+ toplevel.add_configure_notification([this](int32_t width, int32_t height, struct wl_array *states)
+ {
+ state = wlcs::XdgToplevelV6::State{width, height, states};
+ });
+
+ wl_surface_commit(surface);
+ client.roundtrip();
+ surface.attach_buffer(window_width, window_height);
+ wl_surface_commit(surface);
+ dispatch_until_configure();
+ }
+
+ ~ConfigurationWindow()
+ {
+ client.roundtrip();
+ }
+
+ void dispatch_until_configure()
+ {
+ client.dispatch_until(
+ [prev_count = surface_configure_count, ¤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<struct wl_surface*>(surface)));
+ EXPECT_THAT(client.pointer_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(end_x - window_x - dx),
+ wl_fixed_from_int(end_y - window_y - dy))));
+
+ client.roundtrip();
+}
+
+
+TEST_F(XdgToplevelV6Test, pointer_leaves_surface_during_interactive_move)
+{
+ int window_x = 100, window_y = 100;
+ int window_width = 420, window_height = 390;
+ int start_x = window_x + 5, start_y = window_y + 5;
+
+ wlcs::Client client{the_server()};
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceV6 xdg_shell_surface{client, surface};
+ wlcs::XdgToplevelV6 toplevel{xdg_shell_surface};
+ surface.attach_buffer(window_width, window_height);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ the_server().move_surface_to(surface, window_x, window_y);
+
+ auto pointer = the_server().create_pointer();
+
+ bool button_down{false};
+ uint32_t last_serial{0};
+
+ client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+ last_serial = serial;
+ button_down = is_down;
+ return true;
+ });
+
+ pointer.move_to(start_x, start_y);
+ pointer.left_button_down();
+
+ client.dispatch_until([&](){
+ return button_down;
+ });
+
+ zxdg_toplevel_v6_move(toplevel, client.seat(), last_serial);
+ client.dispatch_until([&](){
+ return !client.window_under_cursor();
+ });
+}
+
+TEST_F(XdgToplevelV6Test, surface_can_be_resized_interactively)
+{
+ int window_x = 100, window_y = 100;
+ int window_width = 420, window_height = 390;
+ int start_x = window_x + 5, start_y = window_y + 5;
+ int dx = 60, dy = -40;
+ int end_x = window_x + dx + 20, end_y = window_y + dy + 20;
+
+ wlcs::Client client{the_server()};
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceV6 xdg_shell_surface{client, surface};
+ wlcs::XdgToplevelV6 toplevel{xdg_shell_surface};
+ surface.attach_buffer(window_width, window_height);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ the_server().move_surface_to(surface, window_x, window_y);
+
+ auto pointer = the_server().create_pointer();
+
+ bool button_down{false};
+ uint32_t last_serial{0};
+
+ client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+ last_serial = serial;
+ button_down = is_down;
+ return true;
+ });
+
+ pointer.move_to(start_x, start_y);
+ pointer.left_button_down();
+
+ client.dispatch_until([&](){
+ return button_down;
+ });
+
+ zxdg_toplevel_v6_resize(toplevel, client.seat(), last_serial, ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT);
+ client.roundtrip();
+ pointer.move_to(start_x + dx, start_x + dy);
+ pointer.left_button_up();
+ client.roundtrip();
+
+ pointer.move_to(end_x, end_y);
+ client.roundtrip();
+
+ EXPECT_THAT(client.window_under_cursor(), Eq(static_cast<struct wl_surface*>(surface)));
+ EXPECT_THAT(client.pointer_position(),
+ Eq(std::make_pair(
+ wl_fixed_from_int(end_x - window_x - dx),
+ wl_fixed_from_int(end_y - window_y - dy))));
+
+ client.roundtrip();
+}
+
+TEST_F(XdgToplevelV6Test, pointer_leaves_surface_during_interactive_resize)
+{
+ int window_x = 100, window_y = 100;
+ int window_width = 420, window_height = 390;
+ int start_x = window_x + 5, start_y = window_y + 5;
+
+ wlcs::Client client{the_server()};
+ wlcs::Surface surface{client};
+ wlcs::XdgSurfaceV6 xdg_shell_surface{client, surface};
+ wlcs::XdgToplevelV6 toplevel{xdg_shell_surface};
+ surface.attach_buffer(window_width, window_height);
+ wl_surface_commit(surface);
+ client.roundtrip();
+
+ the_server().move_surface_to(surface, window_x, window_y);
+
+ auto pointer = the_server().create_pointer();
+
+ bool button_down{false};
+ uint32_t last_serial{0};
+
+ client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+ last_serial = serial;
+ button_down = is_down;
+ return true;
+ });
+
+ pointer.move_to(start_x, start_y);
+ pointer.left_button_down();
+
+ client.dispatch_until([&](){
+ return button_down;
+ });
+
+ zxdg_toplevel_v6_resize(toplevel, client.seat(), last_serial, ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT);
+ client.dispatch_until([&](){
+ return !client.window_under_cursor();
+ });
+}
+
+TEST_F(XdgToplevelV6Test, parent_can_be_set)
+{
+ const int window_pos_x = 200, window_pos_y = 280;
+
+ wlcs::Client client{the_server()};
+
+ ConfigurationWindow parent{client};
+ the_server().move_surface_to(parent.surface, window_pos_x, window_pos_y);
+
+ ConfigurationWindow child{client};
+ the_server().move_surface_to(child.surface, window_pos_x, window_pos_y);
+
+ zxdg_toplevel_v6_set_parent(child, parent);
+ wl_surface_commit(child.surface);
+ client.roundtrip();
+}
+
+TEST_F(XdgToplevelV6Test, null_parent_can_be_set)
+{
+ const int window_pos_x = 200, window_pos_y = 280;
+
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ the_server().move_surface_to(window.surface, window_pos_x, window_pos_y);
+
+ zxdg_toplevel_v6_set_parent(window, nullptr);
+ wl_surface_commit(window.surface);
+ client.roundtrip();
+}
+
+// TODO: interactive resize
+// This would probably make sense as a parameterized test, with resizing in all directions
+// Like move, resize is not implemented in the current WLCS window manager, and should not be tested until it is
+
+using XdgToplevelV6ConfigurationTest = wlcs::InProcessServer;
+
+TEST_F(XdgToplevelV6ConfigurationTest, defaults)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ // default values
+ EXPECT_THAT(state.width, Eq(0));
+ EXPECT_THAT(state.height, Eq(0));
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, window_can_maximize_itself)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ zxdg_toplevel_v6_set_maximized(window);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.width, Gt(0));
+ EXPECT_THAT(state.height, Gt(0));
+ EXPECT_THAT(state.maximized, Eq(true));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, window_can_unmaximize_itself)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ zxdg_toplevel_v6_set_maximized(window);
+ window.dispatch_until_configure();
+
+ ASSERT_THAT(state.maximized, Eq(true)) << "test could not run as precondition failed";
+
+ zxdg_toplevel_v6_unset_maximized(window);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, window_can_fullscreen_itself)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ zxdg_toplevel_v6_set_fullscreen(window, nullptr);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.width, Gt(0));
+ EXPECT_THAT(state.height, Gt(0));
+ EXPECT_THAT(state.maximized, Eq(false)); // is this right? should it not be maximized, even when fullscreen?
+ EXPECT_THAT(state.fullscreen, Eq(true));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, window_can_unfullscreen_itself)
+{
+ wlcs::Client client{the_server()};
+ ConfigurationWindow window{client};
+ auto& state = window.state;
+
+ zxdg_toplevel_v6_set_fullscreen(window, nullptr);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.fullscreen, Eq(true)) << "test could not run as precondition failed";
+
+ zxdg_toplevel_v6_unset_fullscreen(window);
+ window.dispatch_until_configure();
+
+ EXPECT_THAT(state.maximized, Eq(false));
+ EXPECT_THAT(state.fullscreen, Eq(false));
+ EXPECT_THAT(state.resizing, Eq(false));
+ EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, activated_state_follows_pointer)
+{
+ wlcs::Client client{the_server()};
+
+ ConfigurationWindow window_a{client};
+ auto& state_a = window_a.state;
+ int const a_x = 12, a_y = 15;
+ the_server().move_surface_to(window_a, a_x, a_y);
+
+ ConfigurationWindow window_b{client};
+ auto& state_b = window_b.state;
+ int const b_x = a_x + window_a.window_width + 27, b_y = 15;
+ the_server().move_surface_to(window_b, b_x, b_y);
+
+ auto pointer = the_server().create_pointer();
+
+ pointer.move_to(a_x + 10, a_y + 10);
+ pointer.left_click();
+ client.roundtrip();
+
+ ASSERT_THAT(state_a.activated, Eq(true));
+ ASSERT_THAT(state_b.activated, Eq(false));
+
+ pointer.move_to(b_x + 10, b_y + 10);
+ pointer.left_click();
+ client.roundtrip();
+
+ EXPECT_THAT(state_a.activated, Eq(false));
+ EXPECT_THAT(state_b.activated, Eq(true));
+}
--- /dev/null
+#!/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"
+
--- /dev/null
+#!/bin/bash
+
+set -e
+
+if [ -z "${RELEASE}" ]; then
+ echo "ERROR: RELEASE environment variable needs to be set to the" >&2
+ echo " target Ubuntu version." >&2
+ exit 1
+fi
+
+GIT_BRANCH=${GITHUB_REF-$( git rev-parse --abbrev-ref HEAD )}
+# determine the patch release
+if ! [[ "${GIT_BRANCH}" =~ ^(refs/(heads|tags)/)?(master|(release/|v)([0-9\.]+))$ ]]; then
+ echo "ERROR: This script should only run on master or release tags" >&2
+ echo " or branches." >&2
+ exit 3
+fi
+
+source <( python <<EOF
+import os
+from launchpadlib.launchpad import Launchpad
+
+lp = Launchpad.login_anonymously("mir-ci",
+ "production",
+ version="devel")
+
+ubuntu = lp.distributions["ubuntu"]
+series = ubuntu.getSeries(name_or_version=os.environ['RELEASE'])
+
+print("UBUNTU_SERIES={}".format(series.name))
+print("UBUNTU_VERSION={}".format(series.version))
+EOF
+)
+
+if [ -z "${UBUNTU_SERIES}" -o -z "${UBUNTU_VERSION}" ]; then
+ echo "ERROR: \"${RELEASE}\" was not recognized as a valid Ubuntu series" >&2
+ exit 2
+fi
+
+GIT_REVISION=$( git rev-parse --short HEAD )
+
+if [[ "${GIT_BRANCH}" =~ ^(refs/(heads|tags)/)?(release/|v)([0-9\.]+)$ ]]; then
+ # we're on a release branch
+ TARGET_PPA=ppa:mir-team/wlcs-rc
+ WLCS_SERIES=${BASH_REMATCH[4]}
+ if [[ "$( git describe --tags --exact-match )" =~ ^v([0-9\.]+)$ ]]; then
+ # this is a final release, use the tag version
+ WLCS_VERSION=${BASH_REMATCH[1]}
+ else
+ # find the last tagged patch version
+ PATCH_VERSION=$( git describe --abbrev=0 --match "v${WLCS_SERIES}*" \
+ 2> /dev/null | sed 's/^v//')
+ if [ -z "${PATCH_VERSION}" ]; then
+ # start with patch version 0
+ WLCS_VERSION=${WLCS_SERIES}.0
+ else
+ # increment the patch version
+ WLCS_VERSION=$( echo ${PATCH_VERSION} | perl -pe 's/^((\d+\.)*)(\d+)$/$1.($3+1)/e' )
+ fi
+
+ # use the number of commits since master
+ GIT_COMMITS=$( git rev-list --count origin/master..HEAD )
+ WLCS_VERSION=${WLCS_VERSION}~rc${GIT_COMMITS}.git${GIT_REVISION}
+ fi
+else
+ # look for a release tag within parents 2..n
+ PARENT=2
+ while git rev-parse HEAD^${PARENT} >/dev/null 2>&1; do
+ if [[ "$( git describe --exact-match HEAD^${PARENT} )" =~ ^v([0-9\.]+)$ ]]; then
+ # copy packages from ppa:mir-team/wlcs-rc to ppa:mir-team/wlcs
+ RELEASE_VERSION=${BASH_REMATCH[1]}-0ubuntu${UBUNTU_VERSION}
+ echo "Copying wlcs_${RELEASE_VERSION} from ppa:mir-team/wlcs-rc to ppa:mir-team/wlcs…"
+ python - ${RELEASE_VERSION} <<EOF
+import os
+import sys
+
+from launchpadlib.credentials import (RequestTokenAuthorizationEngine,
+ UnencryptedFileCredentialStore)
+from launchpadlib.launchpad import Launchpad
+
+try:
+ lp = Launchpad.login_with(
+ "mir-ci",
+ "production",
+ version="devel",
+ authorization_engine=RequestTokenAuthorizationEngine("production",
+ "mir-ci"),
+ credential_store=UnencryptedFileCredentialStore(
+ os.path.expanduser("~/.launchpadlib/credentials")
+ )
+ )
+except NotImplementedError:
+ raise RuntimeError("Invalid credentials.")
+
+ubuntu = lp.distributions["ubuntu"]
+series = ubuntu.getSeries(name_or_version=os.environ['RELEASE'])
+
+mir_team = lp.people["mir-team"]
+rc_ppa = mir_team.getPPAByName(name="wlcs-rc")
+release_ppa = mir_team.getPPAByName(name="wlcs")
+
+release_ppa.copyPackage(source_name="wlcs",
+ version=sys.argv[1],
+ from_archive=rc_ppa,
+ from_series=series.name,
+ from_pocket="Release",
+ to_series=series.name,
+ to_pocket="Release",
+ include_binaries=True)
+
+EOF
+ break
+ fi
+ PARENT=$(( ${PARENT} + 1 ))
+ done
+
+ # upload to dev PPA
+ TARGET_PPA=ppa:mir-team/wlcs-dev
+ GIT_VERSION=$( git describe | sed 's/^v//' )
+ WLCS_VERSION=${GIT_VERSION/-/+dev}
+fi
+
+PPA_VERSION=${WLCS_VERSION}-0ubuntu${UBUNTU_VERSION}
+
+echo "Setting version to:"
+echo " ${PPA_VERSION}"
+
+debchange \
+ --newversion ${PPA_VERSION} \
+ --force-bad-version \
+ "Automatic build of revision ${GIT_REVISION}"
+
+debchange \
+ --release \
+ --distribution ${UBUNTU_SERIES} \
+ --force-distribution \
+ "" # required for debchange to not open an editor
+
+git archive --format=tar --prefix=wlcs-${WLCS_VERSION}/ HEAD \
+ | xz -9 > ../wlcs_${WLCS_VERSION}.orig.tar.xz
+
+dpkg-buildpackage \
+ -I".git" \
+ -I"build" \
+ -i"^.git|^build" \
+ -d -S
+
+dput ${TARGET_PPA} ../wlcs_${PPA_VERSION}_source.changes
--- /dev/null
+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}