pax_global_header00006660000000000000000000000064145365735750014536gustar00rootroot0000000000000052 comment=111dc970660e08654ec9532ac22ba62efedf204a wlcs-1.7.0/000077500000000000000000000000001453657357500125135ustar00rootroot00000000000000wlcs-1.7.0/.github/000077500000000000000000000000001453657357500140535ustar00rootroot00000000000000wlcs-1.7.0/.github/workflows/000077500000000000000000000000001453657357500161105ustar00rootroot00000000000000wlcs-1.7.0/.github/workflows/ppa-upload.yml000066400000000000000000000022741453657357500207020ustar00rootroot00000000000000name: PPA Upload on: push: branches: - main - release/[0-9]+.[0-9]+ tags: - v[0-9]+.[0-9]+.[0-9]+ jobs: PPAUpload: strategy: fail-fast: true matrix: release: - "22.04" - "23.04" - "23.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@v3 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: Set up Launchpad credentials uses: DamianReeves/write-file-action@v1.0 with: path: lp_credentials contents: ${{ secrets.LAUNCHPAD_CREDENTIALS }} - name: Upload to PPA env: RELEASE: ${{ matrix.release }} run: tools/ppa-upload.sh lp_credentials wlcs-1.7.0/.github/workflows/spread.yml000066400000000000000000000025621453657357500201160ustar00rootroot00000000000000name: Spread concurrency: group: ${{ github.workflow }}-${{ github.event.number && format('pr{0}', github.event.number) || github.run_id }} cancel-in-progress: true on: push: branches: - main - release/[0-9]+.[0-9]+ tags: - v[0-9]+[0-9]+.[0-9]+ merge_group: types: [checks_requested] pull_request: types: [opened, synchronize, reopened, ready_for_review] jobs: BuildAndTest: strategy: fail-fast: false matrix: spread-task: - lxd:ubuntu-22.04:...:gcc - lxd:ubuntu-23.04:...:gcc - lxd:ubuntu-23.10:...:gcc - lxd:ubuntu-23.10:...:clang - lxd:fedora-37:...:gcc - lxd:fedora-38:...:gcc - lxd:fedora-39:...:gcc - lxd:fedora-rawhide:...:gcc - lxd:alpine-3.18:...:gcc - lxd:ubuntu-devel:...:gcc - lxd:ubuntu-devel:...:clang - lxd:alpine-edge:...:gcc runs-on: ubuntu-latest env: DEBFULLNAME: "Mir CI Bot" DEBEMAIL: "mir-ci-bot@canonical.com" steps: - name: Set up LXD uses: canonical/setup-lxd@main - name: Set up Spread run: | set -euo pipefail sudo snap install spread-mir-ci sudo snap run lxd init --auto - name: Check out code uses: actions/checkout@v3 - name: Run Spread task run: snap run spread-mir-ci.spread -v ${{ matrix.spread-task }} wlcs-1.7.0/.github/workflows/tarball.yml000066400000000000000000000027451453657357500202640ustar00rootroot00000000000000name: Build a tarball on: push: tags: - v[0-9]+.[0-9]+.[0-9]+ workflow_dispatch: inputs: version: description: The version to build the tarball for required: true jobs: BuildTarball: runs-on: ubuntu-latest steps: - name: Determine version to build id: get-version run: | if ${{ github.event_name == 'push' }}; then version="$( echo '${{ github.ref }}' | sed 's@^refs/tags/v@@' )" else version='${{ inputs.version }}' fi # assert it's a version number set -x [[ ${version} =~ ^[0-9]+.[0-9]+.[0-9]+$ ]] && echo "version=${version}" >> $GITHUB_OUTPUT - name: Import GPG key uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.MIR_BOT_GPG_PRIVATE_KEY }} - name: Check out code uses: actions/checkout@v3 with: ref: v${{ steps.get-version.outputs.version }} - name: Build the tarball run: | ./tools/make_release_tarball --skip-checks gpg --detach-sig --sign --output wlcs-${{ steps.get-version.outputs.version }}.tar.xz.asc wlcs-${{ steps.get-version.outputs.version }}.tar.xz - name: Store the tarball and signature uses: actions/upload-artifact@v3 with: name: wlcs-${{ steps.get-version.outputs.version }} path: | wlcs-${{ steps.get-version.outputs.version }}.tar.xz wlcs-${{ steps.get-version.outputs.version }}.tar.xz.asc wlcs-1.7.0/.gitignore000066400000000000000000000000431453657357500145000ustar00rootroot00000000000000.idea/ /build/ /cmake-build-debug/ wlcs-1.7.0/CMakeLists.txt000066400000000000000000000237031453657357500152600ustar00rootroot00000000000000# Copyright © 2017 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Authored by: Thomas Voss , # Alan Griffiths , # Christopher James Halse Rogers cmake_minimum_required(VERSION 3.5) cmake_policy(SET CMP0015 NEW) cmake_policy(SET CMP0022 NEW) project(wlcs VERSION 1.7.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 20) 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) GENERATE_PROTOCOL(text-input-unstable-v2) GENERATE_PROTOCOL(text-input-unstable-v3) GENERATE_PROTOCOL(input-method-unstable-v1) GENERATE_PROTOCOL(input-method-unstable-v2) GENERATE_PROTOCOL(wlr-virtual-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_TESTS 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 tests/text_input_v3_with_input_method_v2.cpp tests/wlr_virtual_pointer_v1.cpp tests/text_input_v2_with_input_method_v1.cpp ) 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} ${WLCS_TESTS} ) # g++ 9.4 (on 20.04) hates the MOCK_METHOD macro if (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10) # Neither gnu-zero-variadic-macro-arguments nor variadic-macro help, # we just have to drop pedantic... set_property(SOURCE ${WLCS_TESTS} PROPERTY COMPILE_OPTIONS -Wno-pedantic) endif() 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_INCLUDEDIR} ) include(JoinPaths) join_paths(PKGCONFIG_BINDIR "\${prefix}" "${CMAKE_INSTALL_BINDIR}") join_paths(PKGCONFIG_LIBEXECDIR "\${prefix}" "${CMAKE_INSTALL_LIBEXECDIR}") join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") 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/ ) wlcs-1.7.0/COPYING.GPL2000066400000000000000000000432541453657357500142610ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. wlcs-1.7.0/COPYING.GPL3000066400000000000000000001043741453657357500142630ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . wlcs-1.7.0/README.rst000066400000000000000000000044611453657357500142070ustar00rootroot00000000000000============================== Wayland Conformance Test Suite ============================== ``wlcs`` aspires to be a protocol-conformance-verifying test suite usable by Wayland compositor implementors. It is growing out of porting the existing Weston test suite to be run in `Mir's `_ test suite, but it is designed to be usable by any compositor. There have been a number of previous attempts at a Wayland compositor test suite - the `Wayland Functional Integration Test Suite `_, and the `Weston test suite `_ are a couple of examples. What sets ``wlcs`` apart from the graveyard of existing test suites is its integration method. Previous test suites have used a Wayland protocol extension to interrogate the compositor state. This obviously requires that compositors implement that protocol, means that tests only have access to information provided by that protocol, and the IPC means that there isn't a canonical happens-before ordering available. Instead, ``wlcs`` relies on compositors providing an integration module, providing ``wlcs`` with API hooks to start a compositor, connect a client, move a window, and so on. This makes both writing and 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. wlcs-1.7.0/cmake/000077500000000000000000000000001453657357500135735ustar00rootroot00000000000000wlcs-1.7.0/cmake/FindGtestGmock.cmake000066400000000000000000000053221453657357500174470ustar00rootroot00000000000000include(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() # 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() find_file(GMOCK_SOURCE NAMES gmock-all.cc DOC "GMock source" PATHS /usr/src/googletest/googlemock/src/ /usr/src/gmock/ /usr/src/gmock/src) if (EXISTS ${GMOCK_SOURCE}) find_path(GMOCK_INCLUDE_DIR gmock/gmock.h PATHS /usr/src/googletest/googlemock/include) add_library(GMock STATIC ${GMOCK_SOURCE}) if (EXISTS /usr/src/googletest/googlemock/src) set_source_files_properties(${GMOCK_SOURCE} PROPERTIES COMPILE_FLAGS "-I/usr/src/googletest/googlemock") endif() if (EXISTS /usr/src/gmock/src) set_source_files_properties(${GMOCK_SOURCE} PROPERTIES COMPILE_FLAGS "-I/usr/src/gmock") endif() find_package_handle_standard_args(GMock DEFAULT_MSG GMOCK_INCLUDE_DIR) set(GMOCK_LIBRARY GMock) else() # Assume gmock is no longer source, we'll find out soon enough if that's wrong add_custom_target(GMock) string(REPLACE gtest gmock GMOCK_LIBRARY ${GTEST_LIBRARY}) endif() set(GMOCK_LIBRARIES ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARY}) wlcs-1.7.0/cmake/JoinPaths.cmake000066400000000000000000000014611453657357500164760ustar00rootroot00000000000000# This module provides function for joining paths # known from most languages # # SPDX-License-Identifier: (MIT OR CC0-1.0) # Copyright 2020 Jan Tojnar # https://github.com/jtojnar/cmake-snips # # Modelled after Python’s os.path.join # https://docs.python.org/3.7/library/os.path.html#os.path.join # Windows not supported function(join_paths joined_path first_path_segment) set(temp_path "${first_path_segment}") foreach(current_segment IN LISTS ARGN) if(NOT ("${current_segment}" STREQUAL "")) if(IS_ABSOLUTE "${current_segment}") set(temp_path "${current_segment}") else() set(temp_path "${temp_path}/${current_segment}") endif() endif() endforeach() set(${joined_path} "${temp_path}" PARENT_SCOPE) endfunction() wlcs-1.7.0/debian/000077500000000000000000000000001453657357500137355ustar00rootroot00000000000000wlcs-1.7.0/debian/changelog000066400000000000000000000124441453657357500156140ustar00rootroot00000000000000wlcs (1.7.0-0ubuntu0) UNRELEASED; urgency=medium * New upstream release. Notable changes: + New tests for input-method-v1 (#302) + Handle incomplete logical pointer/touch events better (#313) + XdgToplevelStable: Fix race in .configure handling (#318) + helpers: avoid triggering a kernel warning (#320) + InProcessServer: Fix xdg_shell window construction (#324) + XdgSurfaceStable: Fix configure event logic -- Michał Sawicz Tue, 05 Dec 2023 13:52:28 +0100 wlcs (1.6.1-0ubuntu0) UNRELEASED; urgency=medium * Bump project version in CMakeLists.txt -- Michał Sawicz Tue, 10 Oct 2023 12:14:27 +0200 wlcs (1.6.0-0ubuntu0) UNRELEASED; urgency=medium * New upstream release. Notable changes: + Update wayland.xml to latest + XDG stable: use mock methods instead of notification lists + Test popup constraint_adjustment + Tests for XDG shell (stable) version 5 + Test that text input is entered after child window is closed + Copy geometry headers from Mir + Test popups are dismissed in the correct order + Test buffer can be deleted after it's attached + Less flaky synchronization for VirtualPointerV1Test (#297, #294) + VirtualPointerV1Test: no-events-sent test fails if events are sent (#296) + Fix various frame-event misunderstandings + Change remaining std::experimental::optionals to std::optional -- Alan Griffiths Mon, 17 Jul 2023 13:12:00 +0100 wlcs (1.5.0-0ubuntu0) UNRELEASED; urgency=medium * New upstream release. Notable changes: + Tests for zwlr_virtual_pointer_v1 + Use maximum shared version when binding globals (Fixes #234) + Make zxdg_shell_v6 ExpectedlyNotSupported if not in supported_extensions (Fixes #237) + Destroy xdg_toplevel before xdg_surface on cleanup + Fixup CMakeLists.txt so tests can use MOCK_METHOD + c++20 + Fix BadBufferTest.test_truncated_shm_file protocol error + Fix CMake install dir usage in pkgconfig, honour CMAKE_INSTALL_INCLUDEDIR -- Alan Griffiths Fri, 6 Jan 2023 11:40:13 +0000 wlcs (1.4.0-0ubuntu0) UNRELEASED; urgency=medium * New upstream release. Notable changes: + Add tests for zwp_text_input_unstable_v3 + Add tests for zwp_input_method_unstable_v2 + Add tests for zwlr_layer_shell_v1 version 4 + Drop requriement for compositors to implement wl_shell. Tests which require an arbitrary "visible surface" should now only require one of xdg_shell, xdg_shell_unstable_v6, or wl_shell. + Fix expectations of keyboard focus for xdg_popup tests to match the protocol. NOTE: These tests will fail on many compositors, including Weston, as is is common to not follow the protocol correctly here. -- Christopher James Halse Rogers Fri, 25 Feb 2022 15:33:24 +1100 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 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 Fri, 19 Feb 2021 11:11:11 +0000 wlcs (1.2.0-0ubuntu0) groovy; urgency=medium * New upstream release. Notable changes: + Add tests for wlr_layer_shell_unstable_v1 + Build fixes for Musl libc; WLCS now builds on Alpine. Thanks, Luca Weiss + More XDG Shell tests, particularly around protocol errors, window-geometry, and input edge-cases. + Add tests for wlr_foreign_toplevel_management_unstable_v1 + Many improvements to wl_subsurface tests. Notably this fixes a misinterpretation of the protocol which lead to testing incorrect behaviour. -- Christopher James Halse Rogers Mon, 31 Aug 2020 16:39:21 +1000 wlcs (1.1.0-0ubuntu0) eoan; urgency=medium * New upstream release. Relevant upstream changes: + Document the compositor-integration version macros + Add tests for the wl_output protocol + More tests for XDG Shell, particularly around popups and window movement + Add tests for wp_primary_selection_unstable_v1 protocol + Add tests for gdk_primary_selection protocol + Lots of build fixes for !Ubuntu systems. Thanks, Neal Gompa! -- Christopher James Halse Rogers Tue, 23 Jul 2019 10:37:52 +1000 wlcs (1.0-0ubuntu0) disco; urgency=medium * Initial release -- Christopher James Halse Rogers Tue, 08 Jan 2019 11:32:06 +1100 wlcs-1.7.0/debian/compat000066400000000000000000000000031453657357500151340ustar00rootroot0000000000000010 wlcs-1.7.0/debian/control000066400000000000000000000015611453657357500153430ustar00rootroot00000000000000Source: wlcs Priority: optional Maintainer: Christopher James Halse Rogers Build-Depends: debhelper (>= 9), cmake, libboost-dev, libgtest-dev, libwayland-dev, pkg-config, google-mock Standards-Version: 4.1.3 Section: devel Homepage: https://github.com/MirServer/wlcs Vcs-Browser: https://salsa.debian.org/mir-server-team/wlcs Vcs-Git: https://salsa.debian.org/mir-server-team/wlcs.git Package: wlcs Section: devel Architecture: any Multi-Arch: same Depends: ${misc:Depends}, ${shlibs:Depends} Description: Wayland Conformance Suite's wlcs aspires to be a protocol-conformance-verifying test suite usable by Wayland compositor implementations. . This package contains the headers necessary for a Wayland compositor to provide the integration module needed to run wlcs tests, and the test runner binary needed to run the tests against the compositor. wlcs-1.7.0/debian/copyright000066400000000000000000000056641453657357500157030ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: wlcs Source: https://github.com/MirServer/wlcs Files: * Copyright: 2017-2019 Canonical, Ltd License: GPL-3 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . On Debian systems, the full text of the GNU General Public License version 3 can be found in the file `/usr/share/common-licenses/GPL-3'. Files: tests/test_bad_buffer.cpp tests/test_surface_events.cpp xdg_popup_stable.cpp xdg_popup_v6.cpp xdg_surface_stable.cpp xdg_surface_v6.cpp xdg_toplevel_stable.cpp xdg_toplevel_v6.cpp Copyright: 2012 Intel Corporation 2013 Collabora, Ltd. 2017-2018 Canonical Ltd. License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Files: debian/* Copyright: 2019 Christopher James Halse Rogers License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". wlcs-1.7.0/debian/rules000077500000000000000000000021261453657357500150160ustar00rootroot00000000000000#!/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 ifneq ($(filter i386 armhf, $(DEB_HOST_ARCH)),) # i386 and armhf do not have tsan COMMON_CONFIGURE_OPTIONS += -DWLCS_BUILD_TSAN=OFF endif ifeq ($(DEB_HOST_ARCH), riscv64) # riscv64 does not have sanitizers ifeq ($(DEB_DISTRIBUTION), focal) COMMON_CONFIGURE_OPTIONS += -DWLCS_BUILD_ASAN=OFF endif COMMON_CONFIGURE_OPTIONS += -DWLCS_BUILD_UBSAN=OFF COMMON_CONFIGURE_OPTIONS += -DWLCS_BUILD_TSAN=OFF endif # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329 ifeq ($(DEB_HOST_ARCH), ppc64el) ifneq ($(shell gcc --version | grep '1[23].[[:digit:]]\+.[[:digit:]]\+$$'),) export DEB_CFLAGS_MAINT_APPEND = -O2 export DEB_CXXFLAGS_MAINT_APPEND = -O2 export DEB_FCFLAGS_MAINT_APPEND = -O2 export DEB_FFLAGS_MAINT_APPEND = -O2 endif endif override_dh_auto_configure: dh_auto_configure -- $(COMMON_CONFIGURE_OPTIONS) %: dh $@ wlcs-1.7.0/debian/source/000077500000000000000000000000001453657357500152355ustar00rootroot00000000000000wlcs-1.7.0/debian/source/format000066400000000000000000000000041453657357500164420ustar00rootroot000000000000001.0 wlcs-1.7.0/debian/upstream/000077500000000000000000000000001453657357500155755ustar00rootroot00000000000000wlcs-1.7.0/debian/upstream/signing-key.asc000066400000000000000000000560151453657357500205200ustar00rootroot00000000000000-----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----- wlcs-1.7.0/debian/watch000066400000000000000000000002631453657357500147670ustar00rootroot00000000000000# 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 wlcs-1.7.0/example/000077500000000000000000000000001453657357500141465ustar00rootroot00000000000000wlcs-1.7.0/example/mir_integration.cpp000066400000000000000000000655471453657357500200650ustar00rootroot00000000000000/* * Copyright © 2017 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "display_server.h" #include "pointer.h" #include "touch.h" #include "mutex.h" #include "mir/fd.h" #include "mir/server.h" #include "mir/options/option.h" #include "mir_test_framework/async_server_runner.h" #include "mir_test_framework/headless_display_buffer_compositor_factory.h" #include "mir_test_framework/executable_path.h" #include "mir_test_framework/stub_server_platform_factory.h" #include "mir_test_framework/fake_input_device.h" #include "mir/input/device_capability.h" #include "mir/input/input_device_info.h" #include "mir/test/doubles/mock_gl.h" #include "mir/frontend/session.h" #include "mir/scene/session_listener.h" #include "mir/scene/surface.h" #include "mir/scene/session.h" #include "mir/test/signal.h" namespace mtf = mir_test_framework; namespace mi = mir::input; namespace mf = mir::frontend; using namespace std::chrono_literals; namespace std { // std::chrono::nanoseconds doesn't have a standard hash<> implementation?! template<> struct hash<::std::chrono::nanoseconds> { typedef std::chrono::nanoseconds argument_type; typedef size_t result_type; result_type operator()(argument_type const& arg) const noexcept { /* * The underlying type of std::chrono::nanoseconds is guaranteed to be an * integer of at least 50-something bits, which will already have a perfectly * functional std::hash<> implementation… */ return std::hash{}(arg.count()); } }; } namespace { auto constexpr a_long_time = 5s; using ClientFd = int; class ResourceMapper : public mir::scene::SessionListener { public: ResourceMapper() : listeners{&this->state} { } void starting(std::shared_ptr const&) override { } void stopping(std::shared_ptr const&) override { } void focused(std::shared_ptr const&) override { } void unfocused() override { } void surface_created( mir::scene::Session&, std::shared_ptr const& surface) override { auto state_accessor = state.lock(); if (std::this_thread::get_id() == state_accessor->wayland_thread) { if (listeners.last_wl_window == nullptr) { BOOST_THROW_EXCEPTION(( std::runtime_error{ "Called Shell::create_surface() without first creating a wl_shell_surface?"})); } auto stream = surface->primary_buffer_stream(); auto wl_surface = state_accessor->stream_map.at(stream); state_accessor->surface_map[wl_surface] = surface; } } void destroying_surface( mir::scene::Session&, std::shared_ptr const&) override { // TODO: Maybe delete from map? } void buffer_stream_created( mir::scene::Session&, std::shared_ptr const& stream) override { auto state_accessor = state.lock(); if (std::this_thread::get_id() == state_accessor->wayland_thread) { if (listeners.last_wl_surface == nullptr) { BOOST_THROW_EXCEPTION(( std::runtime_error{"BufferStream created without first constructing a wl_surface?"})); } state_accessor->stream_map[stream] = listeners.last_wl_surface; listeners.last_wl_surface = nullptr; } } void buffer_stream_destroyed( mir::scene::Session&, std::shared_ptr const& stream) override { state.lock()->stream_map.erase(stream); } void init(wl_display* display) { state.lock()->wayland_thread = std::this_thread::get_id(); listeners.client_listener.notify = &client_created; wl_display_add_client_created_listener(display, &listeners.client_listener); } std::weak_ptr surface_for_resource(wl_resource* surface) { if (strcmp(wl_resource_get_class(surface), "wl_surface") != 0) { BOOST_THROW_EXCEPTION(( std::logic_error{ std::string{"Expected a wl_surface, got: "} + wl_resource_get_class(surface) })); } auto state_accessor = state.lock(); return state_accessor->surface_map.at(surface); } wl_client* client_for_fd(int client_socket) { return listeners.state->lock()->client_session_map.at(client_socket); } void associate_client_socket(int client_socket) { auto state_accessor = state.wait_for( [](State& state) { return static_cast(state.latest_client) ; }, std::chrono::seconds{30}); state_accessor->client_session_map[client_socket] = state_accessor->latest_client.value(); state_accessor->latest_client = {}; } private: struct Listeners; struct ResourceListener { ResourceListener(Listeners* const listeners) : listeners{listeners} { } wl_listener resource_listener; Listeners* const listeners; }; struct State { std::thread::id wayland_thread; std::unordered_map> surface_map; std::unordered_map, wl_resource*> stream_map; std::optional latest_client; std::unordered_map client_session_map; std::unordered_map resource_listener; }; wlcs::WaitableMutex state; struct Listeners { Listeners(wlcs::WaitableMutex* const state) : state{state} { } wl_listener client_listener; wl_resource* last_wl_surface{nullptr}; wl_resource* last_wl_window{nullptr}; wlcs::WaitableMutex* const state; } listeners; static void resource_created(wl_listener* listener, void* ctx) { auto resource = static_cast(ctx); ResourceListener* resource_listener; resource_listener = wl_container_of(listener, resource_listener, resource_listener); bool const is_surface = strcmp( wl_resource_get_class(resource), "wl_surface") == 0; bool const is_window = strcmp( wl_resource_get_class(resource), "wl_shell_surface") == 0 || strcmp( wl_resource_get_class(resource), "zxdg_surface_v6") == 0; if (is_surface) { resource_listener->listeners->last_wl_surface = resource; } else if (is_window) { resource_listener->listeners->last_wl_window = resource; } } static void client_created(wl_listener* listener, void* ctx) { auto client = static_cast(ctx); Listeners* listeners; listeners = wl_container_of(listener, listeners, client_listener); wl_listener* resource_listener; { auto state_accessor = listeners->state->lock(); state_accessor->latest_client = client; auto rl = state_accessor->resource_listener.emplace(client, listeners); rl.first->second.resource_listener.notify = &resource_created; resource_listener = &rl.first->second.resource_listener; } listeners->state->notify_all(); wl_client_add_resource_created_listener(client, resource_listener); } }; namespace { class WaylandExecutor : public mir::Executor { public: void spawn (std::function&& work) override { { std::lock_guard lock{mutex}; workqueue.emplace_back(std::move(work)); } if (auto err = eventfd_write(notify_fd, 1)) { BOOST_THROW_EXCEPTION((std::system_error{err, std::system_category(), "eventfd_write failed to notify event loop"})); } } /** * Get an Executor which dispatches onto a wl_event_loop * * \note The executor may outlive the wl_event_loop, but no tasks will be dispatched * after the wl_event_loop is destroyed. * * \param [in] loop The event loop to dispatch on * \return An Executor that queues onto the wl_event_loop */ static std::shared_ptr executor_for_event_loop(wl_event_loop* loop) { if (auto notifier = wl_event_loop_get_destroy_listener(loop, &on_display_destruction)) { DestructionShim* shim; shim = wl_container_of(notifier, shim, destruction_listener); return shim->executor; } else { auto const executor = std::shared_ptr{new WaylandExecutor{loop}}; auto shim = std::make_unique(executor); shim->destruction_listener.notify = &on_display_destruction; wl_event_loop_add_destroy_listener(loop, &(shim.release())->destruction_listener); return executor; } } private: WaylandExecutor(wl_event_loop* loop) : notify_fd{eventfd(0, EFD_CLOEXEC | EFD_SEMAPHORE | EFD_NONBLOCK)}, notify_source{wl_event_loop_add_fd(loop, notify_fd, WL_EVENT_READABLE, &on_notify, this)} { if (notify_fd == mir::Fd::invalid) { BOOST_THROW_EXCEPTION((std::system_error{ errno, std::system_category(), "Failed to create IPC pause notification eventfd"})); } } std::function get_work() { std::lock_guard lock{mutex}; if (!workqueue.empty()) { auto const work = std::move(workqueue.front()); workqueue.pop_front(); return work; } return {}; } static int on_notify(int fd, uint32_t, void* data) { auto executor = static_cast(data); eventfd_t unused; if (auto err = eventfd_read(fd, &unused)) { mir::log( mir::logging::Severity::error, "wlcs-integration", "eventfd_read failed to consume wakeup notification: %s (%i)", strerror(err), err); } std::unique_lock lock{executor->mutex}; while (auto work = executor->get_work()) { try { work(); } catch(...) { mir::log( mir::logging::Severity::critical, "wlcs-integration", std::current_exception(), "Exception processing Wayland event loop work item"); } } return 0; } static void on_display_destruction(wl_listener* listener, void*) { DestructionShim* shim; shim = wl_container_of(listener, shim, destruction_listener); { std::lock_guard lock{shim->executor->mutex}; wl_event_source_remove(shim->executor->notify_source); } delete shim; } std::recursive_mutex mutex; mir::Fd const notify_fd; std::deque> workqueue; wl_event_source* const notify_source; struct DestructionShim { explicit DestructionShim(std::shared_ptr const& executor) : executor{executor} { } std::shared_ptr const executor; wl_listener destruction_listener; }; static_assert( std::is_standard_layout::value, "DestructionShim must be Standard Layout for wl_container_of to be defined behaviour"); }; } class InputEventListener; struct MirWlcsDisplayServer : mtf::AsyncServerRunner { testing::NiceMock mockgl; std::shared_ptr const resource_mapper{std::make_shared()}; std::shared_ptr event_listener; std::shared_ptr executor; std::atomic cursor_x{0}, cursor_y{0}; mir::test::Signal started; }; class InputEventListener : public mir::input::SeatObserver { public: InputEventListener(MirWlcsDisplayServer& runner) : runner{runner} { } std::shared_ptr expect_event_with_time( std::chrono::nanoseconds event_time) { auto done_signal = std::make_shared(); expected_events.lock()->insert(std::make_pair(event_time, done_signal)); return done_signal; } void seat_add_device(uint64_t /*id*/) override { } void seat_remove_device(uint64_t /*id*/) override { } void seat_dispatch_event(std::shared_ptr const& event) override { auto iev = mir_event_get_input_event(event.get()); auto event_time = std::chrono::nanoseconds{mir_input_event_get_event_time(iev)}; auto expected_events_accessor = expected_events.lock(); if (expected_events_accessor->count(event_time)) { expected_events_accessor->at(event_time)->raise(); expected_events_accessor->erase(event_time); } } void seat_set_key_state( uint64_t /*id*/, std::vector const& /*scan_codes*/) override { } void seat_set_pointer_state( uint64_t /*id*/, unsigned /*buttons*/) override { } void seat_set_cursor_position( float cursor_x, float cursor_y) override { runner.cursor_x = cursor_x; runner.cursor_y = cursor_y; } void seat_set_confinement_region_called( mir::geometry::Rectangles const& /*regions*/) override { } void seat_reset_confinement_regions() override { } private: wlcs::Mutex>> expected_events; MirWlcsDisplayServer& runner; }; template void emit_mir_event(MirWlcsDisplayServer* runner, mir::UniqueModulePtr& emitter, T event) { auto event_time = std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()); auto event_sent = runner->event_listener->expect_event_with_time(event_time); emitter->emit_event(event.with_event_time(event_time)); EXPECT_THAT(event_sent->wait_for(a_long_time), testing::Eq(true)) << "fake event failed to go through"; } } void wlcs_server_start(WlcsDisplayServer* server) { auto runner = reinterpret_cast(server); runner->start_server(); runner->started.wait_for(a_long_time); } void wlcs_server_stop(WlcsDisplayServer* server) { auto runner = reinterpret_cast(server); runner->stop_server(); } WlcsDisplayServer* wlcs_create_server(int argc, char const** argv) { auto runner = new MirWlcsDisplayServer; runner->add_to_environment("MIR_SERVER_PLATFORM_GRAPHICS_LIB", mtf::server_platform("graphics-dummy.so").c_str()); runner->add_to_environment("MIR_SERVER_PLATFORM_INPUT_LIB", mtf::server_platform("input-stub.so").c_str()); runner->add_to_environment("MIR_SERVER_ENABLE_KEY_REPEAT", "false"); runner->add_to_environment("MIR_SERVER_NO_FILE", ""); runner->add_to_environment("MIR_SERVER_WAYLAND_SOCKET_NAME", "wlcs-tests"); runner->add_to_environment("WAYLAND_DISPLAY", "wlcs-tests"); runner->server.override_the_display_buffer_compositor_factory([] { return std::make_shared(); }); runner->server.override_the_session_listener( [runner]() { return runner->resource_mapper; }); runner->event_listener = std::make_shared(*runner); runner->server.set_command_line(argc, argv); runner->server.add_init_callback( [runner]() { runner->server.run_on_wayland_display( [runner](auto wayland_display) { runner->resource_mapper->init(wayland_display); runner->executor = WaylandExecutor::executor_for_event_loop( wl_display_get_event_loop(wayland_display)); // Execute all observations on the Wayland event loop… runner->server.the_seat_observer_registrar()->register_interest( runner->event_listener, *runner->executor); runner->started.raise(); }); }); runner->server.wrap_cursor_listener( [runner](auto const& wrappee) { class ListenerWrapper : public mir::input::CursorListener { public: ListenerWrapper( MirWlcsDisplayServer* runner, std::shared_ptr const& wrapped) : runner{runner}, wrapped{wrapped} { } void cursor_moved_to(float abs_x, float abs_y) override { runner->cursor_x = abs_x; runner->cursor_y = abs_y; wrapped->cursor_moved_to(abs_x, abs_y); } private: MirWlcsDisplayServer* const runner; std::shared_ptr const wrapped; }; return std::make_shared(runner, wrappee); }); return reinterpret_cast(runner); } void wlcs_destroy_server(WlcsDisplayServer* server) { auto runner = reinterpret_cast(server); delete runner; } int wlcs_server_create_client_socket(WlcsDisplayServer* server) { auto runner = reinterpret_cast(server); try { auto client_fd = fcntl( runner->server.open_wayland_client_socket(), F_DUPFD_CLOEXEC, 3); runner->resource_mapper->associate_client_socket(client_fd); return client_fd; } catch (std::exception const&) { mir::log( mir::logging::Severity::critical, "wlcs-bindings", std::current_exception(), "Failed to create Wayland client socket"); } return -1; } struct FakePointer { decltype(mtf::add_fake_input_device(mi::InputDeviceInfo())) pointer; MirWlcsDisplayServer* runner; }; WlcsPointer* wlcs_server_create_pointer(WlcsDisplayServer* server) { auto runner = reinterpret_cast(server); auto constexpr uid = "mouse-uid"; class DeviceObserver : public mir::input::InputDeviceObserver { public: DeviceObserver(std::shared_ptr const& done) : done{done} { } void device_added(std::shared_ptr const& device) override { if (device->unique_id() == uid) seen_device = true; } void device_changed(std::shared_ptr const&) override { } void device_removed(std::shared_ptr const&) override { } void changes_complete() override { if (seen_device) done->raise(); } private: std::shared_ptr const done; bool seen_device{false}; }; auto mouse_added = std::make_shared(); auto observer = std::make_shared(mouse_added); runner->server.the_input_device_hub()->add_observer(observer); auto fake_mouse = mtf::add_fake_input_device( mi::InputDeviceInfo{"mouse", uid, mi::DeviceCapability::pointer}); mouse_added->wait_for(a_long_time); runner->executor->spawn([observer=std::move(observer), the_input_device_hub=runner->server.the_input_device_hub()] { the_input_device_hub->remove_observer(observer); }); auto fake_pointer = new FakePointer; fake_pointer->runner = runner; fake_pointer->pointer = std::move(fake_mouse); return reinterpret_cast(fake_pointer); } void wlcs_destroy_pointer(WlcsPointer* pointer) { delete reinterpret_cast(pointer); } void wlcs_pointer_move_relative(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y) { auto device = reinterpret_cast(pointer); auto event = mir::input::synthesis::a_pointer_event() .with_movement(wl_fixed_to_int(x), wl_fixed_to_int(y)); emit_mir_event(device->runner, device->pointer, event); } void wlcs_pointer_move_absolute(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y) { auto device = reinterpret_cast(pointer); auto rel_x = wl_fixed_to_double(x) - device->runner->cursor_x; auto rel_y = wl_fixed_to_double(y) - device->runner->cursor_y; wlcs_pointer_move_relative(pointer, wl_fixed_from_double(rel_x), wl_fixed_from_double(rel_y)); } void wlcs_pointer_button_down(WlcsPointer* pointer, int button) { auto device = reinterpret_cast(pointer); auto event = mir::input::synthesis::a_button_down_event() .of_button(button); emit_mir_event(device->runner, device->pointer, event); } void wlcs_pointer_button_up(WlcsPointer* pointer, int button) { auto device = reinterpret_cast(pointer); auto event = mir::input::synthesis::a_button_up_event() .of_button(button); emit_mir_event(device->runner, device->pointer, event); } struct FakeTouch { decltype(mtf::add_fake_input_device(mi::InputDeviceInfo())) touch; int last_x{0}, last_y{0}; MirWlcsDisplayServer* runner; }; WlcsTouch* wlcs_server_create_touch(WlcsDisplayServer* server) { auto runner = reinterpret_cast(server); auto constexpr uid = "touch-uid"; class DeviceObserver : public mir::input::InputDeviceObserver { public: DeviceObserver(std::shared_ptr const& done) : done{done} { } void device_added(std::shared_ptr const& device) override { if (device->unique_id() == uid) seen_device = true; } void device_changed(std::shared_ptr const&) override { } void device_removed(std::shared_ptr const&) override { } void changes_complete() override { if (seen_device) done->raise(); } private: std::shared_ptr const done; bool seen_device{false}; }; auto touch_added = std::make_shared(); auto observer = std::make_shared(touch_added); runner->server.the_input_device_hub()->add_observer(observer); auto fake_touch_dev = mtf::add_fake_input_device( mi::InputDeviceInfo{"touch", uid, mi::DeviceCapability::multitouch}); touch_added->wait_for(a_long_time); runner->executor->spawn([observer=std::move(observer), the_input_device_hub=runner->server.the_input_device_hub()] { the_input_device_hub->remove_observer(observer); }); auto fake_touch = new FakeTouch; fake_touch->runner = runner; fake_touch->touch = std::move(fake_touch_dev); return reinterpret_cast(fake_touch); } void wlcs_destroy_touch(WlcsTouch* touch) { delete reinterpret_cast(touch); } void wlcs_touch_down(WlcsTouch* touch, int x, int y) { auto device = reinterpret_cast(touch); device->last_x = x; device->last_y = y; auto event = mir::input::synthesis::a_touch_event() .with_action(mir::input::synthesis::TouchParameters::Action::Tap) .at_position({x, y}); emit_mir_event(device->runner, device->touch, event); } void wlcs_touch_move(WlcsTouch* touch, int x, int y) { auto device = reinterpret_cast(touch); device->last_x = x; device->last_y = y; auto event = mir::input::synthesis::a_touch_event() .with_action(mir::input::synthesis::TouchParameters::Action::Move) .at_position({x, y}); emit_mir_event(device->runner, device->touch, event); } void wlcs_touch_up(WlcsTouch* touch) { auto device = reinterpret_cast(touch); auto event = mir::input::synthesis::a_touch_event() .with_action(mir::input::synthesis::TouchParameters::Action::Release) .at_position({device->last_x, device->last_y}); emit_mir_event(device->runner, device->touch, event); } void wlcs_server_position_window_absolute(WlcsDisplayServer* server, wl_display* client, wl_surface* surface, int x, int y) { auto runner = reinterpret_cast(server); try { auto const fd = wl_display_get_fd(client); auto const client = runner->resource_mapper->client_for_fd(fd); auto const id = wl_proxy_get_id(reinterpret_cast(surface)); auto resource = wl_client_get_object(client, id); auto mir_surface = runner->resource_mapper->surface_for_resource(resource); if (auto live_surface = mir_surface.lock()) { live_surface->move_to(mir::geometry::Point{x, y}); } else { abort(); // TODO: log? Error handling? } } catch(std::out_of_range const&) { abort(); // TODO: Error handling. } } wlcs-1.7.0/include/000077500000000000000000000000001453657357500141365ustar00rootroot00000000000000wlcs-1.7.0/include/active_listeners.h000066400000000000000000000025321453657357500176540ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #ifndef WLCS_ACTIVE_LISTENERS_H #define WLCS_ACTIVE_LISTENERS_H #include #include namespace wlcs { class ActiveListeners { public: void add(void* listener) { std::lock_guard lock{mutex}; listeners.insert(listener); } void del(void* listener) { std::lock_guard lock{mutex}; listeners.erase(listener); } bool includes(void* listener) const { std::lock_guard lock{mutex}; return listeners.find(listener) != end(listeners); } private: std::mutex mutable mutex; std::set listeners; }; } #endif //WLCS_ACTIVE_LISTENERS_H wlcs-1.7.0/include/data_device.h000066400000000000000000000123341453657357500165420ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #ifndef WLCS_DATA_DEVICE_H #define WLCS_DATA_DEVICE_H #include "active_listeners.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include #include namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(wl_data_device_manager) class DataSource { public: DataSource() = default; explicit DataSource(struct wl_data_source* ds) : self{ds, deleter} {} operator struct wl_data_source*() const { return self.get(); } void reset() { self.reset(); } void reset(struct wl_data_source* ds) { self.reset(ds, deleter); } friend void wl_data_source_destroy(DataSource const&) = delete; private: static void deleter(struct wl_data_source* ds) { wl_data_source_destroy(ds); } std::shared_ptr self; }; struct DataDeviceListener { DataDeviceListener(struct wl_data_device* data_device) { active_listeners.add(this); wl_data_device_add_listener(data_device, &thunks, this); } virtual ~DataDeviceListener() { active_listeners.del(this); } DataDeviceListener(DataDeviceListener const&) = delete; DataDeviceListener& operator=(DataDeviceListener const&) = delete; virtual void data_offer( struct wl_data_device* wl_data_device, struct wl_data_offer* id); virtual void enter( struct wl_data_device* wl_data_device, uint32_t serial, struct wl_surface* surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer* id); virtual void leave(struct wl_data_device* wl_data_device); virtual void motion( struct wl_data_device* wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y); virtual void drop(struct wl_data_device* wl_data_device); virtual void selection( struct wl_data_device* wl_data_device, struct wl_data_offer* id); private: static void data_offer( void* data, struct wl_data_device* wl_data_device, struct wl_data_offer* id); static void enter( void* data, struct wl_data_device* wl_data_device, uint32_t serial, struct wl_surface* surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer* id); static void leave(void* data, struct wl_data_device* wl_data_device); static void motion( void* data, struct wl_data_device* wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y); static void drop(void* data, struct wl_data_device* wl_data_device); static void selection( void* data, struct wl_data_device* wl_data_device, struct wl_data_offer* id); static ActiveListeners active_listeners; constexpr static wl_data_device_listener thunks = { &data_offer, &enter, &leave, &motion, &drop, &selection }; }; struct DataOfferListener { DataOfferListener() { active_listeners.add(this); } virtual ~DataOfferListener() { active_listeners.del(this); } DataOfferListener(DataOfferListener const&) = delete; DataOfferListener& operator=(DataOfferListener const&) = delete; void listen_to(struct wl_data_offer* data_offer) { wl_data_offer_add_listener(data_offer, &thunks, this); } virtual void offer(struct wl_data_offer* data_offer, char const* mime_type); virtual void source_actions(struct wl_data_offer* data_offer, uint32_t dnd_actions); virtual void action(struct wl_data_offer* data_offer, uint32_t dnd_action); private: static void offer(void* data, struct wl_data_offer* data_offer, char const* mime_type); static void source_actions(void* data, struct wl_data_offer* data_offer, uint32_t dnd_actions); static void action(void* data, struct wl_data_offer* data_offer, uint32_t dnd_action); static ActiveListeners active_listeners; constexpr static wl_data_offer_listener thunks = { &offer, &source_actions, &action }; }; class DataDevice { public: DataDevice() = default; explicit DataDevice(struct wl_data_device* dd) : self{dd, deleter} {} operator struct wl_data_device*() const { return self.get(); } void reset() { self.reset(); } void reset(struct wl_data_device* dd) { self.reset(dd, deleter); } friend void wl_data_device_destroy(DataDevice const&) = delete; private: static void deleter(struct wl_data_device* dd) { wl_data_device_destroy(dd); } std::shared_ptr self; }; } #endif //WLCS_DATA_DEVICE_H wlcs-1.7.0/include/geometry/000077500000000000000000000000001453657357500157715ustar00rootroot00000000000000wlcs-1.7.0/include/geometry/dimensions.h000066400000000000000000000257221453657357500203220ustar00rootroot00000000000000/* * Copyright © 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 . */ #ifndef WLCS_GEOMETRY_DIMENSIONS_H_ #define WLCS_GEOMETRY_DIMENSIONS_H_ #include "forward.h" #include #include #include namespace wlcs { namespace generic { template /// Wraps a geometry value and prevents it from being accidentally used for invalid operations (such as setting a /// width to a height or adding two x positions together). Of course, explicit casts are possible to get around /// these restrictions (see the as_*() functions). struct Value { using ValueType = T; using TagType = Tag; template constexpr typename std::enable_if::value, int>::type as_int() const { return this->value; } template constexpr typename std::enable_if::value, uint32_t>::type as_uint32_t() const { return this->value; } constexpr T as_value() const noexcept { return value; } constexpr Value() noexcept : value{} {} Value& operator=(Value const& that) noexcept { value = that.value; return *this; } constexpr Value(Value const& that) noexcept : value{that.value} { } template explicit constexpr Value(Value const& value) noexcept : value{static_cast(value.as_value())} { } template::value, bool>::type = true> explicit constexpr Value(U const& value) noexcept : value{static_cast(value)} { } inline constexpr auto operator == (Value const& rhs) const -> bool { return value == rhs.as_value(); } inline constexpr auto operator != (Value const& rhs) const -> bool { return value != rhs.as_value(); } inline constexpr auto operator <= (Value const& rhs) const -> bool { return value <= rhs.as_value(); } inline constexpr auto operator >= (Value const& rhs) const -> bool { return value >= rhs.as_value(); } inline constexpr auto operator < (Value const& rhs) const -> bool { return value < rhs.as_value(); } inline constexpr auto operator > (Value const& rhs) const -> bool { return value > rhs.as_value(); } protected: T value; }; template std::ostream& operator<<(std::ostream& out, Value const& value) { out << value.as_value(); return out; } // Adding deltas is fine template inline constexpr DeltaX operator+(DeltaX lhs, DeltaX rhs){ return DeltaX(lhs.as_value() + rhs.as_value()); } template inline constexpr DeltaY operator+(DeltaY lhs, DeltaY rhs) { return DeltaY(lhs.as_value() + rhs.as_value()); } template inline constexpr DeltaX operator-(DeltaX lhs, DeltaX rhs) { return DeltaX(lhs.as_value() - rhs.as_value()); } template inline constexpr DeltaY operator-(DeltaY lhs, DeltaY rhs) { return DeltaY(lhs.as_value() - rhs.as_value()); } template inline constexpr DeltaX operator-(DeltaX rhs) { return DeltaX(-rhs.as_value()); } template inline constexpr DeltaY operator-(DeltaY rhs) { return DeltaY(-rhs.as_value()); } template inline DeltaX& operator+=(DeltaX& lhs, DeltaX rhs) { return lhs = lhs + rhs; } template inline DeltaY& operator+=(DeltaY& lhs, DeltaY rhs) { return lhs = lhs + rhs; } template inline DeltaX& operator-=(DeltaX& lhs, DeltaX rhs) { return lhs = lhs - rhs; } template inline DeltaY& operator-=(DeltaY& lhs, DeltaY rhs) { return lhs = lhs - rhs; } // Adding deltas to co-ordinates is fine template inline constexpr X operator+(X lhs, DeltaX rhs) { return X(lhs.as_value() + rhs.as_value()); } template inline constexpr Y operator+(Y lhs, DeltaY rhs) { return Y(lhs.as_value() + rhs.as_value()); } template inline constexpr X operator-(X lhs, DeltaX rhs) { return X(lhs.as_value() - rhs.as_value()); } template inline constexpr Y operator-(Y lhs, DeltaY rhs) { return Y(lhs.as_value() - rhs.as_value()); } template inline X& operator+=(X& lhs, DeltaX rhs) { return lhs = lhs + rhs; } template inline Y& operator+=(Y& lhs, DeltaY rhs) { return lhs = lhs + rhs; } template inline X& operator-=(X& lhs, DeltaX rhs) { return lhs = lhs - rhs; } template inline Y& operator-=(Y& lhs, DeltaY rhs) { return lhs = lhs - rhs; } // Adding deltas to generic::Width and generic::Height is fine template inline constexpr Width operator+(Width lhs, DeltaX rhs) { return Width(lhs.as_value() + rhs.as_value()); } template inline constexpr Height operator+(Height lhs, DeltaY rhs) { return Height(lhs.as_value() + rhs.as_value()); } template inline constexpr Width operator-(Width lhs, DeltaX rhs) { return Width(lhs.as_value() - rhs.as_value()); } template inline constexpr Height operator-(Height lhs, DeltaY rhs) { return Height(lhs.as_value() - rhs.as_value()); } template inline Width& operator+=(Width& lhs, DeltaX rhs) { return lhs = lhs + rhs; } template inline Height& operator+=(Height& lhs, DeltaY rhs) { return lhs = lhs + rhs; } template inline Width& operator-=(Width& lhs, DeltaX rhs) { return lhs = lhs - rhs; } template inline Height& operator-=(Height& lhs, DeltaY rhs) { return lhs = lhs - rhs; } // Adding Widths and Heights is fine template inline constexpr Width operator+(Width lhs, Width rhs) { return Width(lhs.as_value() + rhs.as_value()); } template inline constexpr Height operator+(Height lhs, Height rhs) { return Height(lhs.as_value() + rhs.as_value()); } template inline Width& operator+=(Width& lhs, Width rhs) { return lhs = lhs + rhs; } template inline Height& operator+=(Height& lhs, Height rhs) { return lhs = lhs + rhs; } // Subtracting coordinates is fine template inline constexpr DeltaX operator-(X lhs, X rhs) { return DeltaX(lhs.as_value() - rhs.as_value()); } template inline constexpr DeltaY operator-(Y lhs, Y rhs) { return DeltaY(lhs.as_value() - rhs.as_value()); } //Subtracting Width and Height is fine template inline constexpr DeltaX operator-(Width lhs, Width rhs) { return DeltaX(lhs.as_value() - rhs.as_value()); } template inline constexpr DeltaY operator-(Height lhs, Height rhs) { return DeltaY(lhs.as_value() - rhs.as_value()); } // Multiplying by a scalar value is fine template inline constexpr Width operator*(Scalar scale, Width const& w) { return Width{scale*w.as_value()}; } template inline constexpr Height operator*(Scalar scale, Height const& h) { return Height{scale*h.as_value()}; } template inline constexpr DeltaX operator*(Scalar scale, DeltaX const& dx) { return DeltaX{scale*dx.as_value()}; } template inline constexpr DeltaY operator*(Scalar scale, DeltaY const& dy) { return DeltaY{scale*dy.as_value()}; } template inline constexpr Width operator*(Width const& w, Scalar scale) { return scale*w; } template inline constexpr Height operator*(Height const& h, Scalar scale) { return scale*h; } template inline constexpr DeltaX operator*(DeltaX const& dx, Scalar scale) { return scale*dx; } template inline constexpr DeltaY operator*(DeltaY const& dy, Scalar scale) { return scale*dy; } // Dividing by a scaler value is fine template inline constexpr Width operator/(Width const& w, Scalar scale) { return Width{w.as_value() / scale}; } template inline constexpr Height operator/(Height const& h, Scalar scale) { return Height{h.as_value() / scale}; } template inline constexpr DeltaX operator/(DeltaX const& dx, Scalar scale) { return DeltaX{dx.as_value() / scale}; } template inline constexpr DeltaY operator/(DeltaY const& dy, Scalar scale) { return DeltaY{dy.as_value() / scale}; } } // namespace // Converting between types is fine, as long as they are along the same axis template inline constexpr generic::Width as_width(generic::DeltaX const& dx) { return generic::Width{dx.as_value()}; } template inline constexpr generic::Height as_height(generic::DeltaY const& dy) { return generic::Height{dy.as_value()}; } template inline constexpr generic::X as_x(generic::DeltaX const& dx) { return generic::X{dx.as_value()}; } template inline constexpr generic::Y as_y(generic::DeltaY const& dy) { return generic::Y{dy.as_value()}; } template inline constexpr generic::DeltaX as_delta(generic::X const& x) { return generic::DeltaX{x.as_value()}; } template inline constexpr generic::DeltaY as_delta(generic::Y const& y) { return generic::DeltaY{y.as_value()}; } template inline constexpr generic::X as_x(generic::Width const& w) { return generic::X{w.as_value()}; } template inline constexpr generic::Y as_y(generic::Height const& h) { return generic::Y{h.as_value()}; } template inline constexpr generic::Width as_width(generic::X const& x) { return generic::Width{x.as_value()}; } template inline constexpr generic::Height as_height(generic::Y const& y) { return generic::Height{y.as_value()}; } template inline constexpr generic::DeltaX as_delta(generic::Width const& w) { return generic::DeltaX{w.as_value()}; } template inline constexpr generic::DeltaY as_delta(generic::Height const& h) { return generic::DeltaY{h.as_value()}; } } // namespace wlcs #endif // WLCS_GEOMETRY_DIMENSIONS_H_ wlcs-1.7.0/include/geometry/displacement.h000066400000000000000000000117261453657357500206210ustar00rootroot00000000000000/* * Copyright © 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 . */ #ifndef WLCS_GEOMETRY_DISPLACEMENT_H_ #define WLCS_GEOMETRY_DISPLACEMENT_H_ #include "forward.h" #include "dimensions.h" #include "point.h" #include namespace wlcs { namespace generic { template struct Point; template struct Size; template struct Displacement { using ValueType = T; constexpr Displacement() {} constexpr Displacement(Displacement const&) = default; Displacement& operator=(Displacement const&) = default; template explicit constexpr Displacement(Displacement const& other) noexcept : dx{DeltaX{other.dx}}, dy{DeltaY{other.dy}} { } template constexpr Displacement(DeltaXType&& dx, DeltaYType&& dy) : dx{dx}, dy{dy} {} template constexpr typename std::enable_if::value, long long>::type length_squared() const { long long x = dx.as_value(), y = dy.as_value(); return x * x + y * y; } template constexpr typename std::enable_if::value, T>::type length_squared() const { T x = dx.as_value(), y = dy.as_value(); return x * x + y * y; } DeltaX dx; DeltaY dy; }; template inline constexpr bool operator==(Displacement const& lhs, Displacement const& rhs) { return lhs.dx == rhs.dx && lhs.dy == rhs.dy; } template inline constexpr bool operator!=(Displacement const& lhs, Displacement const& rhs) { return lhs.dx != rhs.dx || lhs.dy != rhs.dy; } template std::ostream& operator<<(std::ostream& out, Displacement const& value) { out << '(' << value.dx << ", " << value.dy << ')'; return out; } template inline constexpr Displacement operator+(Displacement const& lhs, Displacement const& rhs) { return Displacement{lhs.dx + rhs.dx, lhs.dy + rhs.dy}; } template inline constexpr Displacement operator-(Displacement const& lhs, Displacement const& rhs) { return Displacement{lhs.dx - rhs.dx, lhs.dy - rhs.dy}; } template inline constexpr Displacement operator-(Displacement const& rhs) { return Displacement{-rhs.dx, -rhs.dy}; } template inline constexpr Point operator+(Point const& lhs, Displacement const& rhs) { return Point{lhs.x + rhs.dx, lhs.y + rhs.dy}; } template inline constexpr Point operator+(Displacement const& lhs, Point const& rhs) { return Point{rhs.x + lhs.dx, rhs.y + lhs.dy}; } template inline constexpr Point operator-(Point const& lhs, Displacement const& rhs) { return Point{lhs.x - rhs.dx, lhs.y - rhs.dy}; } template inline constexpr Displacement operator-(Point const& lhs, Point const& rhs) { return Displacement{lhs.x - rhs.x, lhs.y - rhs.y}; } template inline constexpr Point& operator+=(Point& lhs, Displacement const& rhs) { return lhs = lhs + rhs; } template inline constexpr Point& operator-=(Point& lhs, Displacement const& rhs) { return lhs = lhs - rhs; } template inline bool operator<(Displacement const& lhs, Displacement const& rhs) { return lhs.length_squared() < rhs.length_squared(); } template inline constexpr Displacement operator*(Scalar scale, Displacement const& disp) { return Displacement{scale*disp.dx, scale*disp.dy}; } template inline constexpr Displacement operator*(Displacement const& disp, Scalar scale) { return scale*disp; } template inline constexpr Displacement as_displacement(Size const& size) { return Displacement{size.width.as_value(), size.height.as_value()}; } template inline constexpr Size as_size(Displacement const& disp) { return Size{disp.dx.as_value(), disp.dy.as_value()}; } template inline constexpr Displacement as_displacement(Point const& point) { return Displacement{point.x.as_value(), point.y.as_value()}; } template inline constexpr Point as_point(Displacement const& disp) { return Point{disp.dx.as_value(), disp.dy.as_value()}; } } } #endif // WLCS_GEOMETRY_DISPLACEMENT_H_ wlcs-1.7.0/include/geometry/forward.h000066400000000000000000000051311453657357500176060ustar00rootroot00000000000000/* * Copyright © 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 . */ #ifndef WLCS_GEOMETRY_FORWARD_H_ #define WLCS_GEOMETRY_FORWARD_H_ namespace wlcs { /// These tag types determine what type of dimension a value holds and what operations are possible with it. They are /// only used as template parameters, are never instantiated and should only require forward declarations, but some /// compiler versions seem to fail if they aren't given real declarations. /// @{ struct WidthTag{}; struct HeightTag{}; struct XTag{}; struct YTag{}; struct DeltaXTag{}; struct DeltaYTag{}; struct StrideTag{}; /// @} namespace generic { template struct Value; template struct Point; template struct Size; template struct Displacement; template struct Rectangle; template using Width = Value; template using Height = Value; template using X = Value; template using Y = Value; template using DeltaX = Value; template using DeltaY = Value; } using Width = generic::Width; using Height = generic::Height; using X = generic::X; using Y = generic::Y; using DeltaX = generic::DeltaX; using DeltaY = generic::DeltaY; using WidthF = generic::Width; using HeightF = generic::Height; using XF = generic::X; using YF = generic::Y; using DeltaXF = generic::DeltaX; using DeltaYF = generic::DeltaY; // Just to be clear, mir::geometry::Stride is the stride of the buffer in bytes using Stride = generic::Value; using Point = generic::Point; using Size = generic::Size; using Displacement = generic::Displacement; using Rectangle = generic::Rectangle; using PointF = generic::Point; using SizeF = generic::Size; using DisplacementF = generic::Displacement; using RectangleF = generic::Rectangle; } #endif // WLCS_GEOMETRY_FORWARD_H_ wlcs-1.7.0/include/geometry/point.h000066400000000000000000000053051453657357500172760ustar00rootroot00000000000000/* * Copyright © 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 . */ #ifndef WLCS_GEOMETRY_POINT_H_ #define WLCS_GEOMETRY_POINT_H_ #include "forward.h" #include "dimensions.h" #include namespace wlcs { namespace generic { template struct Size; template struct Displacement; template struct Point { using ValueType = T; constexpr Point() = default; constexpr Point(Point const&) = default; Point& operator=(Point const&) = default; template explicit constexpr Point(Point const& other) noexcept : x{X{other.x}}, y{Y{other.y}} { } template constexpr Point(XType&& x, YType&& y) : x(x), y(y) {} X x; Y y; }; template inline constexpr bool operator == (Point const& lhs, Point const& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } template inline constexpr bool operator != (Point const& lhs, Point const& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } template inline constexpr Point operator+(Point lhs, DeltaX rhs) { return{lhs.x + rhs, lhs.y}; } template inline constexpr Point operator+(Point lhs, DeltaY rhs) { return{lhs.x, lhs.y + rhs}; } template inline constexpr Point operator-(Point lhs, DeltaX rhs) { return{lhs.x - rhs, lhs.y}; } template inline constexpr Point operator-(Point lhs, DeltaY rhs) { return{lhs.x, lhs.y - rhs}; } template inline Point& operator+=(Point& lhs, DeltaX rhs) { lhs.x += rhs; return lhs; } template inline Point& operator+=(Point& lhs, DeltaY rhs) { lhs.y += rhs; return lhs; } template inline Point& operator-=(Point& lhs, DeltaX rhs) { lhs.x -= rhs; return lhs; } template inline Point& operator-=(Point& lhs, DeltaY rhs) { lhs.y -= rhs; return lhs; } template std::ostream& operator<<(std::ostream& out, Point const& value) { out << value.x << ", " << value.y; return out; } } } #endif // WLCS_GEOMETRY_POINT_H_ wlcs-1.7.0/include/geometry/rectangle.h000066400000000000000000000103001453657357500201000ustar00rootroot00000000000000/* * Copyright © 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 . */ #ifndef WLCS_GEOMETRY_RECTANGLE_H_ #define WLCS_GEOMETRY_RECTANGLE_H_ #include "forward.h" #include "point.h" #include "size.h" #include "displacement.h" #include namespace wlcs { namespace generic { template struct Rectangle { constexpr Rectangle() = default; constexpr Rectangle(Point const& top_left, Size const& size) : top_left{top_left}, size{size} { } /** * The bottom right boundary point of the rectangle. * * Note that the returned point is *not* included in the rectangle * area, that is, the rectangle is represented as [top_left,bottom_right). */ Point bottom_right() const { return top_left + as_displacement(size); } Point top_right() const { return top_left + as_delta(size.width); } Point bottom_left() const { return top_left + as_delta(size.height); } bool contains(Point const& p) const { if (size.width == decltype(size.width){} || size.height == decltype(size.height){}) return false; auto br = bottom_right(); return p.x >= left() && p.x < br.x && p.y >= top() && p.y < br.y; } /** * Test if the rectangle contains another. * * Note that an empty rectangle can still contain other empty rectangles, * which are treated as points or lines of thickness zero. */ bool contains(Rectangle const& r) const { return r.left() >= left() && r.left() + as_delta(r.size.width) <= left() + as_delta(size.width) && r.top() >= top() && r.top() + as_delta(r.size.height) <= top() + as_delta(size.height); } bool overlaps(Rectangle const& r) const { bool disjoint = r.left() >= right() || r.right() <= left() || r.top() >= bottom() || r.bottom() <= top() || size.width == decltype(size.width){} || size.height == decltype(size.height){} || r.size.width == decltype(r.size.width){} || r.size.height == decltype(r.size.height){}; return !disjoint; } X left() const { return top_left.x; } X right() const { return bottom_right().x; } Y top() const { return top_left.y; } Y bottom() const { return bottom_right().y; } Point top_left; Size size; }; template Rectangle intersection_of(Rectangle const& a, Rectangle const& b) { auto const max_left = std::max(a.left(), b.left()); auto const min_right = std::min(a.right(), b.right()); auto const max_top = std::max(a.top(), b.top()); auto const min_bottom = std::min(a.bottom(), b.bottom()); if (max_left < min_right && max_top < min_bottom) return {{max_left, max_top}, {(min_right - max_left).as_value(), (min_bottom - max_top).as_value()}}; else return {}; } template inline constexpr bool operator == (Rectangle const& lhs, Rectangle const& rhs) { return lhs.top_left == rhs.top_left && lhs.size == rhs.size; } template inline constexpr bool operator != (Rectangle const& lhs, Rectangle const& rhs) { return lhs.top_left != rhs.top_left || lhs.size != rhs.size; } template std::ostream& operator<<(std::ostream& out, Rectangle const& value) { out << '(' << value.top_left << ", " << value.size << ')'; return out; } } } #endif // WLCS_GEOMETRY_RECTANGLE_H_ wlcs-1.7.0/include/geometry/size.h000066400000000000000000000053311453657357500171160ustar00rootroot00000000000000/* * Copyright © 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 . */ #ifndef WLCS_GEOMETRY_SIZE_H_ #define WLCS_GEOMETRY_SIZE_H_ #include "forward.h" #include "dimensions.h" #include namespace wlcs { namespace generic { template struct Point; template struct Displacement; template struct Size { using ValueType = T; constexpr Size() noexcept {} constexpr Size(Size const&) noexcept = default; Size& operator=(Size const&) noexcept = default; template explicit constexpr Size(Size const& other) noexcept : width{Width{other.width}}, height{Height{other.height}} { } template constexpr Size(WidthType&& width, HeightType&& height) noexcept : width(width), height(height) {} Width width; Height height; }; template inline constexpr bool operator == (Size const& lhs, Size const& rhs) { return lhs.width == rhs.width && lhs.height == rhs.height; } template inline constexpr bool operator != (Size const& lhs, Size const& rhs) { return lhs.width != rhs.width || lhs.height != rhs.height; } template std::ostream& operator<<(std::ostream& out, Size const& value) { out << '(' << value.width << ", " << value.height << ')'; return out; } template inline constexpr Size operator*(Scalar scale, Size const& size) { return Size{scale*size.width, scale*size.height}; } template inline constexpr Size operator*(Size const& size, Scalar scale) { return scale*size; } template inline constexpr Size operator/(Size const& size, Scalar scale) { return Size{size.width / scale, size.height / scale}; } template inline constexpr Size as_size(Point const& point) { return Size{point.x.as_value(), point.y.as_value()}; } template inline constexpr Point as_point(Size const& size) { return Point{size.width.as_value(), size.height.as_value()}; } } } #endif // WLCS_GEOMETRY_SIZE_H_ wlcs-1.7.0/include/gtest_helpers.h000066400000000000000000000030221453657357500171540ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #include #include #include namespace std { namespace chrono { /// GTest helper to pretty-print time-points template void PrintTo(time_point const& time, ostream* os) { auto remainder = time.time_since_epoch(); auto const hours = duration_cast(remainder); remainder -= hours; auto const minutes = duration_cast(remainder); remainder -= minutes; auto const seconds = duration_cast(remainder); remainder -= seconds; auto const nsec = duration_cast(remainder); (*os) << hours.count() << ":" << setw(2) << setfill('0') << minutes.count() << ":" << setw(2) << setfill('0') << seconds.count() << "." << setw(9) << setfill('0') << nsec.count(); } } } wlcs-1.7.0/include/gtk_primary_selection.h000066400000000000000000000134411453657357500207070ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #ifndef WLCS_PRIMARY_SELECTION_H #define WLCS_PRIMARY_SELECTION_H #include "generated/gtk-primary-selection-client.h" #include "active_listeners.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include #include #include namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(gtk_primary_selection_device_manager) class GtkPrimarySelectionSource { public: using WrappedType = gtk_primary_selection_source; GtkPrimarySelectionSource() = default; explicit GtkPrimarySelectionSource(gtk_primary_selection_device_manager* manager) : self{gtk_primary_selection_device_manager_create_source(manager), deleter} {} operator WrappedType*() const { return self.get(); } void reset() { self.reset(); } void reset(WrappedType* source) { self.reset(source, deleter); } friend void gtk_primary_selection_source_destroy(GtkPrimarySelectionSource const&) = delete; private: static void deleter(WrappedType* source) { gtk_primary_selection_source_destroy(source); } std::shared_ptr self; }; class GtkPrimarySelectionDevice { public: using WrappedType = gtk_primary_selection_device; GtkPrimarySelectionDevice() = default; GtkPrimarySelectionDevice(gtk_primary_selection_device_manager* manager, wl_seat* seat) : self{gtk_primary_selection_device_manager_get_device(manager, seat), deleter} {} operator WrappedType*() const { return self.get(); } void reset() { self.reset(); } void reset(WrappedType* device) { self.reset(device, deleter); } friend void gtk_primary_selection_device_destroy(GtkPrimarySelectionDevice const&) = delete; private: static void deleter(WrappedType* device) { gtk_primary_selection_device_destroy(device); } std::shared_ptr self; }; struct GtkPrimarySelectionDeviceListener { GtkPrimarySelectionDeviceListener(gtk_primary_selection_device* device) { active_listeners.add(this); gtk_primary_selection_device_add_listener(device, &thunks, this); } virtual ~GtkPrimarySelectionDeviceListener() { active_listeners.del(this); } GtkPrimarySelectionDeviceListener(GtkPrimarySelectionDeviceListener const&) = delete; GtkPrimarySelectionDeviceListener& operator=(GtkPrimarySelectionDeviceListener const&) = delete; virtual void data_offer(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer); virtual void selection(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer); private: static void data_offer(void* data, gtk_primary_selection_device* device, gtk_primary_selection_offer* offer); static void selection(void* data, gtk_primary_selection_device* device, gtk_primary_selection_offer* offer); static ActiveListeners active_listeners; constexpr static gtk_primary_selection_device_listener thunks = { &data_offer, &selection }; }; struct GtkPrimarySelectionOfferListener { GtkPrimarySelectionOfferListener() { active_listeners.add(this); } virtual ~GtkPrimarySelectionOfferListener() { active_listeners.del(this); } GtkPrimarySelectionOfferListener(GtkPrimarySelectionOfferListener const&) = delete; GtkPrimarySelectionOfferListener& operator=(GtkPrimarySelectionOfferListener const&) = delete; void listen_to(gtk_primary_selection_offer* offer) { gtk_primary_selection_offer_add_listener(offer, &thunks, this); } virtual void offer(gtk_primary_selection_offer* offer, const char* mime_type); private: static void offer(void* data, gtk_primary_selection_offer* offer, const char* mime_type); static ActiveListeners active_listeners; constexpr static gtk_primary_selection_offer_listener thunks = { &offer, }; }; struct GtkPrimarySelectionSourceListener { explicit GtkPrimarySelectionSourceListener(GtkPrimarySelectionSource const& source) { active_listeners.add(this); gtk_primary_selection_source_add_listener(source, &thunks, this); } virtual ~GtkPrimarySelectionSourceListener() { active_listeners.del(this); } GtkPrimarySelectionSourceListener(GtkPrimarySelectionSourceListener const&) = delete; GtkPrimarySelectionSourceListener& operator=(GtkPrimarySelectionSourceListener const&) = delete; virtual void send(gtk_primary_selection_source* source, const char* mime_type, int32_t fd); virtual void cancelled(gtk_primary_selection_source* source); private: static void send(void* data, gtk_primary_selection_source* source, const char* mime_type, int32_t fd); static void cancelled(void* data, gtk_primary_selection_source* source); static ActiveListeners active_listeners; constexpr static gtk_primary_selection_source_listener thunks = { &send, &cancelled }; }; } #endif //WLCS_PRIMARY_SELECTION_H wlcs-1.7.0/include/helpers.h000066400000000000000000000022501453657357500157500ustar00rootroot00000000000000/* * Copyright © 2017 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #ifndef WLCS_HELPERS_H_ #define WLCS_HELPERS_H_ #include #include struct WlcsServerIntegration; namespace wlcs { namespace helpers { int create_anonymous_file(size_t size); void set_command_line(int argc, char const** argv); int get_argc(); char const** get_argv(); void set_entry_point(std::shared_ptr const& entry_point); std::shared_ptr get_test_hooks(); } } #endif //WLCS_HELPERS_H_ wlcs-1.7.0/include/in_process_server.h000066400000000000000000000223021453657357500200400ustar00rootroot00000000000000/* * Copyright © 2017-2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #ifndef WLCS_IN_PROCESS_SERVER_H_ #define WLCS_IN_PROCESS_SERVER_H_ #include "generated/wayland-client.h" #include "generated/xdg-shell-unstable-v6-client.h" #include "generated/xdg-shell-client.h" #include #include #include #include #include #include #include "shared_library.h" #include "wl_handle.h" #include struct WlcsPointer; struct WlcsTouch; struct WlcsServerIntegration; struct zwlr_layer_shell_v1; namespace wlcs { class VersionSpecifier; WLCS_CREATE_INTERFACE_DESCRIPTOR(wl_surface) WLCS_CREATE_INTERFACE_DESCRIPTOR(wl_subsurface) /* We need a manually-specified descriptor for wl_output, * as wl_output only has a destructor for version >= 3 */ namespace { void send_release_if_supported(wl_output* to_destroy) { if (wl_output_get_version(to_destroy) >= WL_OUTPUT_RELEASE_SINCE_VERSION) { wl_output_release(to_destroy); } else { wl_output_destroy(to_destroy); } } } template<> struct WlInterfaceDescriptor { static constexpr bool const has_specialisation = true; static constexpr wl_interface const* const interface = &wl_output_interface; static constexpr void (* const destructor)(wl_output*) = &send_release_if_supported; }; class Pointer { public: ~Pointer(); Pointer(Pointer&&); void move_to(int x, int y); void move_by(int dx, int dy); void button_down(int button); void button_up(int button); void click(int button); void left_button_down(); void left_button_up(); void left_click(); private: friend class Server; template Pointer( WlcsPointer* raw_device, std::shared_ptr const& proxy, std::shared_ptr const& keep_dso_loaded); class Impl; std::unique_ptr impl; }; class Touch { public: ~Touch(); Touch(Touch&&); void down_at(int x, int y); void move_to(int x, int y); void up(); private: friend class Server; template Touch( WlcsTouch* raw_device, std::shared_ptr const& proxy, std::shared_ptr const& keep_dso_loaded); class Impl; std::unique_ptr impl; }; class Surface; class Server { public: Server( std::shared_ptr const& module, int argc, char const** argv); ~Server(); int create_client_socket(); Pointer create_pointer(); Touch create_touch(); void move_surface_to(Surface& surface, int x, int y); void start(); void stop(); std::shared_ptr> supported_extensions(); private: class Impl; std::unique_ptr const impl; }; class Client; class Surface { public: explicit Surface(Client& client); virtual ~Surface(); Surface(Surface&& other); operator ::wl_surface*() const; auto wl_surface() const -> ::wl_surface* { return *this; }; void attach_buffer(int width, int height); void add_frame_callback(std::function const& on_frame); void attach_visible_buffer(int width, int height); void run_on_destruction(std::function callback); Client& owner() const; auto current_outputs() -> std::set const&; private: class Impl; std::unique_ptr impl; }; class Subsurface: public Surface { public: static Subsurface create_visible(Surface& parent, int x, int y, int width, int height); explicit Subsurface(Surface& parent); Subsurface(Subsurface &&); ~Subsurface(); operator wl_subsurface*() const; Surface& parent() const; private: class Impl; std::unique_ptr impl; }; class ShmBuffer { public: ShmBuffer(Client& client, int width, int height); ~ShmBuffer(); ShmBuffer(ShmBuffer&& other); operator wl_buffer*() const; void add_release_listener(std::function const &on_release); private: class Impl; std::unique_ptr impl; }; struct OutputState { explicit OutputState(wl_output* output); wl_output* output; std::optional> geometry_position; std::optional> mode_size; std::optional scale; }; class Client { public: explicit Client(Server& server); ~Client(); // Accessors operator wl_display*() const; wl_compositor* compositor() const; wl_subcompositor* subcompositor() const; wl_shm* shm() const; wl_seat* seat() const; void run_on_destruction(std::function callback); ShmBuffer const& create_buffer(int width, int height); Surface create_wl_shell_surface(int width, int height); Surface create_xdg_shell_v6_surface(int width, int height); Surface create_xdg_shell_stable_surface(int width, int height); Surface create_visible_surface(int width, int height); size_t output_count() const; OutputState output_state(size_t index) const; void add_output_done_notifier(size_t index, std::function const& notifier); wl_shell* shell() const; wl_pointer* the_pointer() const; zxdg_shell_v6* xdg_shell_v6() const; xdg_wm_base* xdg_shell_stable() const; wl_surface* keyboard_focused_window() const; wl_surface* window_under_cursor() const; wl_surface* touched_window() const; std::pair pointer_position() const; std::pair touch_position() const; std::optional latest_serial() const; using PointerEnterNotifier = std::function; using PointerLeaveNotifier = std::function; using PointerMotionNotifier = std::function; using PointerButtonNotifier = std::function; void add_pointer_enter_notification(PointerEnterNotifier const& on_enter); void add_pointer_leave_notification(PointerLeaveNotifier const& on_leave); void add_pointer_motion_notification(PointerMotionNotifier const& on_motion); void add_pointer_button_notification(PointerButtonNotifier const& on_button); void dispatch_until( std::function const& predicate, std::chrono::seconds timeout = std::chrono::seconds{10}); template auto bind_if_supported(VersionSpecifier const& version) -> WlHandle { return wrap_wl_object( static_cast(bind_if_supported(*WlInterfaceDescriptor::interface, version))); } auto bind_if_supported(wl_interface const& interface, VersionSpecifier const& version) const -> void*; /** * Perform a `wl_display_roundtrip()` * * This blocks until all previous client requests have been processed * by the server, and the client has processed any server responses. */ void roundtrip(); /** * Perform a `wl_display_flush()` * * This ensures all previous client requests have *actually* been * sent to the server, but does not wait for any replies or process * any incomming messages. * * \note It is possible that the server receive buffer will be too * small to hold all the outgoing messages. This method does * not provide a way to detect this case. */ void flush(); private: class Impl; std::unique_ptr const impl; }; class ProtocolError : public std::system_error { public: ProtocolError(wl_interface const* interface, uint32_t code); char const* what() const noexcept override; uint32_t error_code() const; wl_interface const* interface() const; 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_ wlcs-1.7.0/include/input_method.h000066400000000000000000000050361453657357500170120ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef WLCS_INPUT_METHOD_H_ #define WLCS_INPUT_METHOD_H_ #include #include #include "in_process_server.h" namespace wlcs { struct InputMethod { InputMethod(std::string const& name) : name{name} {} virtual ~InputMethod() = default; struct Device { virtual ~Device() = default; /// Can only be called while decice is up virtual void down_at(std::pair position) = 0; /// Can only be called while device is down virtual void move_to(std::pair position) = 0; /// Can only be called while device is down virtual void up() = 0; }; virtual auto create_device(wlcs::Server& server) -> std::unique_ptr = 0; virtual auto current_surface(wlcs::Client const& client) -> wl_surface* = 0; virtual auto position_on_surface(wlcs::Client const& client) -> std::pair = 0; static auto all_input_methods() -> std::vector>; std::string const name; }; struct PointerInputMethod : InputMethod { PointerInputMethod() : InputMethod{"pointer"} {} struct Pointer; auto create_device(wlcs::Server& server) -> std::unique_ptr override; auto current_surface(wlcs::Client const& client) -> wl_surface* override; auto position_on_surface(wlcs::Client const& client) -> std::pair override; }; struct TouchInputMethod : InputMethod { TouchInputMethod() : InputMethod{"touch"} {} struct Touch; auto create_device(wlcs::Server& server) -> std::unique_ptr override; auto current_surface(wlcs::Client const& client) -> wl_surface* override; auto position_on_surface(wlcs::Client const& client) -> std::pair override; }; } namespace std { std::ostream& operator<<(std::ostream& out, std::shared_ptr const& param); } #endif // WLCS_INPUT_METHOD_H_ wlcs-1.7.0/include/layer_shell_v1.h000066400000000000000000000054401453657357500172230ustar00rootroot00000000000000/* * Copyright © 2018-2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef WLCS_LAYER_SHELL_V1_H #define WLCS_LAYER_SHELL_V1_H #include "in_process_server.h" #include "wl_handle.h" #include "geometry/size.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(to_destroy)); } } } template<> struct WlInterfaceDescriptor { 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_size() const -> wlcs::Size { return last_size_; } private: wlcs::Client& client; WlHandle layer_shell; WlHandle layer_surface; Size last_size_ = {-1, -1}; int configure_count = 0; }; } #endif // WLCS_LAYER_SHELL_V1_H wlcs-1.7.0/include/method_event_impl.h000066400000000000000000000025631453657357500200170ustar00rootroot00000000000000/* * Copyright © 2021 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef WLCS_METHOD_EVENT_IMPL_H_ #define WLCS_METHOD_EVENT_IMPL_H_ #include "wl_handle.h" namespace wlcs { namespace detail { template struct MemberFunctionClass; template struct MemberFunctionClass { using type = T; }; } /// Can be used to easily implement Wayland listeners using methods, including gmock methods template void method_event_impl(void* data, WlType*, Args... args) { auto self = static_cast::type*>(data); (self->*member_fn)(args...); } } #endif // WLCS_METHOD_EVENT_IMPL_H_ wlcs-1.7.0/include/mock_input_method_v1.h000066400000000000000000000045011453657357500204250ustar00rootroot00000000000000/* * Copyright © 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 . */ #ifndef MOCK_INPUT_METHOD_V1_H #define MOCK_INPUT_METHOD_V1_H #include "generated/wayland-client.h" #include "generated/input-method-unstable-v1-client.h" #include "in_process_server.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include "method_event_impl.h" #include #include namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_input_method_v1) WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_input_method_context_v1) WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_input_panel_v1) WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_input_panel_surface_v1) class MockInputMethodContextV1 : public WlHandle { public: MockInputMethodContextV1(zwp_input_method_context_v1* proxy) : WlHandle{proxy} { zwp_input_method_context_v1_add_listener(proxy, &listener, this); } MOCK_METHOD3(surrounding_text, void(std::string const&, uint32_t, uint32_t)); MOCK_METHOD0(reset, void()); MOCK_METHOD2(content_type, void(uint32_t, uint32_t)); MOCK_METHOD2(invoke_action, void(uint32_t, uint32_t)); MOCK_METHOD1(preferred_language, void(std::string const&)); void commit_state(uint32_t in_serial) { serial = in_serial; } static zwp_input_method_context_v1_listener constexpr listener { method_event_impl<&MockInputMethodContextV1::surrounding_text>, method_event_impl<&MockInputMethodContextV1::reset>, method_event_impl<&MockInputMethodContextV1::content_type>, method_event_impl<&MockInputMethodContextV1::invoke_action>, method_event_impl<&MockInputMethodContextV1::commit_state>, method_event_impl<&MockInputMethodContextV1::preferred_language> }; uint32_t serial = 0; }; } #endif wlcs-1.7.0/include/mock_input_method_v2.h000066400000000000000000000045521453657357500204340ustar00rootroot00000000000000/* * Copyright © 2021 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef MOCK_INPUT_METHOD_V2 #define MOCK_INPUT_METHOD_V2 #include "generated/wayland-client.h" #include "generated/input-method-unstable-v2-client.h" #include "in_process_server.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include "method_event_impl.h" #include namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_input_method_manager_v2) WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_input_method_v2) class MockInputMethodV2 : public WlHandle { private: void done_wrapper() { done_count_++; done(); } public: MockInputMethodV2(zwp_input_method_v2* proxy) : WlHandle{proxy} { zwp_input_method_v2_add_listener(proxy, &listener, this); } MOCK_METHOD0(activate, void()); MOCK_METHOD0(deactivate, void()); MOCK_METHOD3(surrounding_text, void(std::string const&, uint32_t, uint32_t)); MOCK_METHOD1(text_change_cause, void(uint32_t)); MOCK_METHOD2(content_type, void(uint32_t, uint32_t)); MOCK_METHOD0(done, void()); MOCK_METHOD0(unavailable, void()); auto done_count() -> uint32_t { return done_count_; } static zwp_input_method_v2_listener constexpr listener { method_event_impl<&MockInputMethodV2::activate>, method_event_impl<&MockInputMethodV2::deactivate>, method_event_impl<&MockInputMethodV2::surrounding_text>, method_event_impl<&MockInputMethodV2::text_change_cause>, method_event_impl<&MockInputMethodV2::content_type>, method_event_impl<&MockInputMethodV2::done_wrapper>, method_event_impl<&MockInputMethodV2::unavailable>, }; private: uint32_t done_count_{0}; }; } #endif // MOCK_INPUT_METHOD_V2 wlcs-1.7.0/include/mock_text_input_v2.h000066400000000000000000000070631453657357500201400ustar00rootroot00000000000000/* * Copyright © 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 . */ #ifndef MOCK_TEXT_INPUT_V2_H #define MOCK_TEXT_INPUT_V2_H #include "generated/wayland-client.h" #include "generated/text-input-unstable-v2-client.h" #include "wl_handle.h" #include "wl_interface_descriptor.h" #include "method_event_impl.h" #include namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_text_input_manager_v2) WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_text_input_v2) class MockTextInputV2 : public WlHandle { public: MockTextInputV2(zwp_text_input_v2* proxy) : WlHandle{proxy} { zwp_text_input_v2_add_listener(proxy, &listener, this); } MOCK_METHOD2(on_enter, void(uint32_t, wl_surface*)); MOCK_METHOD2(on_leave, void(uint32_t, wl_surface*)); void enter(uint32_t in_serial, wl_surface* surface) { serial = in_serial; on_enter(in_serial, surface); } void leave(uint32_t in_serial, wl_surface* surface) { serial = in_serial; on_leave(in_serial, surface); } MOCK_METHOD5(input_panel_state, void(uint32_t, int32_t, int32_t, int32_t, int32_t)); MOCK_METHOD2(preedit_string, void(std::string const&, std::string const&)); MOCK_METHOD3(predit_styling, void(uint32_t, uint32_t, uint32_t)); MOCK_METHOD1(preedit_cursor, void(int32_t)); MOCK_METHOD1(commit_string, void(std::string const&)); MOCK_METHOD2(cursor_position, void(int32_t, int32_t)); MOCK_METHOD2(delete_surrounding_text, void(uint32_t, uint32_t)); MOCK_METHOD1(modifiers_map, void(wl_array*)); MOCK_METHOD4(keysym, void(uint32_t, uint32_t, uint32_t, uint32_t)); MOCK_METHOD1(language, void(std::string const&)); MOCK_METHOD1(text_direction, void(uint32_t)); MOCK_METHOD2(configure_surrounding_text, void(int32_t, int32_t)); MOCK_METHOD2(on_input_method_changed, void(uint32_t, uint32_t)); void input_method_changed(uint32_t in_serial, uint32_t reason) { serial = in_serial; on_input_method_changed(in_serial, reason); } static zwp_text_input_v2_listener constexpr listener { method_event_impl<&MockTextInputV2::enter>, method_event_impl<&MockTextInputV2::leave>, method_event_impl<&MockTextInputV2::input_panel_state>, method_event_impl<&MockTextInputV2::preedit_string>, method_event_impl<&MockTextInputV2::predit_styling>, method_event_impl<&MockTextInputV2::preedit_cursor>, method_event_impl<&MockTextInputV2::commit_string>, method_event_impl<&MockTextInputV2::cursor_position>, method_event_impl<&MockTextInputV2::delete_surrounding_text>, method_event_impl<&MockTextInputV2::modifiers_map>, method_event_impl<&MockTextInputV2::keysym>, method_event_impl<&MockTextInputV2::language>, method_event_impl<&MockTextInputV2::text_direction>, method_event_impl<&MockTextInputV2::configure_surrounding_text>, method_event_impl<&MockTextInputV2::input_method_changed> }; uint32_t serial; }; } #endif wlcs-1.7.0/include/mock_text_input_v3.h000066400000000000000000000041331453657357500201340ustar00rootroot00000000000000/* * Copyright © 2021 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef MOCK_TEXT_INPUT_V3_H #define MOCK_TEXT_INPUT_V3_H #include "generated/wayland-client.h" #include "generated/text-input-unstable-v3-client.h" #include "in_process_server.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include "method_event_impl.h" #include #include namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_text_input_manager_v3) WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_text_input_v3) class MockTextInputV3 : public WlHandle { public: MockTextInputV3(zwp_text_input_v3* proxy) : WlHandle{proxy} { zwp_text_input_v3_add_listener(proxy, &listener, this); } MOCK_METHOD1(enter, void(wl_surface *)); MOCK_METHOD1(leave, void(wl_surface *)); MOCK_METHOD3(preedit_string, void(std::string const&, int32_t, int32_t)); MOCK_METHOD1(commit_string, void(std::string const&)); MOCK_METHOD2(delete_surrounding_text, void(int32_t, int32_t)); MOCK_METHOD1(done, void(int32_t)); static zwp_text_input_v3_listener constexpr listener { method_event_impl<&MockTextInputV3::enter>, method_event_impl<&MockTextInputV3::leave>, method_event_impl<&MockTextInputV3::preedit_string>, method_event_impl<&MockTextInputV3::commit_string>, method_event_impl<&MockTextInputV3::delete_surrounding_text>, method_event_impl<&MockTextInputV3::done>, }; }; } #endif // MOCK_TEXT_INPUT_V3_H wlcs-1.7.0/include/mutex.h000066400000000000000000000063711453657357500154600ustar00rootroot00000000000000/* * Copyright © 2017 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2 or 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #ifndef WLCS_MUTEX_H_ #define WLCS_MUTEX_H_ #include #include #include namespace wlcs { /** * Smart-pointer-esque accessor for Mutex<> protected data. * * Ensures exclusive access to the referenced data. * * \tparam Guarded Type of data guarded by the mutex. */ template class MutexGuard { public: MutexGuard(std::unique_lock&& lock, Guarded& value) : value{value}, lock{std::move(lock)} { } MutexGuard(MutexGuard&& from) = default; ~MutexGuard() noexcept(false) { if (lock.owns_lock()) { lock.unlock(); } } Guarded& operator*() { return value; } Guarded* operator->() { return &value; } private: Guarded& value; std::unique_lock lock; }; /** * A data-locking mutex * * This is a mutex which owns the data it guards, and can give out a * smart-pointer-esque lock to lock and access it. * * \tparam Guarded The type of data guarded by the mutex */ template class Mutex { public: Mutex() = default; Mutex(Guarded&& initial_value) : value{std::move(initial_value)} { } Mutex(Mutex const&) = delete; Mutex& operator=(Mutex const&) = delete; /** * Lock the mutex and return an accessor for the protected data. * * \return A smart-pointer-esque accessor for the contained data. * While code has access to the MutexGuard it is guaranteed to have exclusive * access to the contained data. */ MutexGuard lock() { return MutexGuard{std::unique_lock{mutex}, value}; } protected: std::mutex mutex; Guarded value; }; template class WaitableMutex : public Mutex { public: using Mutex::Mutex; template MutexGuard wait_for(Predicate predicate, std::chrono::duration timeout) { std::unique_lock lock{this->mutex}; if (!notifier.wait_for(lock, timeout, [this, &predicate]() { return predicate(this->value); })) { BOOST_THROW_EXCEPTION((std::runtime_error{"Notification timeout"})); } return MutexGuard{std::move(lock), this->value}; } void notify_all() { notifier.notify_all(); } private: std::condition_variable notifier; }; } #endif //WLCS_MUTEX_H_ wlcs-1.7.0/include/pointer_constraints_unstable_v1.h000066400000000000000000000051331453657357500227230ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #ifndef WLCS_POINTER_CONSTRAINTS_UNSTABLE_V1_H #define WLCS_POINTER_CONSTRAINTS_UNSTABLE_V1_H #include "generated/pointer-constraints-unstable-v1-client.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include #include struct wl_pointer; namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_pointer_constraints_v1) class Client; class ZwpPointerConstraintsV1 { public: ZwpPointerConstraintsV1(Client& client); ~ZwpPointerConstraintsV1(); operator zwp_pointer_constraints_v1*() const; friend void zwp_pointer_constraints_v1_destroy(ZwpPointerConstraintsV1 const&) = delete; private: WlHandle const manager; }; class ZwpConfinedPointerV1 { public: ZwpConfinedPointerV1( ZwpPointerConstraintsV1& manager, wl_surface* surface, wl_pointer* pointer, wl_region* region, uint32_t lifetime); ~ZwpConfinedPointerV1(); MOCK_METHOD0(confined, void()); MOCK_METHOD0(unconfined, void()); operator zwp_confined_pointer_v1*() const; friend void zwp_confined_pointer_v1_destroy(ZwpConfinedPointerV1 const&) = delete; private: zwp_confined_pointer_v1* const relative_pointer; uint32_t const version; static zwp_confined_pointer_v1_listener const listener; }; class ZwpLockedPointerV1 { public: ZwpLockedPointerV1( ZwpPointerConstraintsV1& manager, wl_surface* surface, wl_pointer* pointer, wl_region* region, uint32_t lifetime); ~ZwpLockedPointerV1(); MOCK_METHOD0(locked, void()); MOCK_METHOD0(unlocked, void()); operator zwp_locked_pointer_v1*() const; friend void zwp_locked_pointer_v1_destroy(ZwpLockedPointerV1 const&) = delete; private: zwp_locked_pointer_v1* const locked_pointer; uint32_t const version; static zwp_locked_pointer_v1_listener const listener; }; } #endif //WLCS_POINTER_CONSTRAINTS_UNSTABLE_V1_H wlcs-1.7.0/include/primary_selection.h000066400000000000000000000134621453657357500200450ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #ifndef WLCS_PRIMARY_SELECTION_H #define WLCS_PRIMARY_SELECTION_H #include "generated/primary-selection-unstable-v1-client.h" #include "active_listeners.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include #include #include namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_primary_selection_device_manager_v1) class PrimarySelectionSource { public: using WrappedType = zwp_primary_selection_source_v1; PrimarySelectionSource() = default; explicit PrimarySelectionSource(zwp_primary_selection_device_manager_v1* manager) : self{zwp_primary_selection_device_manager_v1_create_source(manager), deleter} {} operator WrappedType*() const { return self.get(); } void reset() { self.reset(); } void reset(WrappedType* source) { self.reset(source, deleter); } friend void zwp_primary_selection_source_v1_destroy(PrimarySelectionSource const&) = delete; private: static void deleter(WrappedType* source) { zwp_primary_selection_source_v1_destroy(source); } std::shared_ptr self; }; class PrimarySelectionDevice { public: using WrappedType = zwp_primary_selection_device_v1; PrimarySelectionDevice() = default; PrimarySelectionDevice(zwp_primary_selection_device_manager_v1* manager, wl_seat* seat) : self{zwp_primary_selection_device_manager_v1_get_device(manager, seat), deleter} {} operator WrappedType*() const { return self.get(); } void reset() { self.reset(); } void reset(WrappedType* device) { self.reset(device, deleter); } friend void zwp_primary_selection_device_v1_destroy(PrimarySelectionDevice const&) = delete; private: static void deleter(WrappedType* device) { zwp_primary_selection_device_v1_destroy(device); } std::shared_ptr self; }; struct PrimarySelectionDeviceListener { PrimarySelectionDeviceListener(zwp_primary_selection_device_v1* device) { active_listeners.add(this); zwp_primary_selection_device_v1_add_listener(device, &thunks, this); } virtual ~PrimarySelectionDeviceListener() { active_listeners.del(this); } PrimarySelectionDeviceListener(PrimarySelectionDeviceListener const&) = delete; PrimarySelectionDeviceListener& operator=(PrimarySelectionDeviceListener const&) = delete; virtual void data_offer(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer); virtual void selection(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer); private: static void data_offer(void* data, zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer); static void selection(void* data, zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer); static ActiveListeners active_listeners; constexpr static zwp_primary_selection_device_v1_listener thunks = { &data_offer, &selection }; }; struct PrimarySelectionOfferListener { PrimarySelectionOfferListener() { active_listeners.add(this); } virtual ~PrimarySelectionOfferListener() { active_listeners.del(this); } PrimarySelectionOfferListener(PrimarySelectionOfferListener const&) = delete; PrimarySelectionOfferListener& operator=(PrimarySelectionOfferListener const&) = delete; void listen_to(zwp_primary_selection_offer_v1* offer) { zwp_primary_selection_offer_v1_add_listener(offer, &thunks, this); } virtual void offer(zwp_primary_selection_offer_v1* offer, const char* mime_type); private: static void offer(void* data, zwp_primary_selection_offer_v1* offer, const char* mime_type); static ActiveListeners active_listeners; constexpr static zwp_primary_selection_offer_v1_listener thunks = { &offer, }; }; struct PrimarySelectionSourceListener { explicit PrimarySelectionSourceListener(PrimarySelectionSource const& source) { active_listeners.add(this); zwp_primary_selection_source_v1_add_listener(source, &thunks, this); } virtual ~PrimarySelectionSourceListener() { active_listeners.del(this); } PrimarySelectionSourceListener(PrimarySelectionSourceListener const&) = delete; PrimarySelectionSourceListener& operator=(PrimarySelectionSourceListener const&) = delete; virtual void send(zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd); virtual void cancelled(zwp_primary_selection_source_v1* source); private: static void send(void* data, zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd); static void cancelled(void* data, zwp_primary_selection_source_v1* source); static ActiveListeners active_listeners; constexpr static zwp_primary_selection_source_v1_listener thunks = { &send, &cancelled }; }; } #endif //WLCS_PRIMARY_SELECTION_H wlcs-1.7.0/include/relative_pointer_unstable_v1.h000066400000000000000000000041101453657357500221610ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #ifndef WLCS_RELATIVE_POINTER_UNSTABLE_V1_H #define WLCS_RELATIVE_POINTER_UNSTABLE_V1_H #include "generated/relative-pointer-unstable-v1-client.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include #include struct wl_pointer; namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_relative_pointer_manager_v1) class Client; class ZwpRelativePointerManagerV1 { public: ZwpRelativePointerManagerV1(Client& client); ~ZwpRelativePointerManagerV1(); operator zwp_relative_pointer_manager_v1*() const; friend void zwp_relative_pointer_manager_v1_destroy(ZwpRelativePointerManagerV1 const&) = delete; private: WlHandle manager; }; class ZwpRelativePointerV1 { public: ZwpRelativePointerV1(ZwpRelativePointerManagerV1& manager, wl_pointer* pointer); ~ZwpRelativePointerV1(); MOCK_METHOD6(relative_motion, void(uint32_t utime_hi, uint32_t utime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel)); operator zwp_relative_pointer_v1*() const; friend void zwp_relative_pointer_v1_destroy(ZwpRelativePointerV1 const&) = delete; private: zwp_relative_pointer_v1* const relative_pointer; uint32_t const version; static zwp_relative_pointer_v1_listener const listener; }; } #endif //WLCS_RELATIVE_POINTER_UNSTABLE_V1_H wlcs-1.7.0/include/shared_library.h000066400000000000000000000031271453657357500173040ustar00rootroot00000000000000/* * Copyright © 2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2 or 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #ifndef MIR_SHARED_LIBRARY_H_ #define MIR_SHARED_LIBRARY_H_ #include namespace wlcs { class SharedLibrary { public: explicit SharedLibrary(char const* library_name); explicit SharedLibrary(std::string const& library_name); ~SharedLibrary() noexcept; template FunctionPtr load_function(char const* function_name) const { FunctionPtr result{}; (void*&)result = load_symbol(function_name); return result; } template FunctionPtr load_function(std::string const& function_name) const { return load_function(function_name.c_str()); } private: void* const so; void* load_symbol(char const* function_name) const; SharedLibrary(SharedLibrary const&) = delete; SharedLibrary& operator=(SharedLibrary const&) = delete; }; } #endif /* MIR_SHARED_LIBRARY_H_ */ wlcs-1.7.0/include/surface_builder.h000066400000000000000000000057721453657357500174600ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef WLCS_SURFACE_BUILDER_H_ #define WLCS_SURFACE_BUILDER_H_ #include #include #include "in_process_server.h" namespace wlcs { struct SurfaceBuilder { SurfaceBuilder(std::string const& name) : name{name} {} virtual ~SurfaceBuilder() = default; virtual auto build( wlcs::Server& server, wlcs::Client& client, std::pair position, std::pair size) const -> std::unique_ptr = 0; std::string const name; static auto all_surface_types() -> std::vector>; static auto toplevel_surface_types() -> std::vector>; static auto surface_builder_to_string( testing::TestParamInfo> builder) -> std::string; }; struct WlShellSurfaceBuilder : SurfaceBuilder { WlShellSurfaceBuilder() : SurfaceBuilder{"wl_shell_surface"} {} auto build( wlcs::Server& server, wlcs::Client& client, std::pair position, std::pair size) const -> std::unique_ptr override; }; struct XdgV6SurfaceBuilder : SurfaceBuilder { XdgV6SurfaceBuilder() : SurfaceBuilder{"zxdg_surface_v6"} {} auto build( wlcs::Server& server, wlcs::Client& client, std::pair position, std::pair size) const -> std::unique_ptr override; }; struct XdgStableSurfaceBuilder : SurfaceBuilder { XdgStableSurfaceBuilder(int left_offset, int top_offset, int right_offset, int bottom_offset); auto build( wlcs::Server& server, wlcs::Client& client, std::pair position, std::pair size) const -> std::unique_ptr override; int left_offset, top_offset, right_offset, bottom_offset; }; struct SubsurfaceBuilder : SurfaceBuilder { SubsurfaceBuilder(std::pair offset); auto build( wlcs::Server& server, wlcs::Client& client, std::pair position, std::pair size) const -> std::unique_ptr override; std::pair offset; }; // TODO: popup surfaces } namespace std { std::ostream& operator<<(std::ostream& out, std::shared_ptr const& param); } #endif // WLCS_SURFACE_BUILDER_H_ wlcs-1.7.0/include/version_specifier.h000066400000000000000000000036651453657357500200370ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #ifndef WLCS_VERSION_SPECIFIER_H_ #define WLCS_VERSION_SPECIFIER_H_ #include #include #include namespace wlcs { class VersionSpecifier { public: VersionSpecifier() = default; virtual ~VersionSpecifier() = default; virtual auto select_version( uint32_t max_available_version, uint32_t max_supported_version) const -> std::optional = 0; virtual auto describe() const -> std::string = 0; }; class ExactlyVersion : public VersionSpecifier { public: explicit ExactlyVersion(uint32_t version) noexcept; auto select_version( uint32_t max_available_version, uint32_t max_supported_version) const -> std::optional override; auto describe() const -> std::string override; private: uint32_t const version; }; class AtLeastVersion : public VersionSpecifier { public: explicit AtLeastVersion(uint32_t version) noexcept; auto select_version( uint32_t max_available_version, uint32_t max_supported_version) const -> std::optional override; auto describe() const -> std::string override; private: uint32_t const version; }; extern VersionSpecifier const& AnyVersion; } #endif //WLCS_VERSION_SPECIFIER_H_ wlcs-1.7.0/include/wl_handle.h000066400000000000000000000051311453657357500162440ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers * William Wold */ #ifndef WLCS_WL_PROXY_H_ #define WLCS_WL_PROXY_H_ #include "wl_interface_descriptor.h" #include "boost/throw_exception.hpp" #include "boost/current_function.hpp" #include #include struct wl_proxy; namespace wlcs { template class WlHandle { public: explicit WlHandle(T* const proxy) : proxy{proxy}, owns_wl_object{true} { static_assert( WlInterfaceDescriptor::has_specialisation, "Missing specialisation for WlInterfaceDescriptor"); if (proxy == nullptr) { BOOST_THROW_EXCEPTION((std::logic_error{"Attempt to construct a WlHandle from null Wayland object"})); } } ~WlHandle() { if (owns_wl_object) { (*WlInterfaceDescriptor::destructor)(proxy); } } WlHandle(WlHandle&& from) : WlHandle(from.proxy) { from.owns_wl_object = false; } WlHandle(WlHandle const&) = delete; auto operator=(WlHandle&&) -> WlHandle& = delete; auto operator=(WlHandle const&) -> bool = delete; operator T*() const { // This is a precondition failure, but as this is a test-suite let's be generous and make it fail hard and fast if (!owns_wl_object) { std::abort(); } return proxy; } auto wl_proxy() const -> struct wl_proxy* { // This is a precondition failure, but as this is a test-suite let's be generous and make it fail hard and fast if (!owns_wl_object) { std::abort(); } return static_cast(proxy); } private: T* const proxy; bool owns_wl_object; }; template auto wrap_wl_object(WlType* proxy) -> WlHandle { return WlHandle{proxy}; } } #endif // WLCS_WL_PROXY_H_ wlcs-1.7.0/include/wl_interface_descriptor.h000066400000000000000000000042261453657357500212130ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #ifndef WLCS_WL_INTERFACE_DESCRIPTOR_H #define WLCS_WL_INTERFACE_DESCRIPTOR_H struct wl_interface; namespace wlcs { /*** * A specialisable struct containing the constants and types associated with a Wayland protocol * * \tparam WlType The base Wayland object type (eg; wl_surface, xdg_wm_base, etc) */ template struct WlInterfaceDescriptor { // Needed because apparently GCC < 10 can't compare a constexpr wl_interface const* const to nullptr in constexpr context?! static constexpr bool const has_specialisation = false; static constexpr wl_interface const* const interface = nullptr; static constexpr void (* const destructor)(WlType*) = nullptr; }; } /*** * Declare a specialisation of WlInterfaceDescriptor for \param name * * This will use the standard Wayland conventions of * name - name_interface - name_destroy * (eg: wl_surface - wl_surface_interface - wl_surface_destroy) * * If an interface requires special handling, a manual specialisation can be * provided (for example, see wl_output handling in in_process_server.h) */ #define WLCS_CREATE_INTERFACE_DESCRIPTOR(name) \ template<> \ struct WlInterfaceDescriptor \ { \ static constexpr bool const has_specialisation = true; \ static constexpr wl_interface const* const interface = &name##_interface; \ static constexpr void(* const destructor)(name*) = &name##_destroy; \ }; #endif //WLCS_WL_INTERFACE_DESCRIPTOR_H wlcs-1.7.0/include/wlcs/000077500000000000000000000000001453657357500151065ustar00rootroot00000000000000wlcs-1.7.0/include/wlcs/display_server.h000066400000000000000000000150051453657357500203130ustar00rootroot00000000000000/* * Copyright © 2017 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #ifndef WLCS_SERVER_H_ #define WLCS_SERVER_H_ #include #include #ifdef __cplusplus extern "C" { #endif typedef struct wl_surface wl_surface; typedef struct wl_display wl_display; typedef struct wl_event_loop wl_event_loop; typedef struct WlcsPointer WlcsPointer; typedef struct WlcsTouch WlcsTouch; /** * Maximum version of WlcsIntegrationDescriptor this header provides a definition for */ #define WLCS_INTEGRATION_DESCRIPTOR_VERSION 1 typedef struct WlcsExtensionDescriptor WlcsExtensionDescriptor; struct WlcsExtensionDescriptor { /** * Protocol name of extension (eg: wl_shell, xdg_shell, etc) */ char const* name; /** * Maximum version of extension supported */ uint32_t version; }; typedef struct WlcsIntegrationDescriptor WlcsIntegrationDescriptor; struct WlcsIntegrationDescriptor { uint32_t version; /**< Version of the struct this instance provides */ size_t num_extensions; /**< Length of the supported_extensions array */ /** * Array of extension descriptions * * This must be an array of length num_extensions; that is, accesses from * supported_extensions[0] to supported_extensions[num_extensions - 1] * must be valid. */ WlcsExtensionDescriptor const* supported_extensions; }; /** * Maximum version of WlcsDisplayServer this header provides a definition for */ #define WLCS_DISPLAY_SERVER_VERSION 3 typedef struct WlcsDisplayServer WlcsDisplayServer; struct WlcsDisplayServer { uint32_t version; /**< Version of the struct this instance provides */ /** * Start the display server's mainloop. * * This should *not* block until the mainloop exits, which implies the mainloop * will need to be run in a separate thread. * * This does not need to block until the display server is ready to process * input, but the WlcsDisplayServer does need to be able to process other * calls (notably create_client_socket) once this returns. */ void (*start)(WlcsDisplayServer* server); /** * Stop the display server's mainloop. * * In contrast to the start hook, this *should* block until the server's mainloop * has been torn down, so that it does not persist into later tests. */ void (*stop)(WlcsDisplayServer* server); /** * Create a socket that can be connected to by wl_display_connect_fd * * \return A FD to a client Wayland socket. WLCS owns this fd, and will close * it as necessary. */ int (*create_client_socket)(WlcsDisplayServer* server); /** * Position a window in the compositor coördinate space * * \param client the (wayland-client-side) wl_client which owns the * surface * \param surface the (wayland-client-side) wl_surface* * \param x x coördinate (in compositor-space pixels) to move the * left of the window to * \param y y coördinate (in compositor-space pixels) to move the * top of the window to */ void (*position_window_absolute)(WlcsDisplayServer* server, wl_display* client, wl_surface* surface, int x, int y); /** * Create a fake pointer device */ WlcsPointer* (*create_pointer)(WlcsDisplayServer* server); /** * Create a mock touch object */ WlcsTouch* (*create_touch)(WlcsDisplayServer* server); /* Added in version 2 */ /** * Describe the capabilities of this WlcsDisplayServer * * WLCS will use this description to skip tests that the display server * is known to not support. For example, if the set of extensions * described by the WlcsIntegrationDescriptor does not include "xdg_shell" * then all XDG Shell tests will be skipped. * * Different WlcsDisplayServer instances may report different * capabilities (for example, if command line options should influence * the set of extensions exposed). */ WlcsIntegrationDescriptor const* (*get_descriptor)(WlcsDisplayServer const* server); /* Added in version 3 */ /** * Start the display server's event loop, blocking the calling thread. * * When started in this way WLCS will proxy all requests to this mainloop. * All calls to WLCS interfaces will be dispatched from the * wlcs_event_dispatcher loop, so implementations are required to drive * this loop from their own. * * \note This is an optional interface. An implementation must provide at * least one of {start, start_on_this_thread}, but does not need to * provide both. If both are provided, start is preferred. * \param wlcs_event_dispatcher */ void (*start_on_this_thread)(WlcsDisplayServer* server, wl_event_loop* wlcs_event_dispatcher); }; /** * Maximum version of WlcsServerIntegration this header provides a definition of */ #define WLCS_SERVER_INTEGRATION_VERSION 1 typedef struct WlcsServerIntegration WlcsServerIntegration; struct WlcsServerIntegration { uint32_t version; /**< Version of the struct this instance provides */ /** * Create a WlcsDisplayServer instance * * This can do any setup necessary, but should not start the compositor's * mainloop. * * \param argc Command line argument count (after wlcs-specific options * have been stripped) * \param argv Command line arguments (after wlcs-specific options have * been stripped) * \return */ WlcsDisplayServer* (*create_server)(int argc, char const** argv); void (*destroy_server)(WlcsDisplayServer* server); }; /** * Main WLCS entry point * * WLCS will load this symbol to discover the compositor integration * hooks. */ extern WlcsServerIntegration const wlcs_server_integration; #ifdef __cplusplus } #endif #endif //WLCS_SERVER_H_ wlcs-1.7.0/include/wlcs/pointer.h000066400000000000000000000037731453657357500167510ustar00rootroot00000000000000/* * Copyright © 2017 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #ifndef WLCS_POINTER_H_ #define WLCS_POINTER_H_ #include #ifdef __cplusplus extern "C" { #endif /** * Maximum version of WlcsPointer this header provides a definition for */ #define WLCS_POINTER_VERSION 1 typedef struct WlcsPointer WlcsPointer; /** * An object to manipulate the server's pointer state */ struct WlcsPointer { uint32_t version; /**< Version of the struct this instance provides */ /** * Move the pointer to the specified location, in compositor coördinate space */ void (*move_absolute)(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y); /** * Move the pointer by the specified amount, in compositor coördinates. */ void (*move_relative)(WlcsPointer* pointer, wl_fixed_t dx, wl_fixed_t dy); /** * Generate a button-up event * * \param button Button code (as per wl_pointer. eg: BTN_LEFT) */ void (*button_up)(WlcsPointer* pointer, int button); /** * Generate a button-down event * * \param button Button code (as per wl_pointer. eg: BTN_LEFT) */ void (*button_down)(WlcsPointer* pointer, int button); /** * Destroy this pointer, freeing any resources. */ void (*destroy)(WlcsPointer* pointer); }; #ifdef __cplusplus } #endif #endif //WLCS_SERVER_H_ wlcs-1.7.0/include/wlcs/touch.h000066400000000000000000000024531453657357500164050ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef WLCS_TOUCH_H_ #define WLCS_TOUCH_H_ #include #ifdef __cplusplus extern "C" { #endif /** * Maximum version of WlcsTouch this header provides a definition for */ #define WLCS_TOUCH_VERSION 1 typedef struct WlcsTouch WlcsTouch; struct WlcsTouch { uint32_t version; /**< Version of the struct this instance provides */ void (*touch_down)(WlcsTouch* touch, wl_fixed_t x, wl_fixed_t y); void (*touch_move)(WlcsTouch* touch, wl_fixed_t x, wl_fixed_t y); void (*touch_up)(WlcsTouch* touch); void (*destroy)(WlcsTouch* touch); }; #ifdef __cplusplus } #endif #endif //WLCS_TOUCH_H_ wlcs-1.7.0/include/xdg_output_v1.h000066400000000000000000000036601453657357500171240ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef XDG_OUTPUT_V1_H #define XDG_OUTPUT_V1_H #include "generated/wayland-client.h" #include "generated/xdg-output-unstable-v1-client.h" #include "in_process_server.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include #include namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zxdg_output_manager_v1) class XdgOutputManagerV1 { public: XdgOutputManagerV1(Client& client); ~XdgOutputManagerV1(); operator zxdg_output_manager_v1*() const; auto client() const -> Client&; private: struct Impl; std::unique_ptr const impl; }; class XdgOutputV1 { public: struct State { std::optional> logical_position; std::optional> logical_size; std::optional name; std::optional description; }; XdgOutputV1(XdgOutputManagerV1& manager, size_t output_index); XdgOutputV1(XdgOutputV1 const&) = delete; XdgOutputV1 const& operator=(XdgOutputV1 const&) = delete; operator zxdg_output_v1*() const; auto state() -> State const&; private: struct Impl; std::shared_ptr const impl; // shared instead of unique because interally a weak is needed }; } #endif // XDG_OUTPUT_V1_H wlcs-1.7.0/include/xdg_shell_stable.h000066400000000000000000000064541453657357500176230ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef WLCS_XDG_SHELL_STABLE_H #define WLCS_XDG_SHELL_STABLE_H #include "in_process_server.h" #include "generated/xdg-shell-client.h" #include "wl_interface_descriptor.h" #include "wl_handle.h" #include 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(); MOCK_METHOD(void, configure, (uint32_t serial)); operator xdg_surface*() const {return shell_surface;} private: 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(); MOCK_METHOD(void, configure, (int32_t width, int32_t height, wl_array* states)); MOCK_METHOD(void, close, ()); MOCK_METHOD(void, configure_bounds, (int32_t width, int32_t height)); MOCK_METHOD(void, wm_capabilities, (wl_array* capabilities)); operator xdg_toplevel*() const {return toplevel;} XdgSurfaceStable* const shell_surface; xdg_toplevel* toplevel; }; class XdgPositionerStable { public: XdgPositionerStable(wlcs::Client& client); ~XdgPositionerStable(); operator xdg_positioner*() const {return positioner;} auto setup_default(std::pair size) -> XdgPositionerStable&; private: xdg_positioner* const positioner; }; class XdgPopupStable { public: XdgPopupStable( XdgSurfaceStable& shell_surface_, std::optional parent, XdgPositionerStable& positioner); XdgPopupStable(XdgPopupStable const&) = delete; XdgPopupStable& operator=(XdgPopupStable const&) = delete; ~XdgPopupStable(); MOCK_METHOD(void, configure, (int32_t x, int32_t y, int32_t width, int32_t height)); MOCK_METHOD(void, done, ()); MOCK_METHOD(void, repositioned, (uint32_t token)); operator xdg_popup*() const {return popup;} XdgSurfaceStable* const shell_surface; xdg_popup* const popup; std::vector> configure_notifiers; std::vector> popup_done_notifiers; }; } #endif // WLCS_XDG_SHELL_STABLE_H wlcs-1.7.0/include/xdg_shell_v6.h000066400000000000000000000052261453657357500167000ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #ifndef WLCS_XDG_SHELL_V6_ #define WLCS_XDG_SHELL_V6_ #include "in_process_server.h" #include "generated/xdg-shell-unstable-v6-client.h" #include namespace wlcs { class XdgSurfaceV6 { public: XdgSurfaceV6(wlcs::Client& client, wlcs::Surface& surface); XdgSurfaceV6(XdgSurfaceV6 const&) = delete; XdgSurfaceV6& operator=(XdgSurfaceV6 const&) = delete; ~XdgSurfaceV6(); MOCK_METHOD(void, configure, (uint32_t serial)); operator zxdg_surface_v6*() const {return shell_surface;} private: 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(); MOCK_METHOD(void, configure, (int32_t width, int32_t height, wl_array* states)); MOCK_METHOD(void, close, ()); operator zxdg_toplevel_v6*() const {return toplevel;} XdgSurfaceV6* const shell_surface; zxdg_toplevel_v6* toplevel; }; 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(); MOCK_METHOD(void, configure, (int32_t x, int32_t y, int32_t width, int32_t height)); MOCK_METHOD(void, done, ()); operator zxdg_popup_v6*() const {return popup;} XdgSurfaceV6* const shell_surface; zxdg_popup_v6* const popup; }; } #endif // WLCS_XDG_SHELL_V6_ wlcs-1.7.0/spread.yaml000066400000000000000000000012131453657357500146520ustar00rootroot00000000000000project: wlcs kill-timeout: 50m backends: lxd: systems: - ubuntu-22.04 - ubuntu-23.04 - ubuntu-23.10 - ubuntu-devel: image: ubuntu-daily:devel/amd64 - fedora-37 - fedora-38 - fedora-39 - fedora-rawhide: image: images:fedora/39 - alpine-3.18 - alpine-edge suites: spread/build/: summary: Build wlcs environment: CC/gcc: gcc CXX/gcc: g++ CC/clang: clang CXX/clang: clang++ path: /spread/wlcs exclude: - .git wlcs-1.7.0/spread/000077500000000000000000000000001453657357500137715ustar00rootroot00000000000000wlcs-1.7.0/spread/build/000077500000000000000000000000001453657357500150705ustar00rootroot00000000000000wlcs-1.7.0/spread/build/alpine/000077500000000000000000000000001453657357500163405ustar00rootroot00000000000000wlcs-1.7.0/spread/build/alpine/task.yaml000066400000000000000000000006771453657357500202000ustar00rootroot00000000000000summary: 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) wlcs-1.7.0/spread/build/fedora/000077500000000000000000000000001453657357500163305ustar00rootroot00000000000000wlcs-1.7.0/spread/build/fedora/task.yaml000066400000000000000000000012261453657357500201570ustar00rootroot00000000000000summary: Build (on Fedora) systems: [fedora-*] execute: | if [[ "${SPREAD_SYSTEM}" == "fedora-rawhide" ]]; then dnf --refresh --assumeyes upgrade dnf --assumeyes install fedora-repos-rawhide dnf --refresh --assumeyes --enablerepo=rawhide distro-sync fi 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) wlcs-1.7.0/spread/build/ubuntu/000077500000000000000000000000001453657357500164125ustar00rootroot00000000000000wlcs-1.7.0/spread/build/ubuntu/task.yaml000066400000000000000000000016501453657357500202420ustar00rootroot00000000000000summary: Build (on Ubuntu) systems: [-fedora-*, -alpine-*] execute: | # Grab builds of Mir git master add-apt-repository ppa:mir-team/dev apt-get 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 } wlcs-1.7.0/src/000077500000000000000000000000001453657357500133025ustar00rootroot00000000000000wlcs-1.7.0/src/data_device.cpp000066400000000000000000000104341453657357500162400ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "data_device.h" wlcs::ActiveListeners wlcs::DataDeviceListener::active_listeners; constexpr wl_data_device_listener wlcs::DataDeviceListener::thunks; void wlcs::DataDeviceListener::data_offer(void* data, struct wl_data_device* wl_data_device, struct wl_data_offer* id) { if (active_listeners.includes(data)) static_cast(data)->data_offer(wl_data_device, id); } void wlcs::DataDeviceListener::enter( void* data, struct wl_data_device* wl_data_device, uint32_t serial, struct wl_surface* surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer* id) { if (active_listeners.includes(data)) static_cast(data)->enter(wl_data_device, serial, surface, x, y, id); } void wlcs::DataDeviceListener::leave(void* data, struct wl_data_device* wl_data_device) { if (active_listeners.includes(data)) static_cast(data)->leave(wl_data_device); } void wlcs::DataDeviceListener::motion( void* data, struct wl_data_device* wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) { if (active_listeners.includes(data)) static_cast(data)->motion(wl_data_device, time, x, y); } void wlcs::DataDeviceListener::drop(void* data, struct wl_data_device* wl_data_device) { if (active_listeners.includes(data)) static_cast(data)->drop(wl_data_device); } void wlcs::DataDeviceListener::selection( void* data, struct wl_data_device* wl_data_device, struct wl_data_offer* id) { if (active_listeners.includes(data)) static_cast(data)->selection(wl_data_device, wl_data_device, id); } void wlcs::DataDeviceListener::data_offer(struct wl_data_device* /*wl_data_device*/, struct wl_data_offer* /*id*/) { } void wlcs::DataDeviceListener::enter( struct wl_data_device* /*wl_data_device*/, uint32_t /*serial*/, struct wl_surface* /*surface*/, wl_fixed_t /*x*/, wl_fixed_t /*y*/, struct wl_data_offer* /*id*/) { } void wlcs::DataDeviceListener::leave(struct wl_data_device* /*wl_data_device*/) { } void wlcs::DataDeviceListener::motion( struct wl_data_device* /*wl_data_device*/, uint32_t /*time*/, wl_fixed_t /*x*/, wl_fixed_t /*y*/) { } void wlcs::DataDeviceListener::drop(struct wl_data_device* /*wl_data_device*/) { } void wlcs::DataDeviceListener::selection( struct wl_data_device* /*wl_data_device*/, struct wl_data_offer* /*id*/) { } wlcs::ActiveListeners wlcs::DataOfferListener::active_listeners; constexpr wl_data_offer_listener wlcs::DataOfferListener::thunks; void wlcs::DataOfferListener::offer(void* data, struct wl_data_offer* data_offer, char const* mime_type) { if (active_listeners.includes(data)) static_cast(data)->offer(data_offer, mime_type); } void wlcs::DataOfferListener::source_actions(void* data, struct wl_data_offer* data_offer, uint32_t dnd_actions) { if (active_listeners.includes(data)) static_cast(data)->source_actions(data_offer, dnd_actions); } void wlcs::DataOfferListener::action(void* data, struct wl_data_offer* data_offer, uint32_t dnd_action) { if (active_listeners.includes(data)) static_cast(data)->action(data_offer, dnd_action); } void wlcs::DataOfferListener::offer(struct wl_data_offer* /*data_offer*/, char const* /*mime_type*/) { } void wlcs::DataOfferListener::source_actions(struct wl_data_offer* /*data_offer*/, uint32_t /*dnd_actions*/) { } void wlcs::DataOfferListener::action(struct wl_data_offer* /*data_offer*/, uint32_t /*dnd_action*/) { } wlcs-1.7.0/src/gtk_primary_selection.cpp000066400000000000000000000062321453657357500204060ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "gtk_primary_selection.h" #include wlcs::ActiveListeners wlcs::GtkPrimarySelectionDeviceListener::active_listeners; constexpr gtk_primary_selection_device_listener wlcs::GtkPrimarySelectionDeviceListener::thunks; wlcs::ActiveListeners wlcs::GtkPrimarySelectionOfferListener::active_listeners; constexpr gtk_primary_selection_offer_listener wlcs::GtkPrimarySelectionOfferListener::thunks; wlcs::ActiveListeners wlcs::GtkPrimarySelectionSourceListener::active_listeners; constexpr gtk_primary_selection_source_listener wlcs::GtkPrimarySelectionSourceListener::thunks; void wlcs::GtkPrimarySelectionDeviceListener::data_offer( void* data, gtk_primary_selection_device* device, gtk_primary_selection_offer* offer) { if (active_listeners.includes(data)) static_cast(data)->data_offer(device, offer); } void wlcs::GtkPrimarySelectionDeviceListener::data_offer(gtk_primary_selection_device*, gtk_primary_selection_offer*) { } void wlcs::GtkPrimarySelectionDeviceListener::selection(gtk_primary_selection_device*, gtk_primary_selection_offer*) { } void wlcs::GtkPrimarySelectionDeviceListener::selection( void* data, gtk_primary_selection_device* device, gtk_primary_selection_offer* offer) { if (active_listeners.includes(data)) static_cast(data)->selection(device, offer); } void wlcs::GtkPrimarySelectionOfferListener::offer(gtk_primary_selection_offer*, const char*) { } void wlcs::GtkPrimarySelectionOfferListener::offer( void* data, gtk_primary_selection_offer* offer, const char* mime_type) { if (active_listeners.includes(data)) static_cast(data)->offer(offer, mime_type); } void wlcs::GtkPrimarySelectionSourceListener::send(gtk_primary_selection_source*, const char*, int32_t fd) { close(fd); } void wlcs::GtkPrimarySelectionSourceListener::cancelled(gtk_primary_selection_source*) { } void wlcs::GtkPrimarySelectionSourceListener::send( void* data, gtk_primary_selection_source* source, const char* mime_type, int32_t fd) { if (active_listeners.includes(data)) static_cast(data)->send(source, mime_type, fd); } void wlcs::GtkPrimarySelectionSourceListener::cancelled(void* data, gtk_primary_selection_source* source) { if (active_listeners.includes(data)) static_cast(data)->cancelled(source); } wlcs-1.7.0/src/helpers.cpp000066400000000000000000000075221453657357500154560ustar00rootroot00000000000000/* * Copyright © 2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2 or 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authored by: * Alexandros Frantzis */ #include "helpers.h" #include "shared_library.h" #include #include #include #include #include /* Since kernel 6.3 it generates a warning to construct a memfd without one of * MFD_EXEC (to mark the memfd as executable) or MFD_NOEXEC_SEAL (to permanently * prevent the memfd from being marked as executable). * * Since we don't need execution from our shm buffers, we can mark them as * MFD_NOEXEC_SEAL. Since this is only silencing a warning in dmesg we can safely * null it out if we're building against too-old headers. */ #ifndef MFD_NOEXEC_SEAL #define MFD_NOEXEC_SEAL 0 #endif namespace { bool error_indicates_tmpfile_not_supported(int error) { return error == EISDIR || // Directory exists, but no support for O_TMPFILE error == ENOENT || // Directory doesn't exist, and no support for O_TMPFILE error == EOPNOTSUPP || // Filesystem that directory resides on does not support O_TMPFILE error == EINVAL; // There apparently exists at least one development board that has a kernel // that incorrectly returns EINVAL. Yay. } int memfd_create(char const* name, unsigned int flags) { return static_cast(syscall(SYS_memfd_create, name, flags)); } } int wlcs::helpers::create_anonymous_file(size_t size) { int fd = memfd_create("wlcs-unnamed", MFD_CLOEXEC | MFD_NOEXEC_SEAL); if (fd == -1 && errno == EINVAL) { // Maybe we're running on a kernel prior to MFD_NOEXEC_SEAL? fd = memfd_create("wlcs-unnamed", MFD_CLOEXEC); } if (fd == -1 && errno == ENOSYS) { fd = open("/dev/shm", O_TMPFILE | O_RDWR | O_EXCL | O_CLOEXEC, S_IRWXU); // Workaround for filesystems that don't support O_TMPFILE if (fd == -1 && error_indicates_tmpfile_not_supported(errno)) { char template_filename[] = "/dev/shm/wlcs-buffer-XXXXXX"; fd = mkostemp(template_filename, O_CLOEXEC); if (fd != -1) { if (unlink(template_filename) < 0) { close(fd); fd = -1; } } } } if (fd == -1) { BOOST_THROW_EXCEPTION( std::system_error(errno, std::system_category(), "Failed to open temporary file")); } if (ftruncate(fd, size) == -1) { close(fd); BOOST_THROW_EXCEPTION( std::system_error(errno, std::system_category(), "Failed to resize temporary file")); } return fd; } namespace { static int argc; static char const** argv; std::shared_ptr entry_point; } void wlcs::helpers::set_command_line(int argc, char const** argv) { ::argc = argc; ::argv = argv; } int wlcs::helpers::get_argc() { return ::argc; } char const** wlcs::helpers::get_argv() { return ::argv; } void wlcs::helpers::set_entry_point(std::shared_ptr const& entry_point) { ::entry_point = entry_point; } std::shared_ptr wlcs::helpers::get_test_hooks() { return ::entry_point; } wlcs-1.7.0/src/in_process_server.cpp000066400000000000000000001734161453657357500175540ustar00rootroot00000000000000/* * Copyright © 2017-2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #include "in_process_server.h" #include "thread_proxy.h" #include "version_specifier.h" #include "wlcs/display_server.h" #include "helpers.h" #include "wlcs/pointer.h" #include "wlcs/touch.h" #include "xdg_shell_v6.h" #include "xdg_shell_stable.h" #include "generated/wayland-client.h" #include "generated/xdg-shell-unstable-v6-client.h" #include "generated/xdg-shell-client.h" #include "linux/input.h" #include #include #include #include #include #include #include #include #include #include using namespace std::literals::chrono_literals; class ShimNotImplemented : public std::logic_error { public: ShimNotImplemented() : std::logic_error("Function not implemented in display server shim") { } ShimNotImplemented(const std::string& name) : std::logic_error("Function '" + name + "()' not implemented in display server shim") { } }; namespace { auto interface_description_if_valid(wl_interface const* interface) -> std::string { if (interface) { using namespace std::literals::string_literals; return interface->name + " v"s + std::to_string(interface->version); } return ""; } } wlcs::ProtocolError::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_description_if_valid(interface)} { } char const* wlcs::ProtocolError::what() const noexcept { return message.c_str(); } uint32_t wlcs::ProtocolError::error_code() const { return code_; } wl_interface const* wlcs::ProtocolError::interface() const { return interface_; } wlcs::ExtensionExpectedlyNotSupported::ExtensionExpectedlyNotSupported(char const* extension, VersionSpecifier const& version) : std::runtime_error{ std::string{"Extension: "} + extension + " version " + version.describe() + " not supported by compositor under test."} { auto const skip_reason = std::string{"Missing extension: "} + extension + version.describe(); ::testing::Test::RecordProperty("wlcs-skip-test", skip_reason); } wlcs::Timeout::Timeout(char const* message) : std::runtime_error(message) { } class wlcs::Pointer::Impl { public: template Impl( WlcsPointer* raw_device, std::shared_ptr const& proxy, std::shared_ptr const& keep_dso_loaded) : keep_dso_loaded{keep_dso_loaded}, pointer{raw_device} { setup_thunks(proxy); } ~Impl() { destroy_thunk(); } void move_to(int x, int y) { move_absolute_thunk(wl_fixed_from_int(x), wl_fixed_from_int(y)); } void move_by(int dx, int dy) { move_relative_thunk(wl_fixed_from_int(dx), wl_fixed_from_int(dy)); } void button_down(int button) { button_down_thunk(button); } void button_up(int button) { button_up_thunk(button); } private: template void setup_thunks(std::shared_ptr const& proxy) { move_absolute_thunk = proxy->register_op( [this](wl_fixed_t x, wl_fixed_t y) { pointer->move_absolute(pointer, x, y); }); move_relative_thunk = proxy->register_op( [this](wl_fixed_t dx, wl_fixed_t dy) { pointer->move_relative(pointer, dx, dy); }); button_down_thunk = proxy->register_op( [this](int button) { pointer->button_down(pointer, button); }); button_up_thunk = proxy->register_op( [this](int button) { pointer->button_up(pointer, button); }); destroy_thunk = proxy->register_op( [this]() { pointer->destroy(pointer); }); } std::shared_ptr const keep_dso_loaded; WlcsPointer* const pointer; std::function move_absolute_thunk; std::function move_relative_thunk; std::function button_down_thunk; std::function button_up_thunk; std::function destroy_thunk; }; wlcs::Pointer::~Pointer() = default; wlcs::Pointer::Pointer(Pointer&&) = default; template wlcs::Pointer::Pointer( WlcsPointer* raw_device, std::shared_ptr const& proxy, std::shared_ptr const& keep_dso_loaded) : impl{std::make_unique(raw_device, proxy, keep_dso_loaded)} { } void wlcs::Pointer::move_to(int x, int y) { impl->move_to(x, y); } void wlcs::Pointer::move_by(int dx, int dy) { impl->move_by(dx, dy); } void wlcs::Pointer::button_down(int button) { impl->button_down(button); } void wlcs::Pointer::button_up(int button) { impl->button_up(button); } void wlcs::Pointer::click(int button) { button_down(button); button_up(button); } void wlcs::Pointer::left_button_down() { button_down(BTN_LEFT); } void wlcs::Pointer::left_button_up() { button_up(BTN_LEFT); } void wlcs::Pointer::left_click() { click(BTN_LEFT); } class wlcs::Touch::Impl { public: template Impl( WlcsTouch* raw_device, std::shared_ptr const& proxy, std::shared_ptr const& keep_dso_loaded) : keep_dso_loaded{keep_dso_loaded}, touch{raw_device, proxy->register_op([](WlcsTouch* raw_device) { raw_device->destroy(raw_device); })} { if (touch->version != WLCS_TOUCH_VERSION) { BOOST_THROW_EXCEPTION(( std::runtime_error{ std::string{"Unexpected WlcsTouch version. Expected: "} + std::to_string(WLCS_TOUCH_VERSION) + " received: " + std::to_string(touch->version)})); } set_up_thunks(proxy); } void down_at(int x, int y) { touch->touch_down(touch.get(), x, y); } void move_to(int x, int y) { touch->touch_move(touch.get(), x, y); } void up() { touch->touch_up(touch.get()); } private: template void set_up_thunks(std::shared_ptr const& proxy) { touch_down_thunk = proxy->register_op( [this](int x, int y) { touch->touch_down(touch.get(), x, y); }); touch_move_thunk = proxy->register_op( [this](int x, int y) { touch->touch_move(touch.get(), x, y); }); touch_up_thunk = proxy->register_op( [this]() { touch->touch_up(touch.get()); }); } std::shared_ptr const keep_dso_loaded; std::unique_ptr> const touch; std::function touch_down_thunk; std::function touch_move_thunk; std::function touch_up_thunk; }; wlcs::Touch::~Touch() = default; wlcs::Touch::Touch(Touch&&) = default; template wlcs::Touch::Touch( WlcsTouch* raw_device, std::shared_ptr const& proxy, std::shared_ptr const& keep_dso_loaded) : impl{std::make_unique(raw_device, proxy, keep_dso_loaded)} { } void wlcs::Touch::down_at(int x, int y) { impl->down_at(x, y); } void wlcs::Touch::move_to(int x, int y) { impl->move_to(x, y); } void wlcs::Touch::up() { impl->up(); } namespace { std::shared_ptr const> extract_supported_extensions(WlcsDisplayServer* server) { if (server->version < 2) { return {}; } auto const descriptor = server->get_descriptor(server); auto extensions = std::make_shared>(); for (auto i = 0u; i < descriptor->num_extensions; ++i) { (*extensions)[descriptor->supported_extensions[i].name] = descriptor->supported_extensions[i].version; } return extensions; } class NullProxy { public: template auto register_op(Callable handler) { return handler; } }; } class wlcs::Server::Impl { public: Impl(std::shared_ptr const& hooks, int argc, char const** argv) : server{hooks->create_server(argc, argv), hooks->destroy_server}, thread_context{make_context_if_needed(*server)}, hooks{hooks}, supported_extensions_{extract_supported_extensions(server.get())} { if (hooks->version < 1) { BOOST_THROW_EXCEPTION((std::runtime_error{"Server integration too old"})); } if (server->version < 1) { BOOST_THROW_EXCEPTION((std::runtime_error{"Server integration too old"})); } if (!server->stop) { BOOST_THROW_EXCEPTION((std::logic_error{"Missing required WlcsDisplayServer.stop definition"})); } if (thread_context) { initialise_thunks(thread_context->proxy); } else { initialise_thunks(std::make_shared()); } } void start() { if (thread_context) { thread_context->server_thread = std::thread{ [this]() { server->start_on_this_thread(server.get(), thread_context->event_loop.get()); }}; } else { server->start(server.get()); } } void stop() { stop_thunk(); } int create_client_socket() { auto fd = create_client_socket_thunk(); if (fd < 0) { BOOST_THROW_EXCEPTION((std::system_error{ errno, std::system_category(), "Failed to get client socket from server"})); } return fd; } Pointer create_pointer() { if (thread_context) { return Pointer{create_pointer_thunk(), thread_context->proxy, hooks}; } else { return Pointer{create_pointer_thunk(), std::make_shared(), hooks}; } } Touch create_touch() { if (thread_context) { return Touch{create_touch_thunk(), thread_context->proxy, hooks}; } else { return Touch{create_touch_thunk(), std::make_shared(), hooks}; } } void move_surface_to(Surface& surface, int x, int y) { // Ensure the server knows about the IDs we're about to send... surface.owner().roundtrip(); position_window_absolute_thunk(surface.owner(), surface, x, y); } WlcsDisplayServer* wlcs_server() const { return server.get(); } std::shared_ptr> supported_extensions() const { return supported_extensions_; } private: struct ThreadContext { explicit ThreadContext(std::unique_ptr loop) : event_loop{std::move(loop)}, proxy{std::make_shared(event_loop.get())} { } ThreadContext(ThreadContext&&) = default; ~ThreadContext() { if (server_thread.joinable()) { server_thread.join(); } } std::unique_ptr event_loop; std::thread server_thread; std::shared_ptr proxy; }; static std::optional make_context_if_needed(WlcsDisplayServer const& server) { if (server.version >= 3) { if (!server.start) { if (!server.start_on_this_thread) { BOOST_THROW_EXCEPTION(( std::runtime_error{"Server integration missing both start() and start_on_this_thread()"})); } auto loop = std::unique_ptr{ wl_event_loop_create(), &wl_event_loop_destroy }; if (!loop) { BOOST_THROW_EXCEPTION(( std::runtime_error{"Failed to create eventloop for WLCS events"})); } return {ThreadContext{std::move(loop)}}; } } return {}; }; std::unique_ptr const server; std::optional thread_context; std::shared_ptr const hooks; std::shared_ptr const> const supported_extensions_; template void initialise_thunks(std::shared_ptr proxy) { stop_thunk = proxy->register_op( [this]() { server->stop(server.get()); }); create_client_socket_thunk = proxy->register_op( [this]() { return server->create_client_socket(server.get()); }); create_pointer_thunk = proxy->register_op( [this]() { return server->create_pointer(server.get()); }); create_touch_thunk = proxy->register_op( [this]() { return server->create_touch(server.get()); }); position_window_absolute_thunk = proxy->register_op( [this]( struct wl_display* client, struct wl_surface* surface, int x, int y) { server->position_window_absolute( server.get(), client, surface, x, y); }); } std::function stop_thunk; std::function create_client_socket_thunk; std::function create_pointer_thunk; std::function create_touch_thunk; std::function position_window_absolute_thunk; }; wlcs::Server::Server( std::shared_ptr const& hooks, int argc, char const** argv) : impl{std::make_unique(hooks, argc, argv)} { } wlcs::Server::~Server() = default; void wlcs::Server::start() { impl->start(); } void wlcs::Server::stop() { impl->stop(); } std::shared_ptr> wlcs::Server::supported_extensions() { return impl->supported_extensions(); } int wlcs::Server::create_client_socket() { return impl->create_client_socket(); } void wlcs::Server::move_surface_to(Surface& surface, int x, int y) { impl->move_surface_to(surface, x, y); } wlcs::Pointer wlcs::Server::create_pointer() { return impl->create_pointer(); } wlcs::Touch wlcs::Server::create_touch() { return impl->create_touch(); } wlcs::InProcessServer::InProcessServer() : server{helpers::get_test_hooks(), helpers::get_argc(), helpers::get_argv()} { } void wlcs::InProcessServer::SetUp() { server.start(); } void wlcs::InProcessServer::TearDown() { server.stop(); } wlcs::Server& wlcs::InProcessServer::the_server() { return server; } void throw_wayland_error(wl_display* display) { auto err = wl_display_get_error(display); if (err != EPROTO) { BOOST_THROW_EXCEPTION((std::system_error{ err, std::system_category(), "Error while dispatching Wayland events" })); } else { uint32_t object_id; uint32_t protocol_error; wl_interface const* interface; protocol_error = wl_display_get_protocol_error(display, &interface, &object_id); BOOST_THROW_EXCEPTION((wlcs::ProtocolError{interface, protocol_error})); } } class wlcs::Client::Impl { public: Impl(Server& server) : supported_extensions{server.supported_extensions()} { try { display = wl_display_connect_to_fd(server.create_client_socket()); } catch (ShimNotImplemented const&) { // TODO: Warn about connecting to who-knows-what display = wl_display_connect(NULL); } if (!display) { BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to connect to Wayland socket"})); } registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, this); server_roundtrip(); } ~Impl() { if (shm) wl_shm_destroy(shm); if (shell) wl_shell_destroy(shell); if (compositor) wl_compositor_destroy(compositor); if (subcompositor) wl_subcompositor_destroy(subcompositor); if (registry) wl_registry_destroy(registry); if (seat) wl_seat_destroy(seat); if (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 callback) { destruction_callbacks.push_back(callback); } ShmBuffer const& create_buffer(Client& client, int width, int height) { auto buffer = std::make_shared(client, width, height); run_on_destruction([buffer]() mutable { buffer.reset(); }); return *buffer; } Surface create_wl_shell_surface(Client& client, int width, int height) { Surface surface{client}; wl_shell_surface * shell_surface = wl_shell_get_shell_surface(the_shell(), surface); surface.run_on_destruction([shell_surface]() { wl_shell_surface_destroy(shell_surface); }); wl_shell_surface_set_toplevel(shell_surface); wl_surface_commit(surface); surface.attach_visible_buffer(width, height); return surface; } Surface create_xdg_shell_v6_surface(Client& client, int width, int height) { Surface surface{client}; auto xdg_surface = std::make_shared(client, surface); auto xdg_toplevel = std::make_shared(*xdg_surface); surface.run_on_destruction([xdg_surface, xdg_toplevel]() mutable { xdg_toplevel.reset(); xdg_surface.reset(); }); wl_surface_commit(surface); surface.attach_visible_buffer(width, height); return surface; } Surface create_xdg_shell_stable_surface(Client& client, int width, int height) { Surface surface{client}; auto xdg_surface = std::make_shared(client, surface); auto xdg_toplevel = std::make_shared(*xdg_surface); // Protocol *requires* us to ack the initial configure before attaching a buffer bool initial_configure_received = false; EXPECT_CALL(*xdg_surface, configure(testing::_)) .WillOnce( [&initial_configure_received, xdg_surface = static_cast(*xdg_surface)](uint32_t serial) { xdg_surface_ack_configure(xdg_surface, serial); initial_configure_received = true; }); surface.run_on_destruction([xdg_surface, xdg_toplevel]() mutable { xdg_toplevel.reset(); xdg_surface.reset(); }); wl_surface_commit(surface); client.dispatch_until([&initial_configure_received]() { return initial_configure_received; }); /* Make absolutely sure the functor above, which captures a stack variable by reference * is not going to be invoked outside this function */ testing::Mock::VerifyAndClearExpectations(xdg_surface.get()); /* And we might as well ack any subsequent configures. * Since we don't need to capture anything but the xdg_surface, which has to be * live if we're getting a callback on it, this is safe to escape this frame */ ON_CALL(*xdg_surface, configure(testing::_)) .WillByDefault( [xdg_surface = static_cast(*xdg_surface)](uint32_t serial) { xdg_surface_ack_configure(xdg_surface, serial); }); surface.attach_visible_buffer(width, height); return surface; } Surface create_visible_surface(Client& client, int width, int height) { if (shell) { return create_wl_shell_surface(client, width, height); } else if (xdg_shell_stable) { return create_xdg_shell_stable_surface(client, width, height); } else if (xdg_shell_v6) { return create_xdg_shell_v6_surface(client, width, height); } else { throw std::runtime_error("compositor does not support any known shell protocols"); } } wl_shell* the_shell() const { if (shell) { return shell; } else { if (!supported_extensions || !supported_extensions->count("wl_shell")) { BOOST_THROW_EXCEPTION((ExtensionExpectedlyNotSupported{"wl_shell", AnyVersion})); } else { throw std::runtime_error("Failed to bind to wl_shell"); } } } zxdg_shell_v6* the_xdg_shell_v6() const { if (xdg_shell_v6) { return xdg_shell_v6; } else { if (!supported_extensions || !supported_extensions->count("zxdg_shell_v6")) { BOOST_THROW_EXCEPTION((ExtensionExpectedlyNotSupported{"zxdg_shell_v6", AnyVersion})); } else { throw std::runtime_error("Failed to bind to zxdg_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 (current_pointer_location) { return current_pointer_location->surface; } return nullptr; } wl_surface* touched_window() const { wl_surface* surface = nullptr; for (auto const& touch : current_touches) { if (surface && touch.second.surface != surface) BOOST_THROW_EXCEPTION(std::runtime_error("Multiple surfaces have active touches")); else surface = touch.second.surface; } return surface; } std::pair pointer_position() const { return current_pointer_location.value().coordinates; }; std::pair touch_position() const { 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::optional 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::optional expected_to_be_supported{}; if (supported_extensions) { expected_to_be_supported = supported_extensions->count(to_bind.name) && version.select_version(supported_extensions->at(to_bind.name), to_bind.version); } /* 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, to_bind.version); if (version_to_bind) { auto global_proxy = wl_registry_bind(registry, global->second.id, &to_bind, version_to_bind.value()); if (!global_proxy) { throw_wayland_error(display); } return global_proxy; } else if (!expected_to_be_supported.value_or(true)) { // We didn't expect to find the needed version of this extension anyway… BOOST_THROW_EXCEPTION((ExtensionExpectedlyNotSupported{to_bind.name, version})); } BOOST_THROW_EXCEPTION((std::runtime_error{ "Failed to bind to " + std::string{to_bind.name} + " version " + version.describe()})); } else if (!expected_to_be_supported.value_or(true)) { // We didn't expect to find this extension anyway… BOOST_THROW_EXCEPTION((ExtensionExpectedlyNotSupported{to_bind.name, version})); } BOOST_THROW_EXCEPTION((std::runtime_error{ "Failed to bind to " + std::string{to_bind.name} + " version " + version.describe()})); } void dispatch_until( std::function const& predicate, std::chrono::seconds timeout) { using namespace std::chrono; auto const end_time = steady_clock::now() + timeout; while (!predicate()) { while (wl_display_prepare_read(display) != 0) { if (wl_display_dispatch_pending(display) < 0) throw_wayland_error(display); } wl_display_flush(display); auto const time_left = end_time - steady_clock::now(); if (time_left.count() < 0) { BOOST_THROW_EXCEPTION((Timeout{"Timeout waiting for condition"})); } /* * TODO: We really want std::chrono::duration::ceil() here, but that's C++17 */ /* * We want to wait *at least* as long as time_left. duration_cast * will perform integer division, so any fractional milliseconds will get dropped. * * Adding 1ms will ensure we wait until *after* we're meant to timeout. */ auto const maximum_wait_ms = duration_cast(time_left) + 1ms; pollfd fd{ wl_display_get_fd(display), POLLIN | POLLERR, 0 }; auto const poll_result = poll(&fd, 1, maximum_wait_ms.count()); if (poll_result < 0) { wl_display_cancel_read(display); BOOST_THROW_EXCEPTION((std::system_error{ errno, std::system_category(), "Failed to wait for Wayland event"})); } if (poll_result == 0) { wl_display_cancel_read(display); BOOST_THROW_EXCEPTION((Timeout{"Timeout waiting for condition"})); } if (wl_display_read_events(display) < 0) { throw_wayland_error(display); } if (wl_display_dispatch_pending(display) < 0) { throw_wayland_error(display); } } } void server_roundtrip() { if (wl_display_roundtrip(display) < 0) { throw_wayland_error(display); } } void client_flush() { if (wl_display_flush(display) == -1) { /* flush will return a (non-fatal) EAGAIN if the send buffer is * full. * * We don't particularly want to care about the EAGAIN case * at the moment, so just ignore it. */ if (errno != EAGAIN) { throw_wayland_error(display); } } } struct Output { OutputState current; OutputState pending; std::vector> done_notifiers; Output(struct wl_output* output) : current{output}, pending{output} { } static void geometry_thunk( void* ctx, struct wl_output */*wl_output*/, int32_t x, int32_t y, int32_t /*physical_width*/, int32_t /*physical_height*/, int32_t /*subpixel*/, const char */*make*/, const char */*model*/, int32_t /*transform*/) { auto me = static_cast(ctx); me->pending.geometry_position = std::make_pair(x, y); } static void mode_thunk( void* ctx, struct wl_output */*wl_output*/, uint32_t /*flags*/, int32_t width, int32_t height, int32_t /*refresh*/) { auto me = static_cast(ctx); me->pending.mode_size = std::make_pair(width, height); } static void done_thunk(void* ctx, struct wl_output */*wl_output*/) { auto me = static_cast(ctx); if (me->pending.geometry_position) me->current.geometry_position = me->pending.geometry_position; if (me->pending.mode_size) me->current.mode_size = me->pending.mode_size; if (me->pending.scale) me->current.scale = me->pending.scale; me->pending = OutputState{me->current.output}; for (auto const& notifier : me->done_notifiers) notifier(); } static void scale_thunk(void* ctx, struct wl_output */*wl_output*/, int32_t factor) { auto me = static_cast(ctx); me->pending.scale = factor; } static constexpr wl_output_listener listener = { &Impl::Output::geometry_thunk, &Impl::Output::mode_thunk, &Impl::Output::done_thunk, &Impl::Output::scale_thunk, }; }; std::vector> outputs; private: static void 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(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(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(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(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(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::nullopt; me->pending_pointer_leave = true; } static void pointer_motion( void* ctx, wl_pointer* /*pointer*/, uint32_t /*time*/, wl_fixed_t x, wl_fixed_t y) { auto me = static_cast(ctx); if (!me->current_pointer_location && !me->pending_pointer_location) FAIL() << "Got wl_pointer.motion when the pointer was not on a surface"; if (!me->pending_pointer_location) me->pending_pointer_location = me->current_pointer_location; me->pending_pointer_location.value().coordinates = std::make_pair(x, y); } static void pointer_button( void *ctx, wl_pointer* /*wl_pointer*/, uint32_t serial, uint32_t /*time*/, uint32_t button, uint32_t state) { auto me = static_cast(ctx); me->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(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::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::nullopt; if (!old_pointer_location) { me->notify_of_pointer_enter( me->current_pointer_location.value().surface, me->current_pointer_location.value().coordinates); } else { me->notify_of_pointer_motion(me->current_pointer_location.value().coordinates); } } if (!me->pending_buttons.empty()) { me->notify_of_pointer_buttons(me->pending_buttons); me->pending_buttons.clear(); } } void notify_of_pointer_enter(wl_surface* surface, std::pair position) { std::vector to_remove; for (auto notifier = enter_notifiers.begin(); notifier != enter_notifiers.end(); ++notifier) { if (!(*notifier)(surface, position.first, position.second)) { to_remove.push_back(notifier); } } for (auto removed : to_remove) { enter_notifiers.erase(removed); } } void notify_of_pointer_leave(wl_surface* surface) { std::vector to_remove; for (auto notifier = leave_notifiers.begin(); notifier != leave_notifiers.end(); ++notifier) { if (!(*notifier)(surface)) { to_remove.push_back(notifier); } } for (auto removed : to_remove) { leave_notifiers.erase(removed); } } void notify_of_pointer_motion(std::pair position) { std::vector to_remove; for (auto notifier = motion_notifiers.begin(); notifier != motion_notifiers.end(); ++notifier) { if (!(*notifier)(position.first, position.second)) { to_remove.push_back(notifier); } } for (auto removed : to_remove) { motion_notifiers.erase(removed); } } void notify_of_pointer_buttons(std::map> const& buttons) { for (auto const& button : buttons) { std::vector to_remove; for (auto notifier = button_notifiers.begin(); notifier != button_notifiers.end(); ++notifier) { if (!(*notifier)(button.second.first, button.first, button.second.second)) { to_remove.push_back(notifier); } } for (auto removed : to_remove) { button_notifiers.erase(removed); } } } static constexpr wl_pointer_listener pointer_listener = { &Impl::pointer_enter, &Impl::pointer_leave, &Impl::pointer_motion, &Impl::pointer_button, [](auto...){}, // axis &Impl::pointer_frame, // frame [](auto...){}, // axis_source [](auto...){}, // axis_stop [](auto...){}, // axis_discrete }; static void touch_down( void* ctx, wl_touch* /*wl_touch*/, uint32_t serial, uint32_t /*time*/, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { auto me = static_cast(ctx); 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(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(ctx); auto touch = me->current_touches.find(id); if (touch == me->current_touches.end()) FAIL() << "Got wl_touch.up with unknown ID " << id; me->pending_touches[id] = SurfaceLocation { touch->second.surface, std::make_pair(x,y) }; } static void touch_frame(void* ctx, wl_touch* /*touch*/) { auto me = static_cast(ctx); for (auto const& id : me->pending_up_touches) { me->current_touches.erase(id); } for (auto const& touch : me->pending_touches) { me->current_touches[touch.first] = touch.second; } me->pending_up_touches.clear(); me->pending_touches.clear(); } static constexpr wl_touch_listener touch_listener = { &Impl::touch_down, &Impl::touch_up, &Impl::touch_motion, &Impl::touch_frame, nullptr, // cancel nullptr, // shape nullptr, // orientation }; static void seat_capabilities( void* ctx, struct wl_seat* seat, uint32_t capabilities) { auto me = static_cast(ctx); if (capabilities & WL_SEAT_CAPABILITY_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; static auto const safe_bind = [] (wl_registry* registry, uint32_t name, const wl_interface* iface, uint32_t version) { return wl_registry_bind(registry, name, iface, std::min(version, static_cast(iface->version))); }; auto me = static_cast(ctx); me->global_type_names[id] = interface; me->globals[interface] = Global{id, version}; if ("wl_shm"s == interface) { me->shm = static_cast(safe_bind(registry, id, &wl_shm_interface, version)); } else if ("wl_compositor"s == interface) { me->compositor = static_cast( safe_bind(registry, id, &wl_compositor_interface, version)); } else if ("wl_subcompositor"s == interface) { me->subcompositor = static_cast( safe_bind(registry, id, &wl_subcompositor_interface, version)); } else if ("wl_shell"s == interface) { me->shell = static_cast(safe_bind(registry, id, &wl_shell_interface, version)); } else if ("wl_seat"s == interface) { me->seat = static_cast(safe_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(safe_bind(registry, id, &wl_output_interface, version)); auto output = std::make_unique(wl_output); wl_output_add_listener(wl_output, &Output::listener, output.get()); me->outputs.push_back(std::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(safe_bind(registry, id, &zxdg_shell_v6_interface, version)); } else if ("xdg_wm_base"s == interface) { me->xdg_shell_stable = static_cast(safe_bind(registry, id, &xdg_wm_base_interface, version)); } } static void global_removed(void* ctx, wl_registry*, uint32_t id) { auto me = static_cast(ctx); auto const name = me->global_type_names.find(id); if (name != me->global_type_names.end()) { me->globals.erase(name->second); me->global_type_names.erase(name); } } constexpr static wl_registry_listener registry_listener = { &global_handler, &global_removed }; std::shared_ptr const> const supported_extensions; struct wl_display* display; struct wl_registry* registry = nullptr; struct wl_compositor* compositor = nullptr; struct wl_subcompositor* subcompositor = nullptr; struct wl_shm* shm = nullptr; struct wl_shell* shell = nullptr; struct wl_seat* seat = nullptr; struct wl_keyboard* keyboard = nullptr; struct wl_pointer* pointer = nullptr; struct wl_touch* touch = nullptr; struct zxdg_shell_v6* xdg_shell_v6 = nullptr; std::vector> destruction_callbacks; struct xdg_wm_base* xdg_shell_stable = nullptr; struct Global { uint32_t id; uint32_t version; }; std::map globals; std::map global_type_names; struct SurfaceLocation { wl_surface* surface; std::pair coordinates; }; wl_surface* keyboard_focused_surface = nullptr; std::optional current_pointer_location; std::optional pending_pointer_location; bool pending_pointer_leave{false}; std::map> pending_buttons; ///< Maps button id to the serial and if it's down std::map current_touches; ///< Touches that have gotten a frame event std::map pending_touches; ///< Touches that have gotten down or motion events without a frame std::set pending_up_touches; ///< Touches that have gotten up events without a frame std::optional latest_serial_; std::vector enter_notifiers; std::vector leave_notifiers; std::vector motion_notifiers; std::vector 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(server)} { } wlcs::Client::~Client() = default; wlcs::Client::operator wl_display*() const { return impl->wl_display(); } wl_compositor* wlcs::Client::compositor() const { return impl->wl_compositor(); } wl_subcompositor* wlcs::Client::subcompositor() const { return impl->wl_subcompositor(); } wl_shm* wlcs::Client::shm() const { return impl->wl_shm(); } wl_seat* wlcs::Client::seat() const { return impl->wl_seat(); } void wlcs::Client::run_on_destruction(std::function callback) { impl->run_on_destruction(callback); } wlcs::ShmBuffer const& wlcs::Client::create_buffer(int width, int height) { return impl->create_buffer(*this, width, height); } wlcs::Surface wlcs::Client::create_wl_shell_surface(int width, int height) { return impl->create_wl_shell_surface(*this, width, height); } wlcs::Surface wlcs::Client::create_xdg_shell_v6_surface(int width, int height) { return impl->create_xdg_shell_v6_surface(*this, width, height); } wlcs::Surface wlcs::Client::create_xdg_shell_stable_surface(int width, int height) { return impl->create_xdg_shell_stable_surface(*this, width, height); } wlcs::Surface wlcs::Client::create_visible_surface(int width, int height) { return impl->create_visible_surface(*this, width, height); } size_t wlcs::Client::output_count() const { return impl->outputs.size(); } wlcs::OutputState wlcs::Client::output_state(size_t index) const { if (index > output_count()) throw std::out_of_range("Invalid output index"); return impl->outputs[index]->current; } void wlcs::Client::add_output_done_notifier(size_t index, std::function const& notifier) { if (index > output_count()) throw std::runtime_error("Invalid output index"); impl->outputs[index]->done_notifiers.push_back(notifier); } wl_shell* wlcs::Client::shell() const { return impl->the_shell(); } zxdg_shell_v6* wlcs::Client::xdg_shell_v6() const { return impl->the_xdg_shell_v6(); } xdg_wm_base* wlcs::Client::xdg_shell_stable() const { return impl->the_xdg_shell_stable(); } wl_surface* wlcs::Client::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 wlcs::Client::pointer_position() const { return impl->pointer_position(); } std::pair wlcs::Client::touch_position() const { return impl->touch_position(); } std::optional 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 const& predicate, std::chrono::seconds timeout) { impl->dispatch_until(predicate, timeout); } void wlcs::Client::roundtrip() { impl->server_roundtrip(); } void wlcs::Client::flush() { impl->client_flush(); } 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*>(wl_callback_get_user_data(pending_callback)); wl_callback_destroy(pending_callback); pending_callbacks.erase(pending_callbacks.begin() + i); } else { ++i; } } for (auto const& callback: destruction_callbacks) callback(); destruction_callbacks.clear(); wl_surface_destroy(surface_); } ::wl_surface* surface() const { return surface_; } void attach_buffer(int width, int height) { auto& buffer = owner_.create_buffer(width, height); wl_surface_attach(surface_, buffer, 0, 0); } void add_frame_callback(std::function const& on_frame) { std::unique_ptr> holder{ new std::function(on_frame)}; auto callback = wl_surface_frame(surface_); pending_callbacks.push_back(std::make_pair(this, callback)); wl_callback_add_listener(callback, &frame_listener, holder.release()); } void attach_visible_buffer(int width, int height) { attach_buffer(width, height); auto surface_rendered = std::make_shared(false); add_frame_callback([surface_rendered](auto) { *surface_rendered = true; }); wl_surface_commit(surface_); owner_.dispatch_until([surface_rendered]() { return *surface_rendered; }); } void run_on_destruction(std::function callback) { destruction_callbacks.push_back(callback); } Client& owner() const { return owner_; } auto current_outputs() -> std::set const& { return outputs; } private: static std::vector> pending_callbacks; std::set 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*>(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(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(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(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(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> destruction_callbacks; }; std::vector> 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(client)} { } wlcs::Surface::~Surface() = default; wlcs::Surface::Surface(Surface&&) = default; wlcs::Surface::operator ::wl_surface*() const { return impl->surface(); } void wlcs::Surface::attach_buffer(int width, int height) { impl->attach_buffer(width, height); } void wlcs::Surface::add_frame_callback(std::function const& on_frame) { impl->add_frame_callback(on_frame); } void wlcs::Surface::attach_visible_buffer(int width, int height) { impl->attach_visible_buffer(width, height); } void wlcs::Surface::run_on_destruction(std::function callback) { impl->run_on_destruction(callback); } wlcs::Client& wlcs::Surface::owner() const { return impl->owner(); } auto wlcs::Surface:: current_outputs() -> std::set 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(surface_ptr); if (subsurface_ptr) surface_ptr = &subsurface_ptr->parent(); else surface_ptr = nullptr; } parent.owner().dispatch_until([&surface_rendered]() { return surface_rendered; }); return subsurface; } wlcs::Subsurface::Subsurface(wlcs::Surface& parent) : Surface{parent.owner()}, impl{std::make_unique(parent.owner(), *this, parent)} { } wlcs::Subsurface::Subsurface(Subsurface &&) = default; wlcs::Subsurface::~Subsurface() = default; wlcs::Subsurface::operator wl_subsurface*() const { return impl->subsurface_; } wlcs::Surface& wlcs::Subsurface::parent() const { return impl->parent_; } class wlcs::ShmBuffer::Impl { public: Impl(Client& client, int width, int height) { auto stride = width * 4; auto size = stride * height; auto fd = wlcs::helpers::create_anonymous_file(size); auto pool = wl_shm_create_pool(client.shm(), fd, size); buffer_ = wl_shm_pool_create_buffer( pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); wl_shm_pool_destroy(pool); close(fd); wl_buffer_add_listener(buffer_, &listener, this); } ~Impl() { wl_buffer_destroy(buffer_); } wl_buffer* buffer() const { return buffer_; } void add_release_listener(std::function const& on_release) { release_notifiers.push_back(on_release); } private: static void on_release(void* ctx, wl_buffer* /*buffer*/) { auto me = static_cast(ctx); std::vectorrelease_notifiers.begin())> expired_notifiers; for (auto notifier = me->release_notifiers.begin(); notifier != me->release_notifiers.end(); ++notifier) { if (!(*notifier)()) { expired_notifiers.push_back(notifier); } } for (auto const& expired : expired_notifiers) me->release_notifiers.erase(expired); } static constexpr wl_buffer_listener listener { &on_release }; wl_buffer* buffer_; std::vector> release_notifiers; }; constexpr wl_buffer_listener wlcs::ShmBuffer::Impl::listener; wlcs::ShmBuffer::ShmBuffer(Client &client, int width, int height) : impl{std::make_unique(client, width, height)} { } wlcs::ShmBuffer::ShmBuffer(ShmBuffer&&) = default; wlcs::ShmBuffer::~ShmBuffer() = default; wlcs::ShmBuffer::operator wl_buffer*() const { return impl->buffer(); } void wlcs::ShmBuffer::add_release_listener(std::function const &on_release) { impl->add_release_listener(on_release); } wlcs::OutputState::OutputState(wl_output* output) : output{output} { } wlcs-1.7.0/src/input_method.cpp000066400000000000000000000075041453657357500165130ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "input_method.h" #include auto wlcs::InputMethod::all_input_methods() -> std::vector> { return { std::make_shared(), std::make_shared()}; } struct wlcs::PointerInputMethod::Pointer : Device { Pointer(wlcs::Server& server) : pointer{server.create_pointer()} { } void down_at(std::pair position) override { ASSERT_THAT(button_down, testing::Eq(false)) << "Called down_at() with pointer already down"; pointer.move_to(position.first, position.second); pointer.left_button_down(); button_down = true; } void move_to(std::pair position) override { ASSERT_THAT(button_down, testing::Eq(true)) << "Called move_to() with pointer up"; pointer.move_to(position.first, position.second); } void up() override { ASSERT_THAT(button_down, testing::Eq(true)) << "Called up() with pointer already up"; pointer.left_button_up(); button_down = false; } wlcs::Pointer pointer; bool button_down = false; }; auto wlcs::PointerInputMethod::create_device(wlcs::Server& server) -> std::unique_ptr { return std::make_unique(server); } auto wlcs::PointerInputMethod::current_surface(wlcs::Client const& client) -> wl_surface* { return client.window_under_cursor(); } auto wlcs::PointerInputMethod::position_on_surface(wlcs::Client const& client) -> std::pair { auto const wl_fixed_position = client.pointer_position(); return { wl_fixed_to_int(wl_fixed_position.first), wl_fixed_to_int(wl_fixed_position.second)}; } struct wlcs::TouchInputMethod::Touch : Device { Touch(wlcs::Server& server) : touch{server.create_touch()} { } void down_at(std::pair position) override { ASSERT_THAT(is_down, testing::Eq(false)) << "Called down_at() with touch already down"; touch.down_at(position.first, position.second); is_down = true; } void move_to(std::pair position) override { ASSERT_THAT(is_down, testing::Eq(true)) << "Called move_to() with touch up"; touch.move_to(position.first, position.second); } void up() override { ASSERT_THAT(is_down, testing::Eq(true)) << "Called up() with touch already up"; touch.up(); is_down = false; } wlcs::Touch touch; bool is_down = false; }; auto wlcs::TouchInputMethod::create_device(wlcs::Server& server) -> std::unique_ptr { return std::make_unique(server); } auto wlcs::TouchInputMethod::current_surface(wlcs::Client const& client) -> wl_surface* { return client.touched_window(); } auto wlcs::TouchInputMethod::position_on_surface(wlcs::Client const& client) -> std::pair { auto const wl_fixed_position = client.touch_position(); return { wl_fixed_to_int(wl_fixed_position.first), wl_fixed_to_int(wl_fixed_position.second)}; } std::ostream& std::operator<<(std::ostream& out, std::shared_ptr const& param) { return out << param->name; } wlcs-1.7.0/src/layer_shell_v1.cpp000066400000000000000000000043631453657357500167250ustar00rootroot00000000000000/* * Copyright © 2018-2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "layer_shell_v1.h" #include "in_process_server.h" #include "version_specifier.h" wlcs::LayerSurfaceV1::LayerSurfaceV1( wlcs::Client& client, wlcs::Surface& surface, zwlr_layer_shell_v1_layer layer, wl_output* output, const char *_namespace) : client{client}, layer_shell{client.bind_if_supported(AnyVersion)}, layer_surface{wrap_wl_object( zwlr_layer_shell_v1_get_layer_surface( layer_shell, surface, output, layer, _namespace))} { static struct zwlr_layer_surface_v1_listener const listener { [](void* data, struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t serial, uint32_t width, uint32_t height) { auto self = static_cast(data); self->last_size_ = {width, 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; }); } wlcs-1.7.0/src/main.cpp000066400000000000000000000061241453657357500147350ustar00rootroot00000000000000/* * Copyright © 2017 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #include #include #include #include #include "xfail_supporting_test_listener.h" #include "shared_library.h" #include "wlcs/display_server.h" #include "helpers.h" int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); if (argc < 2 || !argv[1] || std::string{"--help"} == argv[1]) { std::cerr << "WayLand Conformance Suite test runner" << std::endl << "Usage: " << argv[0] << " COMPOSITOR_INTEGRATION_MODULE [GTEST OPTIONS]... [COMPOSITOR_OPTIONS]..." << std::endl; return 1; } auto const integration_filename = argv[1]; // Shuffle the integration module argument out of argv for (auto i = 1 ; i < (argc - 1) ; ++i) { argv[i] = argv[i + 1]; } wlcs::helpers::set_command_line(argc - 1, const_cast(argv)); std::shared_ptr dso; try { dso = std::make_shared(integration_filename); } catch (std::exception const& err) { std::cerr << "Failed to load compositor integration module " << integration_filename << ": " << err.what() << std::endl; return 1; } std::shared_ptr entry_point; try { entry_point = std::shared_ptr{ dso, dso->load_function("wlcs_server_integration") }; } catch (std::exception const& err) { std::cerr << "Failed to load compositor entry point: " << err.what() << std::endl; return 1; } wlcs::helpers::set_entry_point(entry_point); auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); auto wrapping_listener = new testing::XFailSupportingTestListenerWrapper{ std::unique_ptr<::testing::TestEventListener>{ listeners.Release(listeners.default_result_printer())}}; listeners.Append(wrapping_listener); /* (void)! is apparently the magical incantation required to get GCC to * *actually* silently ignore the return value of a function declared with * __attribute__(("warn_unused_result")) * cf: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 */ (void)!RUN_ALL_TESTS(); if (wrapping_listener->failed()) { return EXIT_FAILURE; } return EXIT_SUCCESS; } wlcs-1.7.0/src/pointer_constraints_unstable_v1.cpp000066400000000000000000000060041453657357500224200ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "pointer_constraints_unstable_v1.h" #include "in_process_server.h" #include wlcs::ZwpPointerConstraintsV1::ZwpPointerConstraintsV1(Client& client) : manager{client.bind_if_supported(AnyVersion)} { } wlcs::ZwpPointerConstraintsV1::~ZwpPointerConstraintsV1() = default; wlcs::ZwpPointerConstraintsV1::operator zwp_pointer_constraints_v1*() const { return manager; } wlcs::ZwpConfinedPointerV1::ZwpConfinedPointerV1( ZwpPointerConstraintsV1& manager, wl_surface* surface, wl_pointer* pointer, wl_region* region, uint32_t lifetime) : relative_pointer{zwp_pointer_constraints_v1_confine_pointer(manager, surface, pointer, region, lifetime)}, version{zwp_confined_pointer_v1_get_version(relative_pointer)} { zwp_confined_pointer_v1_set_user_data(relative_pointer, this); zwp_confined_pointer_v1_add_listener(relative_pointer, &listener, this); } wlcs::ZwpConfinedPointerV1::~ZwpConfinedPointerV1() { zwp_confined_pointer_v1_destroy(relative_pointer); } wlcs::ZwpConfinedPointerV1::operator zwp_confined_pointer_v1*() const { return relative_pointer; } zwp_confined_pointer_v1_listener const wlcs::ZwpConfinedPointerV1::listener = { [](void* self, auto*) { static_cast(self)->confined(); }, [](void* self, auto*) { static_cast(self)->unconfined(); }, }; wlcs::ZwpLockedPointerV1::ZwpLockedPointerV1( ZwpPointerConstraintsV1& manager, wl_surface* surface, wl_pointer* pointer, wl_region* region, uint32_t lifetime) : 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(self)->locked(); }, [](void* self, auto*) { static_cast(self)->unlocked(); }, }; wlcs-1.7.0/src/primary_selection.cpp000066400000000000000000000062121453657357500175370ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "primary_selection.h" #include wlcs::ActiveListeners wlcs::PrimarySelectionDeviceListener::active_listeners; constexpr zwp_primary_selection_device_v1_listener wlcs::PrimarySelectionDeviceListener::thunks; wlcs::ActiveListeners wlcs::PrimarySelectionOfferListener::active_listeners; constexpr zwp_primary_selection_offer_v1_listener wlcs::PrimarySelectionOfferListener::thunks; wlcs::ActiveListeners wlcs::PrimarySelectionSourceListener::active_listeners; constexpr zwp_primary_selection_source_v1_listener wlcs::PrimarySelectionSourceListener::thunks; void wlcs::PrimarySelectionDeviceListener::data_offer( void* data, zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer) { if (active_listeners.includes(data)) static_cast(data)->data_offer(device, offer); } void wlcs::PrimarySelectionDeviceListener::data_offer(zwp_primary_selection_device_v1*, zwp_primary_selection_offer_v1*) { } void wlcs::PrimarySelectionDeviceListener::selection(zwp_primary_selection_device_v1*, zwp_primary_selection_offer_v1*) { } void wlcs::PrimarySelectionDeviceListener::selection( void* data, zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer) { if (active_listeners.includes(data)) static_cast(data)->selection(device, offer); } void wlcs::PrimarySelectionOfferListener::offer(zwp_primary_selection_offer_v1*, const char*) { } void wlcs::PrimarySelectionOfferListener::offer( void* data, zwp_primary_selection_offer_v1* offer, const char* mime_type) { if (active_listeners.includes(data)) static_cast(data)->offer(offer, mime_type); } void wlcs::PrimarySelectionSourceListener::send(zwp_primary_selection_source_v1*, const char*, int32_t fd) { close(fd); } void wlcs::PrimarySelectionSourceListener::cancelled(zwp_primary_selection_source_v1*) { } void wlcs::PrimarySelectionSourceListener::send( void* data, zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd) { if (active_listeners.includes(data)) static_cast(data)->send(source, mime_type, fd); } void wlcs::PrimarySelectionSourceListener::cancelled(void* data, zwp_primary_selection_source_v1* source) { if (active_listeners.includes(data)) static_cast(data)->cancelled(source); } wlcs-1.7.0/src/protocol/000077500000000000000000000000001453657357500151435ustar00rootroot00000000000000wlcs-1.7.0/src/protocol/gtk-primary-selection.xml000066400000000000000000000263151453657357500221250ustar00rootroot00000000000000 Copyright © 2015, 2016 Red Hat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol provides the ability to have a primary selection device to match that of the X server. This primary selection is a shortcut to the common clipboard selection, where text just needs to be selected in order to allow copying it elsewhere. The de facto way to perform this action is the middle mouse button, although it is not limited to this one. Clients wishing to honor primary selection should create a primary selection source and set it as the selection through wp_primary_selection_device.set_selection whenever the text selection changes. In order to minimize calls in pointer-driven text selection, it should happen only once after the operation finished. Similarly, a NULL source should be set when text is unselected. wp_primary_selection_offer objects are first announced through the wp_primary_selection_device.data_offer event. Immediately after this event, the primary data offer will emit wp_primary_selection_offer.offer events to let know of the mime types being offered. When the primary selection changes, the client with the keyboard focus will receive wp_primary_selection_device.selection events. Only the client with the keyboard focus will receive such events with a non-NULL wp_primary_selection_offer. Across keyboard focus changes, previously focused clients will receive wp_primary_selection_device.events with a NULL wp_primary_selection_offer. In order to request the primary selection data, the client must pass a recent serial pertaining to the press event that is triggering the operation, if the compositor deems the serial valid and recent, the wp_primary_selection_source.send event will happen in the other end to let the transfer begin. The client owning the primary selection should write the requested data, and close the file descriptor immediately. If the primary selection owner client disappeared during the transfer, the client reading the data will receive a wp_primary_selection_device.selection event with a NULL wp_primary_selection_offer, the client should take this as a hint to finish the reads related to the no longer existing offer. The primary selection owner should be checking for errors during writes, merely cancelling the ongoing transfer if any happened. The primary selection device manager is a singleton global object that provides access to the primary selection. It allows to create wp_primary_selection_source objects, as well as retrieving the per-seat wp_primary_selection_device objects. Create a new primary selection source. Create a new data device for a given seat. Destroy the primary selection device manager. Replaces the current selection. The previous owner of the primary selection will receive a wp_primary_selection_source.cancelled event. To unset the selection, set the source to NULL. Introduces a new wp_primary_selection_offer object that may be used to receive the current primary selection. Immediately following this event, the new wp_primary_selection_offer object will send wp_primary_selection_offer.offer events to describe the offered mime types. The wp_primary_selection_device.selection event is sent to notify the client of a new primary selection. This event is sent after the wp_primary_selection.data_offer event introducing this object, and after the offer has announced its mimetypes through wp_primary_selection_offer.offer. The data_offer is valid until a new offer or NULL is received or until the client loses keyboard focus. The client must destroy the previous selection data_offer, if any, upon receiving this event. Destroy the primary selection device. A wp_primary_selection_offer represents an offer to transfer the contents of the primary selection clipboard to the client. Similar to wl_data_offer, the offer also describes the mime types that the source will transferthat the data can be converted to and provides the mechanisms for transferring the data directly to the client. To transfer the contents of the primary selection clipboard, the client issues this request and indicates the mime type that it wants to receive. The transfer happens through the passed file descriptor (typically created with the pipe system call). The source client writes the data in the mime type representation requested and then closes the file descriptor. The receiving client reads from the read end of the pipe until EOF and closes its end, at which point the transfer is complete. Destroy the primary selection offer. Sent immediately after creating announcing the wp_primary_selection_offer through wp_primary_selection_device.data_offer. One event is sent per offered mime type. The source side of a wp_primary_selection_offer, it provides a way to describe the offered data and respond to requests to transfer the requested contents of the primary selection clipboard. This request adds a mime type to the set of mime types advertised to targets. Can be called several times to offer multiple types. Destroy the primary selection source. Request for the current primary selection contents from the client. Send the specified mime type over the passed file descriptor, then close it. This primary selection source is no longer valid. The client should clean up and destroy this primary selection source. wlcs-1.7.0/src/protocol/input-method-unstable-v1.xml000066400000000000000000000314271453657357500224500ustar00rootroot00000000000000 Copyright © 2012, 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. Corresponds to a text input on the input method side. An input method context is created on text input activation on the input method side. It allows receiving information about the text input from the application via events. Input method contexts do not keep state after deactivation and should be destroyed after deactivation is handled. Text is generally UTF-8 encoded, indices and lengths are in bytes. Serials are used to synchronize the state between the text input and an input method. New serials are sent by the text input in the commit_state request and are used by the input method to indicate the known text input state in events like preedit_string, commit_string, and keysym. The text input can then ignore events from the input method which are based on an outdated state (for example after a reset). 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. Send the commit string text for insertion to the application. The text to commit could be either just a single character after a key press or the result of some composing (pre-edit). It could be also an empty text when some text should be removed (see delete_surrounding_text) or when the input cursor should be moved (see cursor_position). Any previously set composing text will be removed. Send the pre-edit string text to the application text input. The commit text can be used to replace the pre-edit text on reset (for example on unfocus). Previously sent preedit_style and preedit_cursor requests are also processed by the text_input. Set the styling information on composing text. The style is applied for length in bytes from index relative to the beginning of the composing text (as byte offset). Multiple styles can be applied to a composing text. This request should be sent before sending a preedit_string request. Set the cursor position inside the composing text (as byte offset) relative to the start of the composing text. When index is negative no cursor should be displayed. This request should be sent before sending a preedit_string request. Remove the surrounding text. This request will be handled on the text_input side directly following a commit_string request. Set the cursor and anchor to a new position. Index is the new cursor position in bytes (when >= 0 this is relative to the end of the inserted text, otherwise it is relative to the beginning of the inserted text). Anchor is the new anchor position in bytes (when >= 0 this is relative to the end of the inserted text, otherwise it is relative to the beginning of the inserted text). When there should be no selected text, anchor should be the same as index. This request will be handled on the text_input side directly following a commit_string request. Notify when a key event was sent. Key events should not be used for normal text input operations, which should be done with commit_string, delete_surrounding_text, etc. The key event follows the wl_keyboard key event convention. Sym is an XKB keysym, state is a wl_keyboard key_state. Allow an input method to receive hardware keyboard input and process key events to generate text events (with pre-edit) over the wire. This allows input methods which compose multiple key events for inputting text like it is done for CJK languages. Forward a wl_keyboard::key event to the client that was not processed by the input method itself. Should be used when filtering key events with grab_keyboard. The arguments should be the ones from the wl_keyboard::key event. For generating custom key events use the keysym request instead. Forward a wl_keyboard::modifiers event to the client that was not processed by the input method itself. Should be used when filtering key events with grab_keyboard. The arguments should be the ones from the wl_keyboard::modifiers event. The plain surrounding text around the input position. Cursor is the position in bytes within the surrounding text relative to the beginning of the text. Anchor is the position in bytes of the selection anchor within the surrounding text relative to the beginning of the text. If there is no selected text then anchor is the same as cursor. An input method object is responsible for composing text in response to input from hardware or virtual keyboards. There is one input method object per seat. On activate there is a new input method context object created which allows the input method to communicate with the text input. A text input was activated. Creates an input method context object which allows communication with the text input. The text input corresponding to the context argument was deactivated. The input method context should be destroyed after deactivation is handled. Only one client can bind this interface at a time. Set the input_panel_surface type to keyboard. A keyboard surface is only shown when a text input is active. Set the input_panel_surface to be an overlay panel. This is shown near the input cursor above the application window when a text input is active. wlcs-1.7.0/src/protocol/input-method-unstable-v2.xml000066400000000000000000000513731453657357500224530ustar00rootroot00000000000000 Copyright © 2008-2011 Kristian Høgsberg Copyright © 2010-2011 Intel Corporation Copyright © 2012-2013 Collabora, Ltd. Copyright © 2012, 2013 Intel Corporation Copyright © 2015, 2016 Jan Arne Petersen Copyright © 2017, 2018 Red Hat, Inc. Copyright © 2018 Purism SPC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows applications to act as input methods for compositors. An input method context is used to manage the state of the input method. Text strings are UTF-8 encoded, their indices and lengths are in bytes. This document adheres to the RFC 2119 when using words like "must", "should", "may", etc. 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. An input method object allows for clients to compose text. The objects connects the client to a text input in an application, and lets the client to serve as an input method for a seat. The zwp_input_method_v2 object can occupy two distinct states: active and inactive. In the active state, the object is associated to and communicates with a text input. In the inactive state, there is no associated text input, and the only communication is with the compositor. Initially, the input method is in the inactive state. Requests issued in the inactive state must be accepted by the compositor. Because of the serial mechanism, and the state reset on activate event, they will not have any effect on the state of the next text input. There must be no more than one input method object per seat. Notification that a text input focused on this seat requested the input method to be activated. This event serves the purpose of providing the compositor with an active input method. This event resets all state associated with previous enable, disable, surrounding_text, text_change_cause, and content_type events, as well as the state associated with set_preedit_string, commit_string, and delete_surrounding_text requests. In addition, it marks the zwp_input_method_v2 object as active, and makes any existing zwp_input_popup_surface_v2 objects visible. The surrounding_text, and content_type events must follow before the next done event if the text input supports the respective functionality. State set with this event is double-buffered. It will get applied on the next zwp_input_method_v2.done event, and stay valid until changed. Notification that no focused text input currently needs an active input method on this seat. This event marks the zwp_input_method_v2 object as inactive. The compositor must make all existing zwp_input_popup_surface_v2 objects invisible until the next activate event. State set with this event is double-buffered. It will get applied on the next zwp_input_method_v2.done event, and stay valid until changed. Updates the surrounding plain text around the cursor, excluding the preedit text. If any preedit text is present, it is replaced with the cursor for the purpose of this event. The argument text is a buffer containing the preedit string, and must include the cursor position, and the complete selection. It should contain additional characters before and after these. There is a maximum length of wayland messages, so text can not be longer than 4000 bytes. cursor is the byte offset of the cursor within the text buffer. anchor is the byte offset of the selection anchor within the text buffer. If there is no selected text, anchor must be the same as cursor. If this event does not arrive before the first done event, the input method may assume that the text input does not support this functionality and ignore following surrounding_text events. Values set with this event are double-buffered. They will get applied and set to initial values on the next zwp_input_method_v2.done event. The initial state for affected fields is empty, meaning that the text input does not support sending surrounding text. If the empty values get applied, subsequent attempts to change them may have no effect. Tells the input method why the text surrounding the cursor changed. Whenever the client detects an external change in text, cursor, or anchor position, it must issue this request to the compositor. This request is intended to give the input method a chance to update the preedit text in an appropriate way, e.g. by removing it when the user starts typing with a keyboard. cause describes the source of the change. The value set with this event is double-buffered. It will get applied and set to its initial value on the next zwp_input_method_v2.done event. The initial value of cause is input_method. Indicates the content type and hint for the current zwp_input_method_v2 instance. Values set with this event are double-buffered. They will get applied on the next zwp_input_method_v2.done event. The initial value for hint is none, and the initial value for purpose is normal. Atomically applies state changes recently sent to the client. The done event establishes and updates the state of the client, and must be issued after any changes to apply them. Text input state (content purpose, content hint, surrounding text, and change cause) is conceptually double-buffered within an input method context. Events modify the pending state, as opposed to the current state in use by the input method. A done event atomically applies all pending state, replacing the current state. After done, the new pending state is as documented for each related request. Events must be applied in the order of arrival. Neither current nor pending state are modified unless noted otherwise. Send the commit string text for insertion to the application. Inserts a string at current cursor position (see commit event sequence). The string to commit could be either just a single character after a key press or the result of some composing. The argument text is a buffer containing the string to insert. There is a maximum length of wayland messages, so text can not be longer than 4000 bytes. Values set with this event are double-buffered. They must be applied and reset to initial on the next zwp_text_input_v3.commit request. The initial value of text is an empty string. Send the pre-edit string text to the application text input. Place a new composing text (pre-edit) at the current cursor position. Any previously set composing text must be removed. Any previously existing selected text must be removed. The cursor is moved to a new position within the preedit string. The argument text is a buffer containing the preedit string. There is a maximum length of wayland messages, so text can not be longer than 4000 bytes. The arguments cursor_begin and cursor_end are counted in bytes relative to the beginning of the submitted string buffer. Cursor should be hidden by the text input when both are equal to -1. cursor_begin indicates the beginning of the cursor. cursor_end indicates the end of the cursor. It may be equal or different than cursor_begin. Values set with this event are double-buffered. They must be applied on the next zwp_input_method_v2.commit event. The initial value of text is an empty string. The initial value of cursor_begin, and cursor_end are both 0. Remove the surrounding text. before_length and after_length are the number of bytes before and after the current cursor index (excluding the preedit text) to delete. If any preedit text is present, it is replaced with the cursor for the purpose of this event. In effect before_length is counted from the beginning of preedit text, and after_length from its end (see commit event sequence). Values set with this event are double-buffered. They must be applied and reset to initial on the next zwp_input_method_v2.commit request. The initial values of both before_length and after_length are 0. Apply state changes from commit_string, set_preedit_string and delete_surrounding_text requests. The state relating to these events is double-buffered, and each one modifies the pending state. This request replaces the current state with the pending state. The connected text input is expected to proceed by evaluating the changes in the following order: 1. Replace existing preedit string with the cursor. 2. Delete requested surrounding text. 3. Insert commit string with the cursor at its end. 4. Calculate surrounding text to send. 5. Insert new preedit text in cursor position. 6. Place cursor inside preedit text. The serial number reflects the last state of the zwp_input_method_v2 object known to the client. The value of the serial argument must be equal to the number of done events already issued by that object. When the compositor receives a commit request with a serial different than the number of past done events, it must proceed as normal, except it should not change the current state of the zwp_input_method_v2 object. Creates a new zwp_input_popup_surface_v2 object wrapping a given surface. The surface gets assigned the "input_popup" role. If the surface already has an assigned role, the compositor must issue a protocol error. Allow an input method to receive hardware keyboard input and process key events to generate text events (with pre-edit) over the wire. This allows input methods which compose multiple key events for inputting text like it is done for CJK languages. The compositor should send all keyboard events on the seat to the grab holder via the returned wl_keyboard object. Nevertheless, the compositor may decide not to forward any particular event. The compositor must not further process any event after it has been forwarded to the grab holder. Releasing the resulting wl_keyboard object releases the grab. The input method ceased to be available. The compositor must issue this event as the only event on the object if there was another input_method object associated with the same seat at the time of its creation. The compositor must issue this request when the object is no longer useable, e.g. due to seat removal. The input method context becomes inert and should be destroyed after deactivation is handled. Any further requests and events except for the destroy request must be ignored. Destroys the zwp_text_input_v2 object and any associated child objects, i.e. zwp_input_popup_surface_v2 and zwp_input_method_keyboard_grab_v2. This interface marks a surface as a popup for interacting with an input method. The compositor should place it near the active text input area. It must be visible if and only if the input method is in the active state. The client must not destroy the underlying wl_surface while the zwp_input_popup_surface_v2 object exists. Notify about the position of the area of the text input expressed as a rectangle in surface local coordinates. This is a hint to the input method telling it the relative position of the text being entered. The zwp_input_method_keyboard_grab_v2 interface represents an exclusive grab of the wl_keyboard interface associated with the seat. This event provides a file descriptor to the client which can be memory-mapped to provide a keyboard mapping description. A key was pressed or released. The time argument is a timestamp with millisecond granularity, with an undefined base. Notifies clients that the modifier and/or group state has changed, and it should update its local state. Informs the client about the keyboard's repeat rate and delay. This event is sent as soon as the zwp_input_method_keyboard_grab_v2 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 zwp_input_method_keyboard_grab_v2. The input method manager allows the client to become the input method on a chosen seat. No more than one input method must be associated with any seat at any given time. Request a new input zwp_input_method_v2 object associated with a given seat. Destroys the zwp_input_method_manager_v2 object. The zwp_input_method_v2 objects originating from it remain valid. wlcs-1.7.0/src/protocol/pointer-constraints-unstable-v1.xml000066400000000000000000000360731453657357500240620ustar00rootroot00000000000000 Copyright © 2014 Jonas Ådahl Copyright © 2015 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol specifies a set of interfaces used for adding constraints to the motion of a pointer. Possible constraints include confining pointer motions to a given region, or locking it to its current position. In order to constrain the pointer, a client must first bind the global interface "wp_pointer_constraints" which, if a compositor supports pointer constraints, is exposed by the registry. Using the bound global object, the client uses the request that corresponds to the type of constraint it wants to make. See wp_pointer_constraints for more details. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. The global interface exposing pointer constraining functionality. It exposes two requests: lock_pointer for locking the pointer to its position, and confine_pointer for locking the pointer to a region. The lock_pointer and confine_pointer requests create the objects wp_locked_pointer and wp_confined_pointer respectively, and the client can use these objects to interact with the lock. For any surface, only one lock or confinement may be active across all wl_pointer objects of the same seat. If a lock or confinement is requested when another lock or confinement is active or requested on the same surface and with any of the wl_pointer objects of the same seat, an 'already_constrained' error will be raised. These errors can be emitted in response to wp_pointer_constraints requests. These values represent different lifetime semantics. They are passed as arguments to the factory requests to specify how the constraint lifetimes should be managed. A oneshot pointer constraint will never reactivate once it has been deactivated. See the corresponding deactivation event (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for details. A persistent pointer constraint may again reactivate once it has been deactivated. See the corresponding deactivation event (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for details. Used by the client to notify the server that it will no longer use this pointer constraints object. The lock_pointer request lets the client request to disable movements of the virtual pointer (i.e. the cursor), effectively locking the pointer to a position. This request may not take effect immediately; in the future, when the compositor deems implementation-specific constraints are satisfied, the pointer lock will be activated and the compositor sends a locked event. The protocol provides no guarantee that the constraints are ever satisfied, and does not require the compositor to send an error if the constraints cannot ever be satisfied. It is thus possible to request a lock that will never activate. There may not be another pointer constraint of any kind requested or active on the surface for any of the wl_pointer objects of the seat of the passed pointer when requesting a lock. If there is, an error will be raised. See general pointer lock documentation for more details. The intersection of the region passed with this request and the input region of the surface is used to determine where the pointer must be in order for the lock to activate. It is up to the compositor whether to warp the pointer or require some kind of user interaction for the lock to activate. If the region is null the surface input region is used. A surface may receive pointer focus without the lock being activated. The request creates a new object wp_locked_pointer which is used to interact with the lock as well as receive updates about its state. See the the description of wp_locked_pointer for further information. Note that while a pointer is locked, the wl_pointer objects of the corresponding seat will not emit any wl_pointer.motion events, but relative motion events will still be emitted via wp_relative_pointer objects of the same seat. wl_pointer.axis and wl_pointer.button events are unaffected. The confine_pointer request lets the client request to confine the pointer cursor to a given region. This request may not take effect immediately; in the future, when the compositor deems implementation- specific constraints are satisfied, the pointer confinement will be activated and the compositor sends a confined event. The intersection of the region passed with this request and the input region of the surface is used to determine where the pointer must be in order for the confinement to activate. It is up to the compositor whether to warp the pointer or require some kind of user interaction for the confinement to activate. If the region is null the surface input region is used. The request will create a new object wp_confined_pointer which is used to interact with the confinement as well as receive updates about its state. See the the description of wp_confined_pointer for further information. The wp_locked_pointer interface represents a locked pointer state. While the lock of this object is active, the wl_pointer objects of the associated seat will not emit any wl_pointer.motion events. This object will send the event 'locked' when the lock is activated. Whenever the lock is activated, it is guaranteed that the locked surface will already have received pointer focus and that the pointer will be within the region passed to the request creating this object. To unlock the pointer, send the destroy request. This will also destroy the wp_locked_pointer object. If the compositor decides to unlock the pointer the unlocked event is sent. See wp_locked_pointer.unlock for details. When unlocking, the compositor may warp the cursor position to the set cursor position hint. If it does, it will not result in any relative motion events emitted via wp_relative_pointer. If the surface the lock was requested on is destroyed and the lock is not yet activated, the wp_locked_pointer object is now defunct and must be destroyed. Destroy the locked pointer object. If applicable, the compositor will unlock the pointer. Set the cursor position hint relative to the top left corner of the surface. If the client is drawing its own cursor, it should update the position hint to the position of its own cursor. A compositor may use this information to warp the pointer upon unlock in order to avoid pointer jumps. The cursor position hint is double buffered. The new hint will only take effect when the associated surface gets it pending state applied. See wl_surface.commit for details. Set a new region used to lock the pointer. The new lock region is double-buffered. The new lock region will only take effect when the associated surface gets its pending state applied. See wl_surface.commit for details. For details about the lock region, see wp_locked_pointer. Notification that the pointer lock of the seat's pointer is activated. Notification that the pointer lock of the seat's pointer is no longer active. If this is a oneshot pointer lock (see wp_pointer_constraints.lifetime) this object is now defunct and should be destroyed. If this is a persistent pointer lock (see wp_pointer_constraints.lifetime) this pointer lock may again reactivate in the future. The wp_confined_pointer interface represents a confined pointer state. This object will send the event 'confined' when the confinement is activated. Whenever the confinement is activated, it is guaranteed that the surface the pointer is confined to will already have received pointer focus and that the pointer will be within the region passed to the request creating this object. It is up to the compositor to decide whether this requires some user interaction and if the pointer will warp to within the passed region if outside. To unconfine the pointer, send the destroy request. This will also destroy the wp_confined_pointer object. If the compositor decides to unconfine the pointer the unconfined event is sent. The wp_confined_pointer object is at this point defunct and should be destroyed. Destroy the confined pointer object. If applicable, the compositor will unconfine the pointer. Set a new region used to confine the pointer. The new confine region is double-buffered. The new confine region will only take effect when the associated surface gets its pending state applied. See wl_surface.commit for details. If the confinement is active when the new confinement region is applied and the pointer ends up outside of newly applied region, the pointer may warped to a position within the new confinement region. If warped, a wl_pointer.motion event will be emitted, but no wp_relative_pointer.relative_motion event. The compositor may also, instead of using the new region, unconfine the pointer. For details about the confine region, see wp_confined_pointer. Notification that the pointer confinement of the seat's pointer is activated. Notification that the pointer confinement of the seat's pointer is no longer active. If this is a oneshot pointer confinement (see wp_pointer_constraints.lifetime) this object is now defunct and should be destroyed. If this is a persistent pointer confinement (see wp_pointer_constraints.lifetime) this pointer confinement may again reactivate in the future. wlcs-1.7.0/src/protocol/primary-selection-unstable-v1.xml000066400000000000000000000243451453657357500235020ustar00rootroot00000000000000 Copyright © 2015, 2016 Red Hat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol provides the ability to have a primary selection device to match that of the X server. This primary selection is a shortcut to the common clipboard selection, where text just needs to be selected in order to allow copying it elsewhere. The de facto way to perform this action is the middle mouse button, although it is not limited to this one. Clients wishing to honor primary selection should create a primary selection source and set it as the selection through wp_primary_selection_device.set_selection whenever the text selection changes. In order to minimize calls in pointer-driven text selection, it should happen only once after the operation finished. Similarly, a NULL source should be set when text is unselected. wp_primary_selection_offer objects are first announced through the wp_primary_selection_device.data_offer event. Immediately after this event, the primary data offer will emit wp_primary_selection_offer.offer events to let know of the mime types being offered. When the primary selection changes, the client with the keyboard focus will receive wp_primary_selection_device.selection events. Only the client with the keyboard focus will receive such events with a non-NULL wp_primary_selection_offer. Across keyboard focus changes, previously focused clients will receive wp_primary_selection_device.events with a NULL wp_primary_selection_offer. In order to request the primary selection data, the client must pass a recent serial pertaining to the press event that is triggering the operation, if the compositor deems the serial valid and recent, the wp_primary_selection_source.send event will happen in the other end to let the transfer begin. The client owning the primary selection should write the requested data, and close the file descriptor immediately. If the primary selection owner client disappeared during the transfer, the client reading the data will receive a wp_primary_selection_device.selection event with a NULL wp_primary_selection_offer, the client should take this as a hint to finish the reads related to the no longer existing offer. The primary selection owner should be checking for errors during writes, merely cancelling the ongoing transfer if any happened. The primary selection device manager is a singleton global object that provides access to the primary selection. It allows to create wp_primary_selection_source objects, as well as retrieving the per-seat wp_primary_selection_device objects. Create a new primary selection source. Create a new data device for a given seat. Destroy the primary selection device manager. Replaces the current selection. The previous owner of the primary selection will receive a wp_primary_selection_source.cancelled event. To unset the selection, set the source to NULL. Introduces a new wp_primary_selection_offer object that may be used to receive the current primary selection. Immediately following this event, the new wp_primary_selection_offer object will send wp_primary_selection_offer.offer events to describe the offered mime types. The wp_primary_selection_device.selection event is sent to notify the client of a new primary selection. This event is sent after the wp_primary_selection.data_offer event introducing this object, and after the offer has announced its mimetypes through wp_primary_selection_offer.offer. The data_offer is valid until a new offer or NULL is received or until the client loses keyboard focus. The client must destroy the previous selection data_offer, if any, upon receiving this event. Destroy the primary selection device. A wp_primary_selection_offer represents an offer to transfer the contents of the primary selection clipboard to the client. Similar to wl_data_offer, the offer also describes the mime types that the data can be converted to and provides the mechanisms for transferring the data directly to the client. To transfer the contents of the primary selection clipboard, the client issues this request and indicates the mime type that it wants to receive. The transfer happens through the passed file descriptor (typically created with the pipe system call). The source client writes the data in the mime type representation requested and then closes the file descriptor. The receiving client reads from the read end of the pipe until EOF and closes its end, at which point the transfer is complete. Destroy the primary selection offer. Sent immediately after creating announcing the wp_primary_selection_offer through wp_primary_selection_device.data_offer. One event is sent per offered mime type. The source side of a wp_primary_selection_offer, it provides a way to describe the offered data and respond to requests to transfer the requested contents of the primary selection clipboard. This request adds a mime type to the set of mime types advertised to targets. Can be called several times to offer multiple types. Destroy the primary selection source. Request for the current primary selection contents from the client. Send the specified mime type over the passed file descriptor, then close it. This primary selection source is no longer valid. The client should clean up and destroy this primary selection source. wlcs-1.7.0/src/protocol/relative-pointer-unstable-v1.xml000066400000000000000000000146701453657357500233250ustar00rootroot00000000000000 Copyright © 2014 Jonas Ådahl Copyright © 2015 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol specifies a set of interfaces used for making clients able to receive relative pointer events not obstructed by barriers (such as the monitor edge or other pointer barriers). To start receiving relative pointer events, a client must first bind the global interface "wp_relative_pointer_manager" which, if a compositor supports relative pointer motion events, is exposed by the registry. After having created the relative pointer manager proxy object, the client uses it to create the actual relative pointer object using the "get_relative_pointer" request given a wl_pointer. The relative pointer motion events will then, when applicable, be transmitted via the proxy of the newly created relative pointer object. See the documentation of the relative pointer interface for more details. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. A global interface used for getting the relative pointer object for a given pointer. Used by the client to notify the server that it will no longer use this relative pointer manager object. Create a relative pointer interface given a wl_pointer object. See the wp_relative_pointer interface for more details. A wp_relative_pointer object is an extension to the wl_pointer interface used for emitting relative pointer events. It shares the same focus as wl_pointer objects of the same seat and will only emit events when it has focus. Relative x/y pointer motion from the pointer of the seat associated with this object. A relative motion is in the same dimension as regular wl_pointer motion events, except they do not represent an absolute position. For example, moving a pointer from (x, y) to (x', y') would have the equivalent relative motion (x' - x, y' - y). If a pointer motion caused the absolute pointer position to be clipped by for example the edge of the monitor, the relative motion is unaffected by the clipping and will represent the unclipped motion. This event also contains non-accelerated motion deltas. The non-accelerated delta is, when applicable, the regular pointer motion delta as it was before having applied motion acceleration and other transformations such as normalization. Note that the non-accelerated delta does not represent 'raw' events as they were read from some device. Pointer motion acceleration is device- and configuration-specific and non-accelerated deltas and accelerated deltas may have the same value on some devices. Relative motions are not coupled to wl_pointer.motion events, and can be sent in combination with such events, but also independently. There may also be scenarios where wl_pointer.motion is sent, but there is no relative motion. The order of an absolute and relative motion event originating from the same physical motion is not guaranteed. If the client needs button events or focus state, it can receive them from a wl_pointer object of the same seat that the wp_relative_pointer object is associated with. wlcs-1.7.0/src/protocol/text-input-unstable-v2.xml000066400000000000000000000473611453657357500221610ustar00rootroot00000000000000 Copyright © 2012, 2013 Intel Corporation Copyright © 2015, 2016 Jan Arne Petersen Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. The zwp_text_input_v2 interface represents text input and input methods associated with a seat. It provides enter/leave events to follow the text input focus for a seat. Requests are used to enable/disable the text-input object and set state information like surrounding and selected text or the content type. The information about the entered text is sent to the text-input object via the pre-edit and commit events. Using this interface removes the need for applications to directly process hardware key events and compose text out of them. Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices have to always point to the first byte of an UTF-8 encoded code point. Lengths are not allowed to contain just a part of an UTF-8 encoded code point. State is sent by the state requests (set_surrounding_text, set_content_type, set_cursor_rectangle and set_preferred_language) and an update_state request. After an enter or an input_method_change event all state information is invalidated and needs to be resent from the client. A reset or entering a new widget on client side also invalidates all current state information. Destroy the wp_text_input object. Also disables all surfaces enabled through this wp_text_input object Enable text input in a surface (usually when a text entry inside of it has focus). This can be called before or after a surface gets text (or keyboard) focus via the enter event. Text input to a surface is only active when it has the current text (or keyboard) focus and is enabled. Disable text input in a surface (typically when there is no focus on any text entry inside the surface). Requests input panels (virtual keyboard) to show. This should be used for example to show a virtual keyboard again (with a tap) after it was closed by pressing on a close button on the keyboard. Requests input panels (virtual keyboard) to hide. Sets the plain surrounding text around the input position. Text is UTF-8 encoded. Cursor is the byte offset within the surrounding text. Anchor is the byte offset of the selection anchor within the surrounding text. If there is no selected text, anchor is the same as cursor. Make sure to always send some text before and after the cursor except when the cursor is at the beginning or end of text. When there was a configure_surrounding_text event take the before_cursor and after_cursor arguments into account for picking how much surrounding text to send. There is a maximum length of wayland messages so text can not be longer than 4000 bytes. Content hint is a bitmask to allow to modify the behavior of the text input. The content purpose allows to specify the primary purpose of a text input. This allows an input method to show special purpose input panels with extra characters or to disallow some characters. Sets the content purpose and content hint. While the purpose is the basic purpose of an input field, the hint flags allow to modify some of the behavior. When no content type is explicitly set, a normal content purpose with none hint should be assumed. Sets the cursor outline as a x, y, width, height rectangle in surface local coordinates. Allows the compositor to put a window with word suggestions near the cursor. Sets a specific language. This allows for example a virtual keyboard to show a language specific layout. The "language" argument is a RFC-3066 format language tag. It could be used for example in a word processor to indicate language of currently edited document or in an instant message application which tracks languages of contacts. Defines the reason for sending an updated state. Allows to atomically send state updates from client. This request should follow after a batch of state updating requests like set_surrounding_text, set_content_type, set_cursor_rectangle and set_preferred_language. The flags field indicates why an updated state is sent to the input method. Reset should be used by an editor widget after the text was changed outside of the normal input method flow. For "change" it is enough to send the changed state, else the full state should be send. Serial should be set to the serial from the last enter or input_method_changed event. To make sure to not receive outdated input method events after a reset or switching to a new widget wl_display_sync() should be used after update_state in these cases. Notification that this seat's text-input focus is on a certain surface. When the seat has the keyboard capability the text-input focus follows the keyboard focus. Notification that this seat's text-input focus is no longer on a certain surface. The leave notification is sent before the enter notification for the new focus. When the seat has the keyboard capabillity the text-input focus follows the keyboard focus. Notification that the visibility of the input panel (virtual keyboard) changed. The rectangle x, y, width, height defines the area overlapped by the input panel (virtual keyboard) on the surface having the text focus in surface local coordinates. That can be used to make sure widgets are visible and not covered by a virtual keyboard. Notify when a new composing text (pre-edit) should be set around the current cursor position. Any previously set composing text should be removed. The commit text can be used to replace the composing text in some cases (for example when losing focus). The text input should also handle all preedit_style and preedit_cursor events occurring directly before preedit_string. Sets styling information on composing text. The style is applied for length bytes from index relative to the beginning of the composing text (as byte offset). Multiple styles can be applied to a composing text by sending multiple preedit_styling events. This event is handled as part of a following preedit_string event. Sets the cursor position inside the composing text (as byte offset) relative to the start of the composing text. When index is a negative number no cursor is shown. When no preedit_cursor event is sent the cursor will be at the end of the composing text by default. This event is handled as part of a following preedit_string event. Notify when text should be inserted into the editor widget. The text to commit could be either just a single character after a key press or the result of some composing (pre-edit). It could be also an empty text when some text should be removed (see delete_surrounding_text) or when the input cursor should be moved (see cursor_position). Any previously set composing text should be removed. Notify when the cursor or anchor position should be modified. This event should be handled as part of a following commit_string event. The text between anchor and index should be selected. Notify when the text around the current cursor position should be deleted. BeforeLength and afterLength is the length (in bytes) of text before and after the current cursor position (excluding the selection) to delete. This event should be handled as part of a following commit_string or preedit_string event. Transfer an array of 0-terminated modifiers names. The position in the array is the index of the modifier as used in the modifiers bitmask in the keysym event. Notify when a key event was sent. Key events should not be used for normal text input operations, which should be done with commit_string, delete_surrounding_text, etc. The key event follows the wl_keyboard key event convention. Sym is a XKB keysym, state a wl_keyboard key_state. Modifiers are a mask for effective modifiers (where the modifier indices are set by the modifiers_map event) Sets the language of the input text. The "language" argument is a RFC-3066 format language tag. Sets the text direction of input text. It is mainly needed for showing input cursor on correct side of the editor when there is no input yet done and making sure neutral direction text is laid out properly. Configure what amount of surrounding text is expected by the input method. The surrounding text will be sent in the set_surrounding_text request on the following state information updates. The input method changed on compositor side, which invalidates all current state information. New state information should be sent from the client via state requests (set_surrounding_text, set_content_hint, ...) and update_state. A factory for text-input objects. This object is a global singleton. Destroy the wp_text_input_manager object. Creates a new text-input object for a given seat. wlcs-1.7.0/src/protocol/text-input-unstable-v3.xml000066400000000000000000000513101453657357500221470ustar00rootroot00000000000000 Copyright © 2012, 2013 Intel Corporation Copyright © 2015, 2016 Jan Arne Petersen Copyright © 2017, 2018 Red Hat, Inc. Copyright © 2018 Purism SPC 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. This protocol allows compositors to act as input methods and to send text to applications. A text input object is used to manage state of what are typically text entry fields in the application. This document adheres to the RFC 2119 when using words like "must", "should", "may", etc. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. The zwp_text_input_v3 interface represents text input and input methods associated with a seat. It provides enter/leave events to follow the text input focus for a seat. Requests are used to enable/disable the text-input object and set state information like surrounding and selected text or the content type. The information about the entered text is sent to the text-input object via the preedit_string and commit_string events. Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices must not point to middle bytes inside a code point: they must either point to the first byte of a code point or to the end of the buffer. Lengths must be measured between two valid indices. Focus moving throughout surfaces will result in the emission of zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused surface must commit zwp_text_input_v3.enable and zwp_text_input_v3.disable requests as the keyboard focus moves across editable and non-editable elements of the UI. Those two requests are not expected to be paired with each other, the compositor must be able to handle consecutive series of the same request. State is sent by the state requests (set_surrounding_text, set_content_type and set_cursor_rectangle) and a commit request. After an enter event or disable request all state information is invalidated and needs to be resent by the client. Destroy the wp_text_input object. Also disables all surfaces enabled through this wp_text_input object. Requests text input on the surface previously obtained from the enter event. This request must be issued every time the active text input changes to a new one, including within the current surface. Use zwp_text_input_v3.disable when there is no longer any input focus on the current surface. Clients must not enable more than one text input on the single seat and should disable the current text input before enabling the new one. At most one instance of text input may be in enabled state per instance, Requests to enable the another text input when some text input is active must be ignored by compositor. This request resets all state associated with previous enable, disable, set_surrounding_text, set_text_change_cause, set_content_type, and set_cursor_rectangle requests, as well as the state associated with preedit_string, commit_string, and delete_surrounding_text events. The set_surrounding_text, set_content_type and set_cursor_rectangle requests must follow if the text input supports the necessary functionality. State set with this request is double-buffered. It will get applied on the next zwp_text_input_v3.commit request, and stay valid until the next committed enable or disable request. The changes must be applied by the compositor after issuing a zwp_text_input_v3.commit request. Explicitly disable text input on the current surface (typically when there is no focus on any text entry inside the surface). State set with this request is double-buffered. It will get applied on the next zwp_text_input_v3.commit request. Sets the surrounding plain text around the input, excluding the preedit text. The client should notify the compositor of any changes in any of the values carried with this request, including changes caused by handling incoming text-input events as well as changes caused by other mechanisms like keyboard typing. If the client is unaware of the text around the cursor, it should not issue this request, to signify lack of support to the compositor. Text is UTF-8 encoded, and should include the cursor position, the complete selection and additional characters before and after them. There is a maximum length of wayland messages, so text can not be longer than 4000 bytes. Cursor is the byte offset of the cursor within text buffer. Anchor is the byte offset of the selection anchor within text buffer. If there is no selected text, anchor is the same as cursor. If any preedit text is present, it is replaced with a cursor for the purpose of this event. Values set with this request are double-buffered. They will get applied on the next zwp_text_input_v3.commit request, and stay valid until the next committed enable or disable request. The initial state for affected fields is empty, meaning that the text input does not support sending surrounding text. If the empty values get applied, subsequent attempts to change them may have no effect. Reason for the change of surrounding text or cursor posision. Tells the compositor why the text surrounding the cursor changed. Whenever the client detects an external change in text, cursor, or anchor posision, it must issue this request to the compositor. This request is intended to give the input method a chance to update the preedit text in an appropriate way, e.g. by removing it when the user starts typing with a keyboard. cause describes the source of the change. The value set with this request is double-buffered. It must be applied and reset to initial at the next zwp_text_input_v3.commit request. The initial value of cause is input_method. Content hint is a bitmask to allow to modify the behavior of the text input. The content purpose allows to specify the primary purpose of a text input. This allows an input method to show special purpose input panels with extra characters or to disallow some characters. Sets the content purpose and content hint. While the purpose is the basic purpose of an input field, the hint flags allow to modify some of the behavior. Values set with this request are double-buffered. They will get applied on the next zwp_text_input_v3.commit request. Subsequent attempts to update them may have no effect. The values remain valid until the next committed enable or disable request. The initial value for hint is none, and the initial value for purpose is normal. Marks an area around the cursor as a x, y, width, height rectangle in surface local coordinates. Allows the compositor to put a window with word suggestions near the cursor, without obstructing the text being input. If the client is unaware of the position of edited text, it should not issue this request, to signify lack of support to the compositor. Values set with this request are double-buffered. They will get applied on the next zwp_text_input_v3.commit request, and stay valid until the next committed enable or disable request. The initial values describing a cursor rectangle are empty. That means the text input does not support describing the cursor area. If the empty values get applied, subsequent attempts to change them may have no effect. Atomically applies state changes recently sent to the compositor. The commit request establishes and updates the state of the client, and must be issued after any changes to apply them. Text input state (enabled status, content purpose, content hint, surrounding text and change cause, cursor rectangle) is conceptually double-buffered within the context of a text input, i.e. between a committed enable request and the following committed enable or disable request. Protocol requests modify the pending state, as opposed to the current state in use by the input method. 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. Requests are applied in the order of arrival. Neither current nor pending state are modified unless noted otherwise. The compositor must count the number of commit requests coming from each zwp_text_input_v3 object and use the count as the serial in done events. Notification that this seat's text-input focus is on a certain surface. If client has created multiple text input objects, compositor must send this event to all of them. When the seat has the keyboard capability the text-input focus follows the keyboard focus. This event sets the current surface for the text-input object. Notification that this seat's text-input focus is no longer on a certain surface. The client should reset any preedit string previously set. The leave notification clears the current surface. It is sent before the enter notification for the new focus. After leave event, compositor must ignore requests from any text input instances until next enter event. When the seat has the keyboard capability the text-input focus follows the keyboard focus. Notify when a new composing text (pre-edit) should be set at the current cursor position. Any previously set composing text must be removed. Any previously existing selected text must be removed. The argument text contains the pre-edit string buffer. The parameters cursor_begin and cursor_end are counted in bytes relative to the beginning of the submitted text buffer. Cursor should be hidden when both are equal to -1. They could be represented by the client as a line if both values are the same, or as a text highlight otherwise. Values set with this event are double-buffered. They must be applied and reset to initial on the next zwp_text_input_v3.done event. The initial value of text is an empty string, and cursor_begin, cursor_end and cursor_hidden are all 0. Notify when text should be inserted into the editor widget. The text to commit could be either just a single character after a key press or the result of some composing (pre-edit). Values set with this event are double-buffered. They must be applied and reset to initial on the next zwp_text_input_v3.done event. The initial value of text is an empty string. Notify when the text around the current cursor position should be deleted. Before_length and after_length are the number of bytes before and after the current cursor index (excluding the selection) to delete. If a preedit text is present, in effect before_length is counted from the beginning of it, and after_length from its end (see done event sequence). Values set with this event are double-buffered. They must be applied and reset to initial on the next zwp_text_input_v3.done event. The initial values of both before_length and after_length are 0. Instruct the application to apply changes to state requested by the preedit_string, commit_string and delete_surrounding_text events. The state relating to these events is double-buffered, and each one modifies the pending state. This event replaces the current state with the pending state. The application must proceed by evaluating the changes in the following order: 1. Replace existing preedit string with the cursor. 2. Delete requested surrounding text. 3. Insert commit string with the cursor at its end. 4. Calculate surrounding text to send. 5. Insert new preedit text in cursor position. 6. Place cursor inside preedit text. The serial number reflects the last state of the zwp_text_input_v3 object known to the compositor. The value of the serial argument must be equal to the number of commit requests already issued on that object. When the client receives a done event with a serial different than the number of past commit requests, it must proceed as normal, except it should not change the current state of the zwp_text_input_v3 object. A factory for text-input objects. This object is a global singleton. Destroy the wp_text_input_manager object. Creates a new text-input object for a given seat. wlcs-1.7.0/src/protocol/wayland.xml000066400000000000000000003626011453657357500173340ustar00rootroot00000000000000 Copyright © 2008-2011 Kristian Høgsberg Copyright © 2010-2011 Intel Corporation Copyright © 2012-2013 Collabora, Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The core global object. This is a special singleton object. It is used for internal Wayland protocol features. The sync request asks the server to emit the 'done' event on the returned wl_callback object. Since requests are handled in-order and events are delivered in-order, this can be used as a barrier to ensure all previous requests and the resulting events have been handled. The object returned by this request will be destroyed by the compositor after the callback is fired and as such the client must not attempt to use it after that point. The callback_data passed in the callback is the event serial. This request creates a registry object that allows the client to list and bind the global objects available from the compositor. The error event is sent out when a fatal (non-recoverable) error has occurred. The object_id argument is the object where the error occurred, most often in response to a request to that object. The code identifies the error and is defined by the object interface. As such, each interface defines its own set of error codes. The message is a brief description of the error, for (debugging) convenience. These errors are global and can be emitted in response to any server request. This event is used internally by the object ID management logic. When a client deletes an object, the server will send this event to acknowledge that it has seen the delete request. When the client receives this event, it will know that it can safely reuse the object ID. The singleton global registry object. The server has a number of global objects that are available to all clients. These objects typically represent an actual object in the server (for example, an input device) or they are singleton objects that provide extension functionality. When a client creates a registry object, the registry object will emit a global event for each global currently in the registry. Globals come and go as a result of device or monitor hotplugs, reconfiguration or other events, and the registry will send out global and global_remove events to keep the client up to date with the changes. To mark the end of the initial burst of events, the client can use the wl_display.sync request immediately after calling wl_display.get_registry. A client can bind to a global object by using the bind request. This creates a client-side handle that lets the object emit events to the client and lets the client invoke requests on the object. Binds a new, client-created object to the server using the specified name as the identifier. Notify the client of global objects. The event notifies the client that a global object with the given name is now available, and it implements the given version of the given interface. Notify the client of removed global objects. This event notifies the client that the global identified by name is no longer available. If the client bound to the global using the bind request, the client should now destroy that object. The object remains valid and requests to the object will be ignored until the client destroys it, to avoid races between the global going away and a client sending a request to it. Clients can handle the 'done' event to get notified when the related request is done. Notify the client when the related request is done. A compositor. This object is a singleton global. The compositor is in charge of combining the contents of multiple surfaces into one displayable output. Ask the compositor to create a new surface. Ask the compositor to create a new region. The wl_shm_pool object encapsulates a piece of memory shared between the compositor and client. Through the wl_shm_pool object, the client can allocate shared memory wl_buffer objects. All objects created through the same pool share the same underlying mapped memory. Reusing the mapped memory avoids the setup/teardown overhead and is useful when interactively resizing a surface or for many small buffers. Create a wl_buffer object from the pool. The buffer is created offset bytes into the pool and has width and height as specified. The stride argument specifies the number of bytes from the beginning of one row to the beginning of the next. The format is the pixel format of the buffer and must be one of those advertised through the wl_shm.format event. A buffer will keep a reference to the pool it was created from so it is valid to destroy the pool immediately after creating a buffer from it. Destroy the shared memory pool. The mmapped memory will be released when all buffers that have been created from this pool are gone. This request will cause the server to remap the backing memory for the pool from the file descriptor passed when the pool was created, but using the new size. This request can only be used to make the pool bigger. A singleton global object that provides support for shared memory. Clients can create wl_shm_pool objects using the create_pool request. At connection setup time, the wl_shm object emits one or more format events to inform clients about the valid pixel formats that can be used for buffers. These errors can be emitted in response to wl_shm requests. This describes the memory layout of an individual pixel. All renderers should support argb8888 and xrgb8888 but any other formats are optional and may not be supported by the particular renderer in use. The drm format codes match the macros defined in drm_fourcc.h. The formats actually supported by the compositor will be reported by the format event. Create a new wl_shm_pool object. The pool can be used to create shared memory based buffer objects. The server will mmap size bytes of the passed file descriptor, to use as backing memory for the pool. Informs the client about a valid pixel format that can be used for buffers. Known formats include argb8888 and xrgb8888. A buffer provides the content for a wl_surface. Buffers are created through factory interfaces such as wl_drm, wl_shm or similar. It has a width and a height and can be attached to a wl_surface, but the mechanism by which a client provides and updates the contents is defined by the buffer factory interface. Destroy a buffer. If and how you need to release the backing storage is defined by the buffer factory interface. For possible side-effects to a surface, see wl_surface.attach. Sent when this wl_buffer is no longer used by the compositor. The client is now free to reuse or destroy this buffer and its backing storage. If a client receives a release event before the frame callback requested in the same wl_surface.commit that attaches this wl_buffer to a surface, then the client is immediately free to reuse the buffer and its backing storage, and does not need a second buffer for the next surface content update. Typically this is possible, when the compositor maintains a copy of the wl_surface contents, e.g. as a GL texture. This is an important optimization for GL(ES) compositors with wl_shm clients. A wl_data_offer represents a piece of data offered for transfer by another client (the source client). It is used by the copy-and-paste and drag-and-drop mechanisms. The offer describes the different mime types that the data can be converted to and provides the mechanism for transferring the data directly from the source client. Indicate that the client can accept the given mime type, or NULL for not accepted. For objects of version 2 or older, this request is used by the client to give feedback whether the client can receive the given mime type, or NULL if none is accepted; the feedback does not determine whether the drag-and-drop operation succeeds or not. For objects of version 3 or newer, this request determines the final result of the drag-and-drop operation. If the end result is that no mime types were accepted, the drag-and-drop operation will be cancelled and the corresponding drag source will receive wl_data_source.cancelled. Clients may still use this event in conjunction with wl_data_source.action for feedback. To transfer the offered data, the client issues this request and indicates the mime type it wants to receive. The transfer happens through the passed file descriptor (typically created with the pipe system call). The source client writes the data in the mime type representation requested and then closes the file descriptor. The receiving client reads from the read end of the pipe until EOF and then closes its end, at which point the transfer is complete. This request may happen multiple times for different mime types, both before and after wl_data_device.drop. Drag-and-drop destination clients may preemptively fetch data or examine it more closely to determine acceptance. Destroy the data offer. Sent immediately after creating the wl_data_offer object. One event per offered mime type. Notifies the compositor that the drag destination successfully finished the drag-and-drop operation. Upon receiving this request, the compositor will emit wl_data_source.dnd_finished on the drag source client. It is a client error to perform other requests than wl_data_offer.destroy after this one. It is also an error to perform this request after a NULL mime type has been set in wl_data_offer.accept or no action was received through wl_data_offer.action. Sets the actions that the destination side client supports for this operation. This request may trigger the emission of wl_data_source.action and wl_data_offer.action events if the compositor needs to change the selected action. This request can be called multiple times throughout the drag-and-drop operation, typically in response to wl_data_device.enter or wl_data_device.motion events. This request determines the final result of the drag-and-drop operation. If the end result is that no action is accepted, the drag source will receive wl_drag_source.cancelled. The dnd_actions argument must contain only values expressed in the wl_data_device_manager.dnd_actions enum, and the preferred_action argument must only contain one of those values set, otherwise it will result in a protocol error. While managing an "ask" action, the destination drag-and-drop client may perform further wl_data_offer.receive requests, and is expected to perform one last wl_data_offer.set_actions request with a preferred action other than "ask" (and optionally wl_data_offer.accept) before requesting wl_data_offer.finish, in order to convey the action selected by the user. If the preferred action is not in the wl_data_offer.source_actions mask, an error will be raised. If the "ask" action is dismissed (e.g. user cancellation), the client is expected to perform wl_data_offer.destroy right away. This request can only be made on drag-and-drop offers, a protocol error will be raised otherwise. This event indicates the actions offered by the data source. It will be sent right after wl_data_device.enter, or anytime the source side changes its offered actions through wl_data_source.set_actions. This event indicates the action selected by the compositor after matching the source/destination side actions. Only one action (or none) will be offered here. This event can be emitted multiple times during the drag-and-drop operation in response to destination side action changes through wl_data_offer.set_actions. This event will no longer be emitted after wl_data_device.drop happened on the drag-and-drop destination, the client must honor the last action received, or the last preferred one set through wl_data_offer.set_actions when handling an "ask" action. Compositors may also change the selected action on the fly, mainly in response to keyboard modifier changes during the drag-and-drop operation. The most recent action received is always the valid one. Prior to receiving wl_data_device.drop, the chosen action may change (e.g. due to keyboard modifiers being pressed). At the time of receiving wl_data_device.drop the drag-and-drop destination must honor the last action received. Action changes may still happen after wl_data_device.drop, especially on "ask" actions, where the drag-and-drop destination may choose another action afterwards. Action changes happening at this stage are always the result of inter-client negotiation, the compositor shall no longer be able to induce a different action. Upon "ask" actions, it is expected that the drag-and-drop destination may potentially choose a different action and/or mime type, based on wl_data_offer.source_actions and finally chosen by the user (e.g. popping up a menu with the available options). The final wl_data_offer.set_actions and wl_data_offer.accept requests must happen before the call to wl_data_offer.finish. The wl_data_source object is the source side of a wl_data_offer. It is created by the source client in a data transfer and provides a way to describe the offered data and a way to respond to requests to transfer the data. This request adds a mime type to the set of mime types advertised to targets. Can be called several times to offer multiple types. Destroy the data source. Sent when a target accepts pointer_focus or motion events. If a target does not accept any of the offered types, type is NULL. Used for feedback during drag-and-drop. Request for data from the client. Send the data as the specified mime type over the passed file descriptor, then close it. This data source is no longer valid. There are several reasons why this could happen: - The data source has been replaced by another data source. - The drag-and-drop operation was performed, but the drop destination did not accept any of the mime types offered through wl_data_source.target. - The drag-and-drop operation was performed, but the drop destination did not select any of the actions present in the mask offered through wl_data_source.action. - The drag-and-drop operation was performed but didn't happen over a surface. - The compositor cancelled the drag-and-drop operation (e.g. compositor dependent timeouts to avoid stale drag-and-drop transfers). The client should clean up and destroy this data source. For objects of version 2 or older, wl_data_source.cancelled will only be emitted if the data source was replaced by another data source. Sets the actions that the source side client supports for this operation. This request may trigger wl_data_source.action and wl_data_offer.action events if the compositor needs to change the selected action. The dnd_actions argument must contain only values expressed in the wl_data_device_manager.dnd_actions enum, otherwise it will result in a protocol error. This request must be made once only, and can only be made on sources used in drag-and-drop, so it must be performed before wl_data_device.start_drag. Attempting to use the source other than for drag-and-drop will raise a protocol error. The user performed the drop action. This event does not indicate acceptance, wl_data_source.cancelled may still be emitted afterwards if the drop destination does not accept any mime type. However, this event might however not be received if the compositor cancelled the drag-and-drop operation before this event could happen. Note that the data_source may still be used in the future and should not be destroyed here. The drop destination finished interoperating with this data source, so the client is now free to destroy this data source and free all associated data. If the action used to perform the operation was "move", the source can now delete the transferred data. This event indicates the action selected by the compositor after matching the source/destination side actions. Only one action (or none) will be offered here. This event can be emitted multiple times during the drag-and-drop operation, mainly in response to destination side changes through wl_data_offer.set_actions, and as the data device enters/leaves surfaces. It is only possible to receive this event after wl_data_source.dnd_drop_performed if the drag-and-drop operation ended in an "ask" action, in which case the final wl_data_source.action event will happen immediately before wl_data_source.dnd_finished. Compositors may also change the selected action on the fly, mainly in response to keyboard modifier changes during the drag-and-drop operation. The most recent action received is always the valid one. The chosen action may change alongside negotiation (e.g. an "ask" action can turn into a "move" operation), so the effects of the final action must always be applied in wl_data_offer.dnd_finished. Clients can trigger cursor surface changes from this point, so they reflect the current action. There is one wl_data_device per seat which can be obtained from the global wl_data_device_manager singleton. A wl_data_device provides access to inter-client data transfer mechanisms such as copy-and-paste and drag-and-drop. This request asks the compositor to start a drag-and-drop operation on behalf of the client. The source argument is the data source that provides the data for the eventual data transfer. If source is NULL, enter, leave and motion events are sent only to the client that initiated the drag and the client is expected to handle the data passing internally. The origin surface is the surface where the drag originates and the client must have an active implicit grab that matches the serial. The icon surface is an optional (can be NULL) surface that provides an icon to be moved around with the cursor. Initially, the top-left corner of the icon surface is placed at the cursor hotspot, but subsequent wl_surface.attach request can move the relative position. Attach requests must be confirmed with wl_surface.commit as usual. The icon surface is given the role of a drag-and-drop icon. If the icon surface already has another role, it raises a protocol error. The current and pending input regions of the icon wl_surface are cleared, and wl_surface.set_input_region is ignored until the wl_surface is no longer used as the icon surface. When the use as an icon ends, the current and pending input regions become undefined, and the wl_surface is unmapped. This request asks the compositor to set the selection to the data from the source on behalf of the client. To unset the selection, set the source to NULL. The data_offer event introduces a new wl_data_offer object, which will subsequently be used in either the data_device.enter event (for drag-and-drop) or the data_device.selection event (for selections). Immediately following the data_device_data_offer event, the new data_offer object will send out data_offer.offer events to describe the mime types it offers. This event is sent when an active drag-and-drop pointer enters a surface owned by the client. The position of the pointer at enter time is provided by the x and y arguments, in surface-local coordinates. This event is sent when the drag-and-drop pointer leaves the surface and the session ends. The client must destroy the wl_data_offer introduced at enter time at this point. This event is sent when the drag-and-drop pointer moves within the currently focused surface. The new position of the pointer is provided by the x and y arguments, in surface-local coordinates. The event is sent when a drag-and-drop operation is ended because the implicit grab is removed. The drag-and-drop destination is expected to honor the last action received through wl_data_offer.action, if the resulting action is "copy" or "move", the destination can still perform wl_data_offer.receive requests, and is expected to end all transfers with a wl_data_offer.finish request. If the resulting action is "ask", the action will not be considered final. The drag-and-drop destination is expected to perform one last wl_data_offer.set_actions request, or wl_data_offer.destroy in order to cancel the operation. The selection event is sent out to notify the client of a new wl_data_offer for the selection for this device. The data_device.data_offer and the data_offer.offer events are sent out immediately before this event to introduce the data offer object. The selection event is sent to a client immediately before receiving keyboard focus and when a new selection is set while the client has keyboard focus. The data_offer is valid until a new data_offer or NULL is received or until the client loses keyboard focus. The client must destroy the previous selection data_offer, if any, upon receiving this event. This request destroys the data device. The wl_data_device_manager is a singleton global object that provides access to inter-client data transfer mechanisms such as copy-and-paste and drag-and-drop. These mechanisms are tied to a wl_seat and this interface lets a client get a wl_data_device corresponding to a wl_seat. Depending on the version bound, the objects created from the bound wl_data_device_manager object will have different requirements for functioning properly. See wl_data_source.set_actions, wl_data_offer.accept and wl_data_offer.finish for details. Create a new data source. Create a new data device for a given seat. This is a bitmask of the available/preferred actions in a drag-and-drop operation. In the compositor, the selected action is a result of matching the actions offered by the source and destination sides. "action" events with a "none" action will be sent to both source and destination if there is no match. All further checks will effectively happen on (source actions ∩ destination actions). In addition, compositors may also pick different actions in reaction to key modifiers being pressed. One common design that is used in major toolkits (and the behavior recommended for compositors) is: - If no modifiers are pressed, the first match (in bit order) will be used. - Pressing Shift selects "move", if enabled in the mask. - Pressing Control selects "copy", if enabled in the mask. Behavior beyond that is considered implementation-dependent. Compositors may for example bind other modifiers (like Alt/Meta) or drags initiated with other buttons than BTN_LEFT to specific actions (e.g. "ask"). This interface is implemented by servers that provide desktop-style user interfaces. It allows clients to associate a wl_shell_surface with a basic surface. Create a shell surface for an existing surface. This gives the wl_surface the role of a shell surface. If the wl_surface already has another role, it raises a protocol error. Only one shell surface can be associated with a given surface. An interface that may be implemented by a wl_surface, for implementations that provide a desktop-style user interface. It provides requests to treat surfaces like toplevel, fullscreen or popup windows, move, resize or maximize them, associate metadata like title and class, etc. On the server side the object is automatically destroyed when the related wl_surface is destroyed. On the client side, wl_shell_surface_destroy() must be called before destroying the wl_surface object. A client must respond to a ping event with a pong request or the client may be deemed unresponsive. Start a pointer-driven move of the surface. This request must be used in response to a button press event. The server may ignore move requests depending on the state of the surface (e.g. fullscreen or maximized). These values are used to indicate which edge of a surface is being dragged in a resize operation. The server may use this information to adapt its behavior, e.g. choose an appropriate cursor image. Start a pointer-driven resizing of the surface. This request must be used in response to a button press event. The server may ignore resize requests depending on the state of the surface (e.g. fullscreen or maximized). Map the surface as a toplevel surface. A toplevel surface is not fullscreen, maximized or transient. These flags specify details of the expected behaviour of transient surfaces. Used in the set_transient request. Map the surface relative to an existing surface. The x and y arguments specify the location of the upper left corner of the surface relative to the upper left corner of the parent surface, in surface-local coordinates. The flags argument controls details of the transient behaviour. Hints to indicate to the compositor how to deal with a conflict between the dimensions of the surface and the dimensions of the output. The compositor is free to ignore this parameter. Map the surface as a fullscreen surface. If an output parameter is given then the surface will be made fullscreen on that output. If the client does not specify the output then the compositor will apply its policy - usually choosing the output on which the surface has the biggest surface area. The client may specify a method to resolve a size conflict between the output size and the surface size - this is provided through the method parameter. The framerate parameter is used only when the method is set to "driver", to indicate the preferred framerate. A value of 0 indicates that the client does not care about framerate. The framerate is specified in mHz, that is framerate of 60000 is 60Hz. A method of "scale" or "driver" implies a scaling operation of the surface, either via a direct scaling operation or a change of the output mode. This will override any kind of output scaling, so that mapping a surface with a buffer size equal to the mode can fill the screen independent of buffer_scale. A method of "fill" means we don't scale up the buffer, however any output scale is applied. This means that you may run into an edge case where the application maps a buffer with the same size of the output mode but buffer_scale 1 (thus making a surface larger than the output). In this case it is allowed to downscale the results to fit the screen. The compositor must reply to this request with a configure event with the dimensions for the output on which the surface will be made fullscreen. Map the surface as a popup. A popup surface is a transient surface with an added pointer grab. An existing implicit grab will be changed to owner-events mode, and the popup grab will continue after the implicit grab ends (i.e. releasing the mouse button does not cause the popup to be unmapped). The popup grab continues until the window is destroyed or a mouse button is pressed in any other client's window. A click in any of the client's surfaces is reported as normal, however, clicks in other clients' surfaces will be discarded and trigger the callback. The x and y arguments specify the location of the upper left corner of the surface relative to the upper left corner of the parent surface, in surface-local coordinates. Map the surface as a maximized surface. If an output parameter is given then the surface will be maximized on that output. If the client does not specify the output then the compositor will apply its policy - usually choosing the output on which the surface has the biggest surface area. The compositor will reply with a configure event telling the expected new surface size. The operation is completed on the next buffer attach to this surface. A maximized surface typically fills the entire output it is bound to, except for desktop elements such as panels. This is the main difference between a maximized shell surface and a fullscreen shell surface. The details depend on the compositor implementation. Set a short title for the surface. This string may be used to identify the surface in a task bar, window list, or other user interface elements provided by the compositor. The string must be encoded in UTF-8. Set a class for the surface. The surface class identifies the general class of applications to which the surface belongs. A common convention is to use the file name (or the full path if it is a non-standard location) of the application's .desktop file as the class. Ping a client to check if it is receiving events and sending requests. A client is expected to reply with a pong request. The configure event asks the client to resize its surface. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). The edges parameter provides a hint about how the surface was resized. The client may use this information to decide how to adjust its content to the new size (e.g. a scrolling area might adjust its content position to leave the viewable content unmoved). The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The popup_done event is sent out when a popup grab is broken, that is, when the user clicks a surface that doesn't belong to the client owning the popup surface. A surface is a rectangular area that is displayed on the screen. It has a location, size and pixel contents. The size of a surface (and relative positions on it) is described in surface-local coordinates, which may differ from the buffer coordinates of the pixel content, in case a buffer_transform or a buffer_scale is used. A surface without a "role" is fairly useless: a compositor does not know where, when or how to present it. The role is the purpose of a wl_surface. Examples of roles are a cursor for a pointer (as set by wl_pointer.set_cursor), a drag icon (wl_data_device.start_drag), a sub-surface (wl_subcompositor.get_subsurface), and a window as defined by a shell protocol (e.g. wl_shell.get_shell_surface). A surface can have only one role at a time. Initially a wl_surface does not have a role. Once a wl_surface is given a role, it is set permanently for the whole lifetime of the wl_surface object. Giving the current role again is allowed, unless explicitly forbidden by the relevant interface specification. Surface roles are given by requests in other interfaces such as wl_pointer.set_cursor. The request should explicitly mention that this request gives a role to a wl_surface. Often, this request also creates a new protocol object that represents the role and adds additional functionality to wl_surface. When a client wants to destroy a wl_surface, they must destroy this 'role object' before the wl_surface. Destroying the role object does not remove the role from the wl_surface, but it may stop the wl_surface from "playing the role". For instance, if a wl_subsurface object is destroyed, the wl_surface it was created for will be unmapped and forget its position and z-order. It is allowed to create a wl_subsurface for the same wl_surface again, but it is not allowed to use the wl_surface as a cursor (cursor is a different role than sub-surface, and role switching is not allowed). These errors can be emitted in response to wl_surface requests. Deletes the surface and invalidates its object ID. Set a buffer as the content of this surface. The new size of the surface is calculated based on the buffer size transformed by the inverse buffer_transform and the inverse buffer_scale. This means that the supplied buffer must be an integer multiple of the buffer_scale. The x and y arguments specify the location of the new pending buffer's upper left corner, relative to the current buffer's upper left corner, in surface-local coordinates. In other words, the x and y, combined with the new surface size define in which directions the surface's size changes. Surface contents are double-buffered state, see wl_surface.commit. The initial surface contents are void; there is no content. wl_surface.attach assigns the given wl_buffer as the pending wl_buffer. wl_surface.commit makes the pending wl_buffer the new surface contents, and the size of the surface becomes the size calculated from the wl_buffer, as described above. After commit, there is no pending buffer until the next attach. Committing a pending wl_buffer allows the compositor to read the pixels in the wl_buffer. The compositor may access the pixels at any time after the wl_surface.commit request. When the compositor will not access the pixels anymore, it will send the wl_buffer.release event. Only after receiving wl_buffer.release, the client may reuse the wl_buffer. A wl_buffer that has been attached and then replaced by another attach instead of committed will not receive a release event, and is not used by the compositor. Destroying the wl_buffer after wl_buffer.release does not change the surface contents. However, if the client destroys the wl_buffer before receiving the wl_buffer.release event, the surface contents become undefined immediately. If wl_surface.attach is sent with a NULL wl_buffer, the following wl_surface.commit will remove the surface content. This request is used to describe the regions where the pending buffer is different from the current surface contents, and where the surface therefore needs to be repainted. The compositor ignores the parts of the damage that fall outside of the surface. Damage is double-buffered state, see wl_surface.commit. The damage rectangle is specified in surface-local coordinates, where x and y specify the upper left corner of the damage rectangle. The initial value for pending damage is empty: no damage. wl_surface.damage adds pending damage: the new pending damage is the union of old pending damage and the given rectangle. wl_surface.commit assigns pending damage as the current damage, and clears pending damage. The server will clear the current damage as it repaints the surface. Alternatively, damage can be posted with wl_surface.damage_buffer which uses buffer coordinates instead of surface coordinates, and is probably the preferred and intuitive way of doing this. Request a notification when it is a good time to start drawing a new frame, by creating a frame callback. This is useful for throttling redrawing operations, and driving animations. When a client is animating on a wl_surface, it can use the 'frame' request to get notified when it is a good time to draw and commit the next frame of animation. If the client commits an update earlier than that, it is likely that some updates will not make it to the display, and the client is wasting resources by drawing too often. The frame request will take effect on the next wl_surface.commit. The notification will only be posted for one frame unless requested again. For a wl_surface, the notifications are posted in the order the frame requests were committed. The server must send the notifications so that a client will not send excessive updates, while still allowing the highest possible update rate for clients that wait for the reply before drawing again. The server should give some time for the client to draw and commit after sending the frame callback events to let it hit the next output refresh. A server should avoid signaling the frame callbacks if the surface is not visible in any way, e.g. the surface is off-screen, or completely obscured by other opaque surfaces. The object returned by this request will be destroyed by the compositor after the callback is fired and as such the client must not attempt to use it after that point. The callback_data passed in the callback is the current time, in milliseconds, with an undefined base. This request sets the region of the surface that contains opaque content. The opaque region is an optimization hint for the compositor that lets it optimize the redrawing of content behind opaque regions. Setting an opaque region is not required for correct behaviour, but marking transparent content as opaque will result in repaint artifacts. The opaque region is specified in surface-local coordinates. The compositor ignores the parts of the opaque region that fall outside of the surface. Opaque region is double-buffered state, see wl_surface.commit. wl_surface.set_opaque_region changes the pending opaque region. wl_surface.commit copies the pending region to the current region. Otherwise, the pending and current regions are never changed. The initial value for an opaque region is empty. Setting the pending opaque region has copy semantics, and the wl_region object can be destroyed immediately. A NULL wl_region causes the pending opaque region to be set to empty. This request sets the region of the surface that can receive pointer and touch events. Input events happening outside of this region will try the next surface in the server surface stack. The compositor ignores the parts of the input region that fall outside of the surface. The input region is specified in surface-local coordinates. Input region is double-buffered state, see wl_surface.commit. wl_surface.set_input_region changes the pending input region. wl_surface.commit copies the pending region to the current region. Otherwise the pending and current regions are never changed, except cursor and icon surfaces are special cases, see wl_pointer.set_cursor and wl_data_device.start_drag. The initial value for an input region is infinite. That means the whole surface will accept input. Setting the pending input region has copy semantics, and the wl_region object can be destroyed immediately. A NULL wl_region causes the input region to be set to infinite. Surface state (input, opaque, and damage regions, attached buffers, etc.) is double-buffered. Protocol requests modify the pending state, as opposed to the current state in use by the compositor. A commit request atomically applies all pending state, replacing the current state. After commit, the new pending state is as documented for each related request. On commit, a pending wl_buffer is applied first, and all other state second. This means that all coordinates in double-buffered state are relative to the new wl_buffer coming into use, except for wl_surface.attach itself. If there is no pending wl_buffer, the coordinates are relative to the current surface contents. All requests that need a commit to become effective are documented to affect double-buffered state. Other interfaces may add further double-buffered surface state. This is emitted whenever a surface's creation, movement, or resizing results in some part of it being within the scanout region of an output. Note that a surface may be overlapping with zero or more outputs. This is emitted whenever a surface's creation, movement, or resizing results in it no longer having any part of it within the scanout region of an output. This request sets an optional transformation on how the compositor interprets the contents of the buffer attached to the surface. The accepted values for the transform parameter are the values for wl_output.transform. Buffer transform is double-buffered state, see wl_surface.commit. A newly created surface has its buffer transformation set to normal. wl_surface.set_buffer_transform changes the pending buffer transformation. wl_surface.commit copies the pending buffer transformation to the current one. Otherwise, the pending and current values are never changed. The purpose of this request is to allow clients to render content according to the output transform, thus permitting the compositor to use certain optimizations even if the display is rotated. Using hardware overlays and scanning out a client buffer for fullscreen surfaces are examples of such optimizations. Those optimizations are highly dependent on the compositor implementation, so the use of this request should be considered on a case-by-case basis. Note that if the transform value includes 90 or 270 degree rotation, the width of the buffer will become the surface height and the height of the buffer will become the surface width. If transform is not one of the values from the wl_output.transform enum the invalid_transform protocol error is raised. This request sets an optional scaling factor on how the compositor interprets the contents of the buffer attached to the window. Buffer scale is double-buffered state, see wl_surface.commit. A newly created surface has its buffer scale set to 1. wl_surface.set_buffer_scale changes the pending buffer scale. wl_surface.commit copies the pending buffer scale to the current one. Otherwise, the pending and current values are never changed. The purpose of this request is to allow clients to supply higher resolution buffer data for use on high resolution outputs. It is intended that you pick the same buffer scale as the scale of the output that the surface is displayed on. This means the compositor can avoid scaling when rendering the surface on that output. Note that if the scale is larger than 1, then you have to attach a buffer that is larger (by a factor of scale in each dimension) than the desired surface size. If scale is not positive the invalid_scale protocol error is raised. This request is used to describe the regions where the pending buffer is different from the current surface contents, and where the surface therefore needs to be repainted. The compositor ignores the parts of the damage that fall outside of the surface. Damage is double-buffered state, see wl_surface.commit. The damage rectangle is specified in buffer coordinates, where x and y specify the upper left corner of the damage rectangle. The initial value for pending damage is empty: no damage. wl_surface.damage_buffer adds pending damage: the new pending damage is the union of old pending damage and the given rectangle. wl_surface.commit assigns pending damage as the current damage, and clears pending damage. The server will clear the current damage as it repaints the surface. This request differs from wl_surface.damage in only one way - it takes damage in buffer coordinates instead of surface-local coordinates. While this generally is more intuitive than surface coordinates, it is especially desirable when using wp_viewport or when a drawing library (like EGL) is unaware of buffer scale and buffer transform. Note: Because buffer transformation changes and damage requests may be interleaved in the protocol stream, it is impossible to determine the actual mapping between surface and buffer damage until wl_surface.commit time. Therefore, compositors wishing to take both kinds of damage into account will have to accumulate damage from the two requests separately and only transform from one to the other after receiving the wl_surface.commit. A seat is a group of keyboards, pointer and touch devices. This object is published as a global during start up, or when such a device is hot plugged. A seat typically has a pointer and maintains a keyboard focus and a pointer focus. This is a bitmask of capabilities this seat has; if a member is set, then it is present on the seat. This is emitted whenever a seat gains or loses the pointer, keyboard or touch capabilities. The argument is a capability enum containing the complete set of capabilities this seat has. When the pointer capability is added, a client may create a wl_pointer object using the wl_seat.get_pointer request. This object will receive pointer events until the capability is removed in the future. When the pointer capability is removed, a client should destroy the wl_pointer objects associated with the seat where the capability was removed, using the wl_pointer.release request. No further pointer events will be received on these objects. In some compositors, if a seat regains the pointer capability and a client has a previously obtained wl_pointer object of version 4 or less, that object may start sending pointer events again. This behavior is considered a misinterpretation of the intended behavior and must not be relied upon by the client. wl_pointer objects of version 5 or later must not send events if created before the most recent event notifying the client of an added pointer capability. The above behavior also applies to wl_keyboard and wl_touch with the keyboard and touch capabilities, respectively. The ID provided will be initialized to the wl_pointer interface for this seat. This request only takes effect if the seat has the pointer capability, or has had the pointer capability in the past. It is a protocol violation to issue this request on a seat that has never had the pointer capability. The ID provided will be initialized to the wl_keyboard interface for this seat. This request only takes effect if the seat has the keyboard capability, or has had the keyboard capability in the past. It is a protocol violation to issue this request on a seat that has never had the keyboard capability. The ID provided will be initialized to the wl_touch interface for this seat. This request only takes effect if the seat has the touch capability, or has had the touch capability in the past. It is a protocol violation to issue this request on a seat that has never had the touch capability. In a multiseat configuration this can be used by the client to help identify which physical devices the seat represents. Based on the seat configuration used by the compositor. Using this request a client can tell the server that it is not going to use the seat object anymore. The wl_pointer interface represents one or more input devices, such as mice, which control the pointer location and pointer_focus of a seat. The wl_pointer interface generates motion, enter and leave events for the surfaces that the pointer is located over, and button and axis events for button presses, button releases and scrolling. Set the pointer surface, i.e., the surface that contains the pointer image (cursor). This request gives the surface the role of a cursor. If the surface already has another role, it raises a protocol error. The cursor actually changes only if the pointer focus for this device is one of the requesting client's surfaces or the surface parameter is the current pointer surface. If there was a previous surface set with this request it is replaced. If surface is NULL, the pointer image is hidden. The parameters hotspot_x and hotspot_y define the position of the pointer surface relative to the pointer location. Its top-left corner is always at (x, y) - (hotspot_x, hotspot_y), where (x, y) are the coordinates of the pointer location, in surface-local coordinates. On surface.attach requests to the pointer surface, hotspot_x and hotspot_y are decremented by the x and y parameters passed to the request. Attach must be confirmed by wl_surface.commit as usual. The hotspot can also be updated by passing the currently set pointer surface to this request with new values for hotspot_x and hotspot_y. The current and pending input regions of the wl_surface are cleared, and wl_surface.set_input_region is ignored until the wl_surface is no longer used as the cursor. When the use as a cursor ends, the current and pending input regions become undefined, and the wl_surface is unmapped. Notification that this seat's pointer is focused on a certain surface. When a seat's focus enters a surface, the pointer image is undefined and a client should respond to this event by setting an appropriate pointer image with the set_cursor request. Notification that this seat's pointer is no longer focused on a certain surface. The leave notification is sent before the enter notification for the new focus. Notification of pointer location change. The arguments surface_x and surface_y are the location relative to the focused surface. Describes the physical state of a button that produced the button event. Mouse button click and release notifications. The location of the click is given by the last motion or enter event. The time argument is a timestamp with millisecond granularity, with an undefined base. The button is a button code as defined in the Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT. Any 16-bit button code value is reserved for future additions to the kernel's event code list. All other button codes above 0xFFFF are currently undefined but may be used in future versions of this protocol. Describes the axis types of scroll events. Scroll and other axis notifications. For scroll events (vertical and horizontal scroll axes), the value parameter is the length of a vector along the specified axis in a coordinate space identical to those of motion events, representing a relative movement along the specified axis. For devices that support movements non-parallel to axes multiple axis events will be emitted. When applicable, for example for touch pads, the server can choose to emit scroll events where the motion vector is equivalent to a motion event vector. When applicable, a client can transform its content relative to the scroll distance. Using this request a client can tell the server that it is not going to use the pointer object anymore. This request destroys the pointer proxy object, so clients must not call wl_pointer_destroy() after using this request. Indicates the end of a set of events that logically belong together. A client is expected to accumulate the data in all events within the frame before proceeding. All wl_pointer events before a wl_pointer.frame event belong logically together. For example, in a diagonal scroll motion the compositor will send an optional wl_pointer.axis_source event, two wl_pointer.axis events (horizontal and vertical) and finally a wl_pointer.frame event. The client may use this information to calculate a diagonal vector for scrolling. When multiple wl_pointer.axis events occur within the same frame, the motion vector is the combined motion of all events. When a wl_pointer.axis and a wl_pointer.axis_stop event occur within the same frame, this indicates that axis movement in one axis has stopped but continues in the other axis. When multiple wl_pointer.axis_stop events occur within the same frame, this indicates that these axes stopped in the same instance. A wl_pointer.frame event is sent for every logical event group, even if the group only contains a single wl_pointer event. Specifically, a client may get a sequence: motion, frame, button, frame, axis, frame, axis_stop, frame. The wl_pointer.enter and wl_pointer.leave events are logical events generated by the compositor and not the hardware. These events are also grouped by a wl_pointer.frame. When a pointer moves from one surface to another, a compositor should group the wl_pointer.leave event within the same wl_pointer.frame. However, a client must not rely on wl_pointer.leave and wl_pointer.enter being in the same wl_pointer.frame. Compositor-specific policies may require the wl_pointer.leave and wl_pointer.enter event being split across multiple wl_pointer.frame groups. Describes the source types for axis events. This indicates to the client how an axis event was physically generated; a client may adjust the user interface accordingly. For example, scroll events from a "finger" source may be in a smooth coordinate space with kinetic scrolling whereas a "wheel" source may be in discrete steps of a number of lines. The "continuous" axis source is a device generating events in a continuous coordinate space, but using something other than a finger. One example for this source is button-based scrolling where the vertical motion of a device is converted to scroll events while a button is held down. The "wheel tilt" axis source indicates that the actual device is a wheel but the scroll event is not caused by a rotation but a (usually sideways) tilt of the wheel. Source information for scroll and other axes. This event does not occur on its own. It is sent before a wl_pointer.frame event and carries the source information for all events within that frame. The source specifies how this event was generated. If the source is wl_pointer.axis_source.finger, a wl_pointer.axis_stop event will be sent when the user lifts the finger off the device. If the source is wl_pointer.axis_source.wheel, wl_pointer.axis_source.wheel_tilt or wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event may or may not be sent. Whether a compositor sends an axis_stop event for these sources is hardware-specific and implementation-dependent; clients must not rely on receiving an axis_stop event for these scroll sources and should treat scroll sequences from these scroll sources as unterminated by default. This event is optional. If the source is unknown for a particular axis event sequence, no event is sent. Only one wl_pointer.axis_source event is permitted per frame. The order of wl_pointer.axis_discrete and wl_pointer.axis_source is not guaranteed. Stop notification for scroll and other axes. For some wl_pointer.axis_source types, a wl_pointer.axis_stop event is sent to notify a client that the axis sequence has terminated. This enables the client to implement kinetic scrolling. See the wl_pointer.axis_source documentation for information on when this event may be generated. Any wl_pointer.axis events with the same axis_source after this event should be considered as the start of a new axis motion. The timestamp is to be interpreted identical to the timestamp in the wl_pointer.axis event. The timestamp value may be the same as a preceding wl_pointer.axis event. Discrete step information for scroll and other axes. This event carries the axis value of the wl_pointer.axis event in discrete steps (e.g. mouse wheel clicks). This event does not occur on its own, it is coupled with a wl_pointer.axis event that represents this axis value on a continuous scale. The protocol guarantees that each axis_discrete event is always followed by exactly one axis event with the same axis number within the same wl_pointer.frame. Note that the protocol allows for other events to occur between the axis_discrete and its coupled axis event, including other axis_discrete or axis events. This event is optional; continuous scrolling devices like two-finger scrolling on touchpads do not have discrete steps and do not generate this event. The discrete value carries the directional information. e.g. a value of -2 is two steps towards the negative direction of this axis. The axis number is identical to the axis number in the associated axis event. The order of wl_pointer.axis_discrete and wl_pointer.axis_source is not guaranteed. The wl_keyboard interface represents one or more keyboards associated with a seat. This specifies the format of the keymap provided to the client with the wl_keyboard.keymap event. This event provides a file descriptor to the client which can be memory-mapped to provide a keyboard mapping description. Notification that this seat's keyboard focus is on a certain surface. Notification that this seat's keyboard focus is no longer on a certain surface. The leave notification is sent before the enter notification for the new focus. Describes the physical state of a key that produced the key event. A key was pressed or released. The time argument is a timestamp with millisecond granularity, with an undefined base. Notifies clients that the modifier and/or group state has changed, and it should update its local state. Informs the client about the keyboard's repeat rate and delay. This event is sent as soon as the wl_keyboard object has been created, and is guaranteed to be received by the client before any key press event. Negative values for either rate or delay are illegal. A rate of zero will disable any repeating (regardless of the value of delay). This event can be sent later on as well with a new value if necessary, so clients should continue listening for the event past the creation of wl_keyboard. The wl_touch interface represents a touchscreen associated with a seat. Touch interactions can consist of one or more contacts. For each contact, a series of events is generated, starting with a down event, followed by zero or more motion events, and ending with an up event. Events relating to the same contact point can be identified by the ID of the sequence. A new touch point has appeared on the surface. This touch point is assigned a unique ID. Future events from this touch point reference this ID. The ID ceases to be valid after a touch up event and may be reused in the future. The touch point has disappeared. No further events will be sent for this touch point and the touch point's ID is released and may be reused in a future touch down event. A touch point has changed coordinates. Indicates the end of a set of events that logically belong together. A client is expected to accumulate the data in all events within the frame before proceeding. A wl_touch.frame terminates at least one event but otherwise no guarantee is provided about the set of events within a frame. A client must assume that any state not updated in a frame is unchanged from the previously known state. Sent if the compositor decides the touch stream is a global gesture. No further events are sent to the clients from that particular gesture. Touch cancellation applies to all touch points currently active on this client's surface. The client is responsible for finalizing the touch points, future touch points on this surface may reuse the touch point ID. Sent when a touchpoint has changed its shape. This event does not occur on its own. It is sent before a wl_touch.frame event and carries the new shape information for any previously reported, or new touch points of that frame. Other events describing the touch point such as wl_touch.down, wl_touch.motion or wl_touch.orientation may be sent within the same wl_touch.frame. A client should treat these events as a single logical touch point update. The order of wl_touch.shape, wl_touch.orientation and wl_touch.motion is not guaranteed. A wl_touch.down event is guaranteed to occur before the first wl_touch.shape event for this touch ID but both events may occur within the same wl_touch.frame. A touchpoint shape is approximated by an ellipse through the major and minor axis length. The major axis length describes the longer diameter of the ellipse, while the minor axis length describes the shorter diameter. Major and minor are orthogonal and both are specified in surface-local coordinates. The center of the ellipse is always at the touchpoint location as reported by wl_touch.down or wl_touch.move. This event is only sent by the compositor if the touch device supports shape reports. The client has to make reasonable assumptions about the shape if it did not receive this event. Sent when a touchpoint has changed its orientation. This event does not occur on its own. It is sent before a wl_touch.frame event and carries the new shape information for any previously reported, or new touch points of that frame. Other events describing the touch point such as wl_touch.down, wl_touch.motion or wl_touch.shape may be sent within the same wl_touch.frame. A client should treat these events as a single logical touch point update. The order of wl_touch.shape, wl_touch.orientation and wl_touch.motion is not guaranteed. A wl_touch.down event is guaranteed to occur before the first wl_touch.orientation event for this touch ID but both events may occur within the same wl_touch.frame. The orientation describes the clockwise angle of a touchpoint's major axis to the positive surface y-axis and is normalized to the -180 to +180 degree range. The granularity of orientation depends on the touch device, some devices only support binary rotation values between 0 and 90 degrees. This event is only sent by the compositor if the touch device supports orientation reports. An output describes part of the compositor geometry. The compositor works in the 'compositor coordinate system' and an output corresponds to a rectangular area in that space that is actually visible. This typically corresponds to a monitor that displays part of the compositor space. This object is published as global during start up, or when a monitor is hotplugged. This enumeration describes how the physical pixels on an output are laid out. This describes the transform that a compositor will apply to a surface to compensate for the rotation or mirroring of an output device. The flipped values correspond to an initial flip around a vertical axis followed by rotation. The purpose is mainly to allow clients to render accordingly and tell the compositor, so that for fullscreen surfaces, the compositor will still be able to scan out directly from client surfaces. The geometry event describes geometric properties of the output. The event is sent when binding to the output object and whenever any of the properties change. These flags describe properties of an output mode. They are used in the flags bitfield of the mode event. The mode event describes an available mode for the output. The event is sent when binding to the output object and there will always be one mode, the current mode. The event is sent again if an output changes mode, for the mode that is now current. In other words, the current mode is always the last mode that was received with the current flag set. The size of a mode is given in physical hardware units of the output device. This is not necessarily the same as the output size in the global compositor space. For instance, the output may be scaled, as described in wl_output.scale, or transformed, as described in wl_output.transform. This event is sent after all other properties have been sent after binding to the output object and after any other property changes done after that. This allows changes to the output properties to be seen as atomic, even if they happen via multiple events. This event contains scaling geometry information that is not in the geometry event. It may be sent after binding the output object or if the output scale changes later. If it is not sent, the client should assume a scale of 1. A scale larger than 1 means that the compositor will automatically scale surface buffers by this amount when rendering. This is used for very high resolution displays where applications rendering at the native resolution would be too small to be legible. It is intended that scaling aware clients track the current output of a surface, and if it is on a scaled output it should use wl_surface.set_buffer_scale with the scale of the output. That way the compositor can avoid scaling the surface, and the client can supply a higher detail image. Using this request a client can tell the server that it is not going to use the output object anymore. A region object describes an area. Region objects are used to describe the opaque and input regions of a surface. Destroy the region. This will invalidate the object ID. Add the specified rectangle to the region. Subtract the specified rectangle from the region. The global interface exposing sub-surface compositing capabilities. A wl_surface, that has sub-surfaces associated, is called the parent surface. Sub-surfaces can be arbitrarily nested and create a tree of sub-surfaces. The root surface in a tree of sub-surfaces is the main surface. The main surface cannot be a sub-surface, because sub-surfaces must always have a parent. A main surface with its sub-surfaces forms a (compound) window. For window management purposes, this set of wl_surface objects is to be considered as a single window, and it should also behave as such. The aim of sub-surfaces is to offload some of the compositing work within a window from clients to the compositor. A prime example is a video player with decorations and video in separate wl_surface objects. This should allow the compositor to pass YUV video buffer processing to dedicated overlay hardware when possible. Informs the server that the client will not be using this protocol object anymore. This does not affect any other objects, wl_subsurface objects included. Create a sub-surface interface for the given surface, and associate it with the given parent surface. This turns a plain wl_surface into a sub-surface. The to-be sub-surface must not already have another role, and it must not have an existing wl_subsurface object. Otherwise a protocol error is raised. An additional interface to a wl_surface object, which has been made a sub-surface. A sub-surface has one parent surface. A sub-surface's size and position are not limited to that of the parent. Particularly, a sub-surface is not automatically clipped to its parent's area. A sub-surface becomes mapped, when a non-NULL wl_buffer is applied and the parent surface is mapped. The order of which one happens first is irrelevant. A sub-surface is hidden if the parent becomes hidden, or if a NULL wl_buffer is applied. These rules apply recursively through the tree of surfaces. The behaviour of a wl_surface.commit request on a sub-surface depends on the sub-surface's mode. The possible modes are synchronized and desynchronized, see methods wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized mode caches the wl_surface state to be applied when the parent's state gets applied, and desynchronized mode applies the pending wl_surface state directly. A sub-surface is initially in the synchronized mode. Sub-surfaces have also other kind of state, which is managed by wl_subsurface requests, as opposed to wl_surface requests. This state includes the sub-surface position relative to the parent surface (wl_subsurface.set_position), and the stacking order of the parent and its sub-surfaces (wl_subsurface.place_above and .place_below). This state is applied when the parent surface's wl_surface state is applied, regardless of the sub-surface's mode. As the exception, set_sync and set_desync are effective immediately. The main surface can be thought to be always in desynchronized mode, since it does not have a parent in the sub-surfaces sense. Even if a sub-surface is in desynchronized mode, it will behave as in synchronized mode, if its parent surface behaves as in synchronized mode. This rule is applied recursively throughout the tree of surfaces. This means, that one can set a sub-surface into synchronized mode, and then assume that all its child and grand-child sub-surfaces are synchronized, too, without explicitly setting them. If the wl_surface associated with the wl_subsurface is destroyed, the wl_subsurface object becomes inert. Note, that destroying either object takes effect immediately. If you need to synchronize the removal of a sub-surface to the parent surface update, unmap the sub-surface first by attaching a NULL wl_buffer, update parent, and then destroy the sub-surface. If the parent wl_surface object is destroyed, the sub-surface is unmapped. The sub-surface interface is removed from the wl_surface object that was turned into a sub-surface with a wl_subcompositor.get_subsurface request. The wl_surface's association to the parent is deleted, and the wl_surface loses its role as a sub-surface. The wl_surface is unmapped. This schedules a sub-surface position change. The sub-surface will be moved so that its origin (top left corner pixel) will be at the location x, y of the parent surface coordinate system. The coordinates are not restricted to the parent surface area. Negative values are allowed. The scheduled coordinates will take effect whenever the state of the parent surface is applied. When this happens depends on whether the parent surface is in synchronized mode or not. See wl_subsurface.set_sync and wl_subsurface.set_desync for details. If more than one set_position request is invoked by the client before the commit of the parent surface, the position of a new request always replaces the scheduled position from any previous request. The initial position is 0, 0. This sub-surface is taken from the stack, and put back just above the reference surface, changing the z-order of the sub-surfaces. The reference surface must be one of the sibling surfaces, or the parent surface. Using any other surface, including this sub-surface, will cause a protocol error. The z-order is double-buffered. Requests are handled in order and applied immediately to a pending state. The final pending state is copied to the active state the next time the state of the parent surface is applied. When this happens depends on whether the parent surface is in synchronized mode or not. See wl_subsurface.set_sync and wl_subsurface.set_desync for details. A new sub-surface is initially added as the top-most in the stack of its siblings and parent. The sub-surface is placed just below the reference surface. See wl_subsurface.place_above. Change the commit behaviour of the sub-surface to synchronized mode, also described as the parent dependent mode. In synchronized mode, wl_surface.commit on a sub-surface will accumulate the committed state in a cache, but the state will not be applied and hence will not change the compositor output. The cached state is applied to the sub-surface immediately after the parent surface's state is applied. This ensures atomic updates of the parent and all its synchronized sub-surfaces. Applying the cached state will invalidate the cache, so further parent surface commits do not (re-)apply old state. See wl_subsurface for the recursive effect of this mode. Change the commit behaviour of the sub-surface to desynchronized mode, also described as independent or freely running mode. In desynchronized mode, wl_surface.commit on a sub-surface will apply the pending state directly, without caching, as happens normally with a wl_surface. Calling wl_surface.commit on the parent surface has no effect on the sub-surface's wl_surface state. This mode allows a sub-surface to be updated on its own. If cached state exists when wl_surface.commit is called in desynchronized mode, the pending state is added to the cached state, and applied as a whole. This invalidates the cache. Note: even if a sub-surface is set to desynchronized, a parent sub-surface may override it to behave as synchronized. For details, see wl_subsurface. If a surface's parent surface behaves as desynchronized, then the cached state is applied on set_desync. wlcs-1.7.0/src/protocol/wlr-foreign-toplevel-management-unstable-v1.xml000066400000000000000000000255731453657357500262350ustar00rootroot00000000000000 Copyright © 2018 Ilia Bozhinov Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. The purpose of this protocol is to enable the creation of taskbars and docks by providing them with a list of opened applications and letting them request certain actions on them, like maximizing, etc. After a client binds the zwlr_foreign_toplevel_manager_v1, each opened toplevel window will be sent via the toplevel event This event is emitted whenever a new toplevel window is created. It is emitted for all toplevels, regardless of the app that has created them. All initial details of the toplevel(title, app_id, states, etc.) will be sent immediately after this event via the corresponding events in zwlr_foreign_toplevel_handle_v1. Indicates the client no longer wishes to receive events for new toplevels. However the compositor may emit further toplevel_created events, until the finished event is emitted. The client must not send any more requests after this one. This event indicates that the compositor is done sending events to the zwlr_foreign_toplevel_manager_v1. The server will destroy the object immediately after sending this request, so it will become invalid and the client should free any resources associated with it. A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel window. Each app may have multiple opened toplevels. Each toplevel has a list of outputs it is visible on, conveyed to the client with the output_enter and output_leave events. This event is emitted whenever the title of the toplevel changes. This event is emitted whenever the app-id of the toplevel changes. This event is emitted whenever the toplevel becomes visible on the given output. A toplevel may be visible on multiple outputs. This event is emitted whenever the toplevel stops being visible on the given output. It is guaranteed that an entered-output event with the same output has been emitted before this event. Requests that the toplevel be maximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be unmaximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be minimized. If the minimized state actually changes, this will be indicated by the state event. Requests that the toplevel be unminimized. If the minimized state actually changes, this will be indicated by the state event. Request that this toplevel be activated on the given seat. There is no guarantee the toplevel will be actually activated. The different states that a toplevel can have. These have the same meaning as the states with the same names defined in xdg-toplevel This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 is created and each time the toplevel state changes, either because of a compositor action or because of a request in this protocol. This event is sent after all changes in the toplevel state have been sent. This allows changes to the zwlr_foreign_toplevel_handle_v1 properties to be seen as atomic, even if they happen via multiple events. Send a request to the toplevel to close itself. The compositor would typically use a shell-specific method to carry out this request, for example by sending the xdg_toplevel.close event. However, this gives no guarantees the toplevel will actually be destroyed. If and when this happens, the zwlr_foreign_toplevel_handle_v1.closed event will be emitted. The rectangle of the surface specified in this request corresponds to the place where the app using this protocol represents the given toplevel. It can be used by the compositor as a hint for some operations, e.g minimizing. The client is however not required to set this, in which case the compositor is free to decide some default value. If the client specifies more than one rectangle, only the last one is considered. The dimensions are given in surface-local coordinates. Setting width=height=0 removes the already-set rectangle. This event means the toplevel has been destroyed. It is guaranteed there won't be any more events for this zwlr_foreign_toplevel_handle_v1. The toplevel itself becomes inert so any requests will be ignored except the destroy request. Destroys the zwlr_foreign_toplevel_handle_v1 object. This request should be called either when the client does not want to use the toplevel anymore or after the closed event to finalize the destruction of the object. Requests that the toplevel be fullscreened on the given output. If the fullscreen state and/or the outputs the toplevel is visible on actually change, this will be indicated by the state and output_enter/leave events. The output parameter is only a hint to the compositor. Also, if output is NULL, the compositor should decide which output the toplevel will be fullscreened on, if at all. Requests that the toplevel be unfullscreened. If the fullscreen state actually changes, this will be indicated by the state event. wlcs-1.7.0/src/protocol/wlr-layer-shell-unstable-v1.xml000066400000000000000000000440361453657357500230560ustar00rootroot00000000000000 Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. After creating a layer_surface object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with a layer_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. 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. 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. Attaching a null buffer to a layer surface unmaps it. Unmapping a layer_surface means that the surface cannot be shown by the compositor until it is explicitly mapped again. The layer_surface returns to the state it had right after layer_shell.get_layer_surface. The client can re-map the surface by performing a commit without any buffer attached, waiting for a configure event and handling it as usual. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two 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. 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. 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. Types of keyboard interaction possible for layer shell surfaces. The rationale for this is twofold: (1) some applications are not interested in keyboard events and not allowing them to be focused can improve the desktop experience; (2) some applications will want to take exclusive keyboard focus. This value indicates that this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. This is the default value, set for newly created layer shell surfaces. This is useful for e.g. desktop widgets that display information or only have interaction with non-keyboard input devices. Request exclusive keyboard focus if this surface is above the shell surface layer. For the top and overlay layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to exclusive. If this layer contains multiple surfaces with keyboard interactivity set to exclusive, the compositor determines the one receiving keyboard events in an implementation- defined manner. In this case, no guarantee is made when this surface will receive keyboard focus (if ever). For the bottom and background layers, the compositor is allowed to use normal focus semantics. This setting is mainly intended for applications that need to ensure they receive all keyboard events, such as a lock screen or a password prompt. This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. The user should be able to unfocus this surface even regardless of the layer it is on. Typically, the compositor will want to use its normal mechanism to manage keyboard focus between layer shell surfaces with this setting and regular toplevels on the desktop layer (e.g. click to focus). Nevertheless, it is possible for a compositor to require a special interaction to focus or unfocus layer shell surfaces (e.g. requiring a click even if focus follows the mouse normally, or providing a keybinding to switch focus between layers). This setting is mainly intended for desktop shell components (e.g. panels) that allow keyboard interaction. Using this option can allow implementing a desktop shell that can be fully usable without the mouse. Set how keyboard events are delivered to this surface. By default, layer shell surfaces do not receive keyboard events; this request can be used to change this. This setting is inherited by child surfaces set by the get_popup request. 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. Keyboard interactivity is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. wlcs-1.7.0/src/protocol/wlr-virtual-pointer-unstable-v1.xml000066400000000000000000000153571453657357500240050ustar00rootroot00000000000000 Copyright © 2019 Josef Gajdusek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to emulate a physical pointer device. The requests are mostly mirror opposites of those specified in wl_pointer. The pointer has moved by a relative amount to the previous request. Values are in the global compositor space. The pointer has moved in an absolute coordinate frame. Value of x can range from 0 to x_extent, value of y can range from 0 to y_extent. A button was pressed or released. Scroll and other axis requests. Indicates the set of events that logically belong together. Source information for scroll and other axis. Stop notification for scroll and other axes. Discrete step information for scroll and other axes. This event allows the client to extend data normally sent using the axis event with discrete value. This object allows clients to create individual virtual pointer objects. Creates a new virtual pointer. The optional seat is a suggestion to the compositor. Creates a new virtual pointer. The seat and the output arguments are optional. If the seat argument is set, the compositor should assign the input device to the requested seat. If the output argument is set, the compositor should map the input device to the requested output. wlcs-1.7.0/src/protocol/xdg-output-unstable-v1.xml000066400000000000000000000225121453657357500221460ustar00rootroot00000000000000 Copyright © 2017 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol aims at describing outputs in a way which is more in line with the concept of an output on desktop oriented systems. Some information are more specific to the concept of an output for a desktop oriented system and may not make sense in other applications, such as IVI systems for example. Typically, the global compositor space on a desktop system is made of a contiguous or overlapping set of rectangular regions. Some of the information provided in this protocol might be identical to their counterparts already available from wl_output, in which case the information provided by this protocol should be preferred to their equivalent in wl_output. The goal is to move the desktop specific concepts (such as output location within the global compositor space, the connector name and types, etc.) out of the core wl_output protocol. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. A global factory interface for xdg_output objects. Using this request a client can tell the server that it is not going to use the xdg_output_manager object anymore. Any objects already created through this instance are not affected. This creates a new xdg_output object for the given wl_output. An xdg_output describes part of the compositor geometry. This typically corresponds to a monitor that displays part of the compositor space. For objects version 3 onwards, after all xdg_output properties have been sent (when the object is created and when properties are updated), a wl_output.done event is sent. This allows changes to the output properties to be seen as atomic, even if they happen via multiple events. Using this request a client can tell the server that it is not going to use the xdg_output object anymore. The position event describes the location of the wl_output within the global compositor space. The logical_position event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the location of the output changes within the global compositor space. The logical_size event describes the size of the output in the global compositor space. For example, a surface without any buffer scale, transformation nor rotation set, with the size matching the logical_size will have the same size as the corresponding output when displayed. Most regular Wayland clients should not pay attention to the logical size and would rather rely on xdg_shell interfaces. Some clients such as Xwayland, however, need this to configure their surfaces in the global compositor space as the compositor may apply a different scale from what is advertised by the output scaling property (to achieve fractional scaling, for example). For example, for a wl_output mode 3840×2160 and a scale factor 2: - A compositor not scaling the surface buffers will advertise a logical size of 3840×2160, - A compositor automatically scaling the surface buffers will advertise a logical size of 1920×1080, - A compositor using a fractional scale of 1.5 will advertise a logical size to 2560×1620. For example, for a wl_output mode 1920×1080 and a 90 degree rotation, the compositor will advertise a logical size of 1080x1920. The logical_size event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the logical size of the output changes, either as a result of a change in the applied scale or because of a change in the corresponding output mode(see wl_output.mode) or transform (see wl_output.transform). This event is sent after all other properties of an xdg_output have been sent. This allows changes to the xdg_output properties to be seen as atomic, even if they happen via multiple events. For objects version 3 onwards, this event is deprecated. Compositors are not required to send it anymore and must send wl_output.done instead. Many compositors will assign names to their outputs, show them to the user, allow them to be configured by name, etc. The client may wish to know this name as well to offer the user similar behaviors. The naming convention is compositor defined, but limited to alphanumeric characters and dashes (-). Each name is unique among all wl_output globals, but if a wl_output global is destroyed the same name may be reused later. The names will also remain consistent across sessions with the same hardware and software configuration. Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc. The name event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output). This event is only sent once per xdg_output, and the name does not change over the lifetime of the wl_output global. Many compositors can produce human-readable descriptions of their outputs. The client may wish to know this description as well, to communicate the user for various purposes. The description is a UTF-8 string with no convention defined for its contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 output via :1'. The description event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the description changes. The description is optional, and may not be sent at all. For objects of version 2 and lower, this event is only sent once per xdg_output, and the description does not change over the lifetime of the wl_output global. wlcs-1.7.0/src/protocol/xdg-shell-unstable-v6.xml000066400000000000000000001303151453657357500217230ustar00rootroot00000000000000 Copyright © 2008-2013 Kristian Høgsberg Copyright © 2013 Rafael Antognolli Copyright © 2013 Jasper St. Pierre Copyright © 2010-2013 Intel Corporation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. xdg_shell allows clients to turn a wl_surface into a "real window" which can be dragged, resized, stacked, and moved around by the user. Everything about this interface is suited towards traditional desktop environments. Destroy this xdg_shell object. Destroying a bound xdg_shell object while there are surfaces still alive created by this xdg_shell object instance is illegal and will result in a protocol error. Create a positioner object. A positioner object is used to position surfaces relative to some parent surface. See the interface description and xdg_surface.get_popup for details. This creates an xdg_surface for the given surface. While xdg_surface itself is not a role, the corresponding surface may only be assigned a role extending xdg_surface, such as xdg_toplevel or xdg_popup. This creates an xdg_surface for the given surface. An xdg_surface is used as basis to define a role to a given surface, such as xdg_toplevel or xdg_popup. It also manages functionality shared between xdg_surface based surface roles. See the documentation of xdg_surface for more details about what an xdg_surface is and how it is used. A client must respond to a ping event with a pong request or the client may be deemed unresponsive. See xdg_shell.ping. The ping event asks the client if it's still alive. Pass the serial specified in the event back to the compositor by sending a "pong" request back with the specified serial. See xdg_shell.ping. Compositors can use this to determine if the client is still alive. It's unspecified what will happen if the client doesn't respond to the ping request, or in what timeframe. Clients should try to respond in a reasonable amount of time. A compositor is free to ping in any way it wants, but a client must always respond to any xdg_shell object it created. The xdg_positioner provides a collection of rules for the placement of a child surface relative to a parent surface. Rules can be defined to ensure the child surface remains within the visible area's borders, and to specify how the child surface changes its position, such as sliding along an axis, or flipping around a rectangle. These positioner-created rules are constrained by the requirement that a child surface must intersect with or be at least partially adjacent to its parent surface. See the various requests for details about possible rules. At the time of the request, the compositor makes a copy of the rules specified by the xdg_positioner. Thus, after the request is complete the xdg_positioner object can be destroyed or reused; further changes to the object will have no effect on previous usages. For an xdg_positioner object to be considered complete, it must have a non-zero size set by set_size, and a non-zero anchor rectangle set by set_anchor_rect. Passing an incomplete xdg_positioner object when positioning a surface raises an error. Notify the compositor that the xdg_positioner will no longer be used. Set the size of the surface that is to be positioned with the positioner object. The size is in surface-local coordinates and corresponds to the window geometry. See xdg_surface.set_window_geometry. If a zero or negative size is set the invalid_input error is raised. Specify the anchor rectangle within the parent surface that the child surface will be placed relative to. The rectangle is relative to the window geometry as defined by xdg_surface.set_window_geometry of the parent surface. The rectangle must be at least 1x1 large. When the xdg_positioner object is used to position a child surface, the anchor rectangle may not extend outside the window geometry of the positioned child's parent surface. If a zero or negative size is set the invalid_input error is raised. Defines a set of edges for the anchor rectangle. These are used to derive an anchor point that the child surface will be positioned relative to. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left position of the rectangle); otherwise, the derived anchor point will be centered on the specified edge, or in the center of the anchor rectangle if no edge is specified. If two parallel anchor edges are specified (e.g. 'left' and 'right'), the invalid_input error is raised. Defines in what direction a surface should be positioned, relative to the anchor point of the parent surface. If two orthogonal gravities are specified (e.g. 'bottom' and 'right'), then the child surface will be placed in the specified direction; otherwise, the child surface will be centered over the anchor point on any axis that had no gravity specified. If two parallel gravities are specified (e.g. 'left' and 'right'), the invalid_input error is raised. The constraint adjustment value define ways the compositor will adjust the position of the surface, if the unadjusted position would result in the surface being partly constrained. Whether a surface is considered 'constrained' is left to the compositor to determine. For example, the surface may be partly outside the compositor's defined 'work area', thus necessitating the child surface's position be adjusted until it is entirely inside the work area. The adjustments can be combined, according to a defined precedence: 1) Flip, 2) Slide, 3) Resize. Don't alter the surface position even if it is constrained on some axis, for example partially outside the edge of a monitor. Slide the surface along the x axis until it is no longer constrained. First try to slide towards the direction of the gravity on the x axis until either the edge in the opposite direction of the gravity is unconstrained or the edge in the direction of the gravity is constrained. Then try to slide towards the opposite direction of the gravity on the x axis until either the edge in the direction of the gravity is unconstrained or the edge in the opposite direction of the gravity is constrained. Slide the surface along the y axis until it is no longer constrained. First try to slide towards the direction of the gravity on the y axis until either the edge in the opposite direction of the gravity is unconstrained or the edge in the direction of the gravity is constrained. Then try to slide towards the opposite direction of the gravity on the y axis until either the edge in the direction of the gravity is unconstrained or the edge in the opposite direction of the gravity is constrained. Invert the anchor and gravity on the x axis if the surface is constrained on the x axis. For example, if the left edge of the surface is constrained, the gravity is 'left' and the anchor is 'left', change the gravity to 'right' and the anchor to 'right'. If the adjusted position also ends up being constrained, the resulting position of the flip_x adjustment will be the one before the adjustment. Invert the anchor and gravity on the y axis if the surface is constrained on the y axis. For example, if the bottom edge of the surface is constrained, the gravity is 'bottom' and the anchor is 'bottom', change the gravity to 'top' and the anchor to 'top'. If the adjusted position also ends up being constrained, the resulting position of the flip_y adjustment will be the one before the adjustment. Resize the surface horizontally so that it is completely unconstrained. Resize the surface vertically so that it is completely unconstrained. Specify how the window should be positioned if the originally intended position caused the surface to be constrained, meaning at least partially outside positioning boundaries set by the compositor. The adjustment is set by constructing a bitmask describing the adjustment to be made when the surface is constrained on that axis. If no bit for one axis is set, the compositor will assume that the child surface should not change its position on that axis when constrained. If more than one bit for one axis is set, the order of how adjustments are applied is specified in the corresponding adjustment descriptions. The default adjustment is none. Specify the surface position offset relative to the position of the anchor on the anchor rectangle and the anchor on the surface. For example if the anchor of the anchor rectangle is at (x, y), the surface has the gravity bottom|right, and the offset is (ox, oy), the calculated surface position will be (x + ox, y + oy). The offset position of the surface is the one used for constraint testing. See set_constraint_adjustment. An example use case is placing a popup menu on top of a user interface element, while aligning the user interface element of the parent surface with some user interface element placed somewhere in the popup surface. An interface that may be implemented by a wl_surface, for implementations that provide a desktop-style user interface. It provides a base set of functionality required to construct user interface elements requiring management by the compositor, such as toplevel windows, menus, etc. The types of functionality are split into xdg_surface roles. Creating an xdg_surface does not set the role for a wl_surface. In order to map an xdg_surface, the client must create a role-specific object using, e.g., get_toplevel, get_popup. The wl_surface for any given xdg_surface can have at most one role, and may not be assigned any role not based on xdg_surface. A role must be assigned before any other requests are made to the xdg_surface object. The client must call wl_surface.commit on the corresponding wl_surface for the xdg_surface state to take effect. Creating an xdg_surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first xdg_surface.configure call must also be treated as errors. For a surface to be mapped by the compositor, the following conditions must be met: (1) the client has assigned a xdg_surface based role to the surface, (2) the client has set and committed the xdg_surface state and the role dependent state to the surface and (3) the client has committed a buffer to the surface. Destroy the xdg_surface object. An xdg_surface must only be destroyed after its role object has been destroyed. This creates an xdg_toplevel object for the given xdg_surface and gives the associated wl_surface the xdg_toplevel role. See the documentation of xdg_toplevel for more details about what an xdg_toplevel is and how it is used. This creates an xdg_popup object for the given xdg_surface and gives the associated wl_surface the xdg_popup role. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. The window geometry of a surface is its "visible bounds" from the user's perspective. Client-side decorations often have invisible portions like drop-shadows which should be ignored for the purposes of aligning, placing and constraining windows. The window geometry is double buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Once the window geometry of the surface is set, it is not possible to unset it, and it will remain the same until set_window_geometry is called again, even if a new subsurface or buffer is attached. If never set, the value is the full bounds of the surface, including any subsurfaces. This updates dynamically on every commit. This unset is meant for extremely simple clients. The arguments are given in the surface-local coordinate space of the wl_surface associated with this xdg_surface. The width and height must be greater than zero. Setting an invalid size will raise an error. When applied, the effective window geometry will be the set window geometry clamped to the bounding rectangle of the combined geometry of the surface of the xdg_surface and the associated subsurfaces. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. For instance, for toplevel surfaces the compositor might use this information to move a surface to the top left only when the client has drawn itself for the maximized or fullscreen state. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. The configure event marks the end of a configure sequence. A configure sequence is a set of one or more events configuring the state of the xdg_surface, including the final xdg_surface.configure event. Where applicable, xdg_surface surface roles will during a configure sequence extend this event as a latched state sent as events before the xdg_surface.configure event. Such events should be considered to make up a set of atomically applied configuration states, where the xdg_surface.configure commits the accumulated state. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. If the client receives multiple configure events before it can respond to one, it is free to discard all but the last event it received. This interface defines an xdg_surface role which allows a surface to, among other things, set window-like properties such as maximize, fullscreen, and minimize, set application-specific metadata like title and id, and well as trigger user interactive operations such as interactive resize and move. Unmap and destroy the window. The window will be effectively hidden from the user's point of view, and all state like maximization, fullscreen, and so on, will be lost. Set the "parent" of this surface. This window should be stacked above a parent. The parent surface must be mapped as long as this surface is mapped. Parent windows should be set on dialogs, toolboxes, or other "auxiliary" surfaces, so that the parent is raised when the dialog is raised. Set a short title for the surface. This string may be used to identify the surface in a task bar, window list, or other user interface elements provided by the compositor. The string must be encoded in UTF-8. Set an application identifier for the surface. The app ID identifies the general class of applications to which the surface belongs. The compositor can use this to group multiple surfaces together, or to determine how to launch a new application. For D-Bus activatable applications, the app ID is used as the D-Bus service name. The compositor shell will try to group application surfaces together by their app ID. As a best practice, it is suggested to select app ID's that match the basename of the application's .desktop file. For example, "org.freedesktop.FooViewer" where the .desktop file is "org.freedesktop.FooViewer.desktop". See the desktop-entry specification [0] for more details on application identifiers and how they relate to well-known D-Bus names and .desktop files. [0] http://standards.freedesktop.org/desktop-entry-spec/ Clients implementing client-side decorations might want to show a context menu when right-clicking on the decorations, giving the user a menu that they can use to maximize or minimize the window. This request asks the compositor to pop up such a window menu at the given position, relative to the local surface coordinates of the parent surface. There are no guarantees as to what menu items the window menu contains. This request must be used in response to some sort of user action like a button press, key press, or touch down event. Start an interactive, user-driven move of the surface. This request must be used in response to some sort of user action like a button press, key press, or touch down event. The passed serial is used to determine the type of interactive move (touch, pointer, etc). The server may ignore move requests depending on the state of the surface (e.g. fullscreen or maximized), or if the passed serial is no longer valid. If triggered, the surface will lose the focus of the device (wl_pointer, wl_touch, etc) used for the move. It is up to the compositor to visually indicate that the move is taking place, such as updating a pointer cursor, during the move. There is no guarantee that the device focus will return when the move is completed. These values are used to indicate which edge of a surface is being dragged in a resize operation. Start a user-driven, interactive resize of the surface. This request must be used in response to some sort of user action like a button press, key press, or touch down event. The passed serial is used to determine the type of interactive resize (touch, pointer, etc). The server may ignore resize requests depending on the state of the surface (e.g. fullscreen or maximized). If triggered, the client will receive configure events with the "resize" state enum value and the expected sizes. See the "resize" enum value for more details about what is required. The client must also acknowledge configure events using "ack_configure". After the resize is completed, the client will receive another "configure" event without the resize state. If triggered, the surface also will lose the focus of the device (wl_pointer, wl_touch, etc) used for the resize. It is up to the compositor to visually indicate that the resize is taking place, such as updating a pointer cursor, during the resize. There is no guarantee that the device focus will return when the resize is completed. The edges parameter specifies how the surface should be resized, and is one of the values of the resize_edge enum. The compositor may use this information to update the surface position for example when dragging the top left corner. The compositor may also use this information to adapt its behavior, e.g. choose an appropriate cursor image. The different state values used on the surface. This is designed for state values like maximized, fullscreen. It is paired with the configure event to ensure that both the client and the compositor setting the state can be synchronized. States set in this way are double-buffered. They will get applied on the next commit. The surface is maximized. The window geometry specified in the configure event must be obeyed by the client. The surface is fullscreen. The window geometry specified in the configure event must be obeyed by the client. The surface is being resized. The window geometry specified in the configure event is a maximum; the client cannot resize beyond it. Clients that have aspect ratio or cell sizing configuration can use a smaller size, however. Client window decorations should be painted as if the window is active. Do not assume this means that the window actually has keyboard or pointer focus. Set a maximum size for the window. The client can specify a maximum size so that the compositor does not try to configure the window beyond this size. The width and height arguments are in window geometry coordinates. See xdg_surface.set_window_geometry. Values set in this way are double-buffered. They will get applied on the next commit. The compositor can use this information to allow or disallow different states like maximize or fullscreen and draw accurate animations. Similarly, a tiling window manager may use this information to place and resize client windows in a more effective way. The client should not rely on the compositor to obey the maximum size. The compositor may decide to ignore the values set by the client and request a larger size. If never set, or a value of zero in the request, means that the client has no expected maximum size in the given dimension. As a result, a client wishing to reset the maximum size to an unspecified state can use zero for width and height in the request. Requesting a maximum size to be smaller than the minimum size of a surface is illegal and will result in a protocol error. The width and height must be greater than or equal to zero. Using strictly negative values for width and height will result in a protocol error. Set a minimum size for the window. The client can specify a minimum size so that the compositor does not try to configure the window below this size. The width and height arguments are in window geometry coordinates. See xdg_surface.set_window_geometry. Values set in this way are double-buffered. They will get applied on the next commit. The compositor can use this information to allow or disallow different states like maximize or fullscreen and draw accurate animations. Similarly, a tiling window manager may use this information to place and resize client windows in a more effective way. The client should not rely on the compositor to obey the minimum size. The compositor may decide to ignore the values set by the client and request a smaller size. If never set, or a value of zero in the request, means that the client has no expected minimum size in the given dimension. As a result, a client wishing to reset the minimum size to an unspecified state can use zero for width and height in the request. Requesting a minimum size to be larger than the maximum size of a surface is illegal and will result in a protocol error. The width and height must be greater than or equal to zero. Using strictly negative values for width and height will result in a protocol error. Maximize the surface. After requesting that the surface should be maximized, the compositor will respond by emitting a configure event with the "maximized" state and the required window geometry. The client should then update its content, drawing it in a maximized state, i.e. without shadow or other decoration outside of the window geometry. The client must also acknowledge the configure when committing the new content (see ack_configure). It is up to the compositor to decide how and where to maximize the surface, for example which output and what region of the screen should be used. If the surface was already maximized, the compositor will still emit a configure event with the "maximized" state. Unmaximize the surface. After requesting that the surface should be unmaximized, the compositor will respond by emitting a configure event without the "maximized" state. If available, the compositor will include the window geometry dimensions the window had prior to being maximized in the configure request. The client must then update its content, drawing it in a regular state, i.e. potentially with shadow, etc. The client must also acknowledge the configure when committing the new content (see ack_configure). It is up to the compositor to position the surface after it was unmaximized; usually the position the surface had before maximizing, if applicable. If the surface was already not maximized, the compositor will still emit a configure event without the "maximized" state. Make the surface fullscreen. You can specify an output that you would prefer to be fullscreen. If this value is NULL, it's up to the compositor to choose which display will be used to map this surface. If the surface doesn't cover the whole output, the compositor will position the surface in the center of the output and compensate with black borders filling the rest of the output. Request that the compositor minimize your surface. There is no way to know if the surface is currently minimized, nor is there any way to unset minimization on this surface. If you are looking to throttle redrawing when minimized, please instead use the wl_surface.frame event for this, as this will also work with live previews on windows in Alt-Tab, Expose or similar compositor features. This configure event asks the client to resize its toplevel surface or to change its state. The configured state should not be applied immediately. See xdg_surface.configure for details. The width and height arguments specify a hint to the window about how its surface should be resized in window geometry coordinates. See set_window_geometry. If the width or height arguments are zero, it means the client should decide its own window dimension. This may happen when the compositor needs to configure the state of the surface but doesn't have any information about any previous or expected dimension. The states listed in the event specify how the width/height arguments should be interpreted, and possibly how it should be drawn. Clients must send an ack_configure in response to this event. See xdg_surface.configure and xdg_surface.ack_configure for details. The close event is sent by the compositor when the user wants the surface to be closed. This should be equivalent to the user clicking the close button in client-side decorations, if your application has any. This is only a request that the user intends to close the window. The client may choose to ignore this request, or show a dialog to ask the user to save their data, etc. A popup surface is a short-lived, temporary surface. It can be used to implement for example menus, popovers, tooltips and other similar user interface concepts. A popup can be made to take an explicit grab. See xdg_popup.grab for details. When the popup is dismissed, a popup_done event will be sent out, and at the same time the surface will be unmapped. See the xdg_popup.popup_done event for details. Explicitly destroying the xdg_popup object will also dismiss the popup and unmap the surface. Clients that want to dismiss the popup when another surface of their own is clicked should dismiss the popup using the destroy request. The parent surface must have either the xdg_toplevel or xdg_popup surface role. A newly created xdg_popup will be stacked on top of all previously created xdg_popup surfaces associated with the same xdg_toplevel. The parent of an xdg_popup must be mapped (see the xdg_surface description) before the xdg_popup itself. The x and y arguments passed when creating the popup object specify where the top left of the popup should be placed, relative to the local surface coordinates of the parent surface. See xdg_surface.get_popup. An xdg_popup must intersect with or be at least partially adjacent to its parent surface. The client must call wl_surface.commit on the corresponding wl_surface for the xdg_popup state to take effect. This destroys the popup. Explicitly destroying the xdg_popup object will also dismiss the popup, and unmap the surface. If this xdg_popup is not the "topmost" popup, a protocol error will be sent. This request makes the created popup take an explicit grab. An explicit grab will be dismissed when the user dismisses the popup, or when the client destroys the xdg_popup. This can be done by the user clicking outside the surface, using the keyboard, or even locking the screen through closing the lid or a timeout. If the compositor denies the grab, the popup will be immediately dismissed. This request must be used in response to some sort of user action like a button press, key press, or touch down event. The serial number of the event should be passed as 'serial'. The parent of a grabbing popup must either be an xdg_toplevel surface or another xdg_popup with an explicit grab. If the parent is another xdg_popup it means that the popups are nested, with this popup now being the topmost popup. Nested popups must be destroyed in the reverse order they were created in, e.g. the only popup you are allowed to destroy at all times is the topmost one. When compositors choose to dismiss a popup, they may dismiss every nested grabbing popup as well. When a compositor dismisses popups, it will follow the same dismissing order as required from the client. The parent of a grabbing popup must either be another xdg_popup with an active explicit grab, or an xdg_popup or xdg_toplevel, if there are no explicit grabs already taken. If the topmost grabbing popup is destroyed, the grab will be returned to the parent of the popup, if that parent previously had an explicit grab. If the parent is a grabbing popup which has already been dismissed, this popup will be immediately dismissed. If the parent is a popup that did not take an explicit grab, an error will be raised. During a popup grab, the client owning the grab will receive pointer and touch events for all their surfaces as normal (similar to an "owner-events" grab in X11 parlance), while the top most grabbing popup will always have keyboard focus. This event asks the popup surface to configure itself given the configuration. The configured state should not be applied immediately. See xdg_surface.configure for details. The x and y arguments represent the position the popup was placed at given the xdg_positioner rule, relative to the upper left corner of the window geometry of the parent surface. The popup_done event is sent out when a popup is dismissed by the compositor. The client should destroy the xdg_popup object at this point. wlcs-1.7.0/src/protocol/xdg-shell.xml000066400000000000000000001642561453657357500175720ustar00rootroot00000000000000 Copyright © 2008-2013 Kristian Høgsberg Copyright © 2013 Rafael Antognolli Copyright © 2013 Jasper St. Pierre Copyright © 2010-2013 Intel Corporation Copyright © 2015-2017 Samsung Electronics Co., Ltd Copyright © 2015-2017 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The xdg_wm_base interface is exposed as a global object enabling clients to turn their wl_surfaces into windows in a desktop environment. It defines the basic functionality needed for clients and the compositor to create windows that can be dragged, resized, maximized, etc, as well as creating transient windows such as popup menus. Destroy this xdg_wm_base object. Destroying a bound xdg_wm_base object while there are surfaces still alive created by this xdg_wm_base object instance is illegal and will result in a defunct_surfaces error. Create a positioner object. A positioner object is used to position surfaces relative to some parent surface. See the interface description and xdg_surface.get_popup for details. This creates an xdg_surface for the given surface. While xdg_surface itself is not a role, the corresponding surface may only be assigned a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is illegal to create an xdg_surface for a wl_surface which already has an assigned role and this will result in a role error. This creates an xdg_surface for the given surface. An xdg_surface is used as basis to define a role to a given surface, such as xdg_toplevel or xdg_popup. It also manages functionality shared between xdg_surface based surface roles. See the documentation of xdg_surface for more details about what an xdg_surface is and how it is used. A client must respond to a ping event with a pong request or the client may be deemed unresponsive. See xdg_wm_base.ping and xdg_wm_base.error.unresponsive. 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.pong. 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. The “unresponsive” error is provided for compositors that wish to disconnect unresponsive clients. A compositor is free to ping in any way it wants, but a client must always respond to any xdg_wm_base object it created. The xdg_positioner provides a collection of rules for the placement of a child surface relative to a parent surface. Rules can be defined to ensure the child surface remains within the visible area's borders, and to specify how the child surface changes its position, such as sliding along an axis, or flipping around a rectangle. These positioner-created rules are constrained by the requirement that a child surface must intersect with or be at least partially adjacent to its parent surface. See the various requests for details about possible rules. At the time of the request, the compositor makes a copy of the rules specified by the xdg_positioner. Thus, after the request is complete the xdg_positioner object can be destroyed or reused; further changes to the object will have no effect on previous usages. For an xdg_positioner object to be considered complete, it must have a non-zero size set by set_size, and a non-zero anchor rectangle set by set_anchor_rect. Passing an incomplete xdg_positioner object when positioning a surface raises an invalid_positioner error. Notify the compositor that the xdg_positioner will no longer be used. Set the size of the surface that is to be positioned with the positioner object. The size is in surface-local coordinates and corresponds to the window geometry. See xdg_surface.set_window_geometry. If a zero or negative size is set the invalid_input error is raised. Specify the anchor rectangle within the parent surface that the child surface will be placed relative to. The rectangle is relative to the window geometry as defined by xdg_surface.set_window_geometry of the parent surface. When the xdg_positioner object is used to position a child surface, the anchor rectangle may not extend outside the window geometry of the positioned child's parent surface. If a negative size is set the invalid_input error is raised. Defines the anchor point for the anchor rectangle. The specified anchor is used derive an anchor point that the child surface will be positioned relative to. If a corner anchor is set (e.g. 'top_left' or 'bottom_right'), the anchor point will be at the specified corner; otherwise, the derived anchor point will be centered on the specified edge, or in the center of the anchor rectangle if no edge is specified. Defines in what direction a surface should be positioned, relative to the anchor point of the parent surface. If a corner gravity is specified (e.g. 'bottom_right' or 'top_left'), then the child surface will be placed towards the specified gravity; otherwise, the child surface will be centered over the anchor point on any axis that had no gravity specified. If the gravity is not in the ‘gravity’ enum, an invalid_input error is raised. The constraint adjustment value define ways the compositor will adjust the position of the surface, if the unadjusted position would result in the surface being partly constrained. Whether a surface is considered 'constrained' is left to the compositor to determine. For example, the surface may be partly outside the compositor's defined 'work area', thus necessitating the child surface's position be adjusted until it is entirely inside the work area. The adjustments can be combined, according to a defined precedence: 1) Flip, 2) Slide, 3) Resize. Don't alter the surface position even if it is constrained on some axis, for example partially outside the edge of an output. Slide the surface along the x axis until it is no longer constrained. First try to slide towards the direction of the gravity on the x axis until either the edge in the opposite direction of the gravity is unconstrained or the edge in the direction of the gravity is constrained. Then try to slide towards the opposite direction of the gravity on the x axis until either the edge in the direction of the gravity is unconstrained or the edge in the opposite direction of the gravity is constrained. Slide the surface along the y axis until it is no longer constrained. First try to slide towards the direction of the gravity on the y axis until either the edge in the opposite direction of the gravity is unconstrained or the edge in the direction of the gravity is constrained. Then try to slide towards the opposite direction of the gravity on the y axis until either the edge in the direction of the gravity is unconstrained or the edge in the opposite direction of the gravity is constrained. Invert the anchor and gravity on the x axis if the surface is constrained on the x axis. For example, if the left edge of the surface is constrained, the gravity is 'left' and the anchor is 'left', change the gravity to 'right' and the anchor to 'right'. If the adjusted position also ends up being constrained, the resulting position of the flip_x adjustment will be the one before the adjustment. Invert the anchor and gravity on the y axis if the surface is constrained on the y axis. For example, if the bottom edge of the surface is constrained, the gravity is 'bottom' and the anchor is 'bottom', change the gravity to 'top' and the anchor to 'top'. The adjusted position is calculated given the original anchor rectangle and offset, but with the new flipped anchor and gravity values. If the adjusted position also ends up being constrained, the resulting position of the flip_y adjustment will be the one before the adjustment. Resize the surface horizontally so that it is completely unconstrained. Resize the surface vertically so that it is completely unconstrained. Specify how the window should be positioned if the originally intended position caused the surface to be constrained, meaning at least partially outside positioning boundaries set by the compositor. The adjustment is set by constructing a bitmask describing the adjustment to be made when the surface is constrained on that axis. If no bit for one axis is set, the compositor will assume that the child surface should not change its position on that axis when constrained. If more than one bit for one axis is set, the order of how adjustments are applied is specified in the corresponding adjustment descriptions. The default adjustment is none. Specify the surface position offset relative to the position of the anchor on the anchor rectangle and the anchor on the surface. For example if the anchor of the anchor rectangle is at (x, y), the surface has the gravity bottom|right, and the offset is (ox, oy), the calculated surface position will be (x + ox, y + oy). The offset position of the surface is the one used for constraint testing. See set_constraint_adjustment. An example use case is placing a popup menu on top of a user interface element, while aligning the user interface element of the parent surface with some user interface element placed somewhere in the popup surface. When set reactive, the surface is reconstrained if the conditions used for constraining changed, e.g. the parent window moved. If the conditions changed and the popup was reconstrained, an xdg_popup.configure event is sent with updated geometry, followed by an xdg_surface.configure event. Set the parent window geometry the compositor should use when positioning the popup. The compositor may use this information to determine the future state the popup should be constrained using. If this doesn't match the dimension of the parent the popup is eventually positioned against, the behavior is undefined. The arguments are given in the surface-local coordinate space. Set the serial of an xdg_surface.configure event this positioner will be used in response to. The compositor may use this information together with set_parent_size to determine what future state the popup should be constrained using. 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. After creating a role-specific object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with an xdg_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. 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, i.e. the client must perform the initial commit again before attaching a buffer. Destroy the xdg_surface object. An xdg_surface must only be destroyed after its role object has been destroyed, otherwise a defunct_role_object error is raised. This creates an xdg_toplevel object for the given xdg_surface and gives the associated wl_surface the xdg_toplevel role. See the documentation of xdg_toplevel for more details about what an xdg_toplevel is and how it is used. This creates an xdg_popup object for the given xdg_surface and gives the associated wl_surface the xdg_popup role. If null is passed as a parent, a parent surface must be specified using some other protocol, before committing the initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. The window geometry of a surface is its "visible bounds" from the user's perspective. Client-side decorations often have invisible portions like drop-shadows which should be ignored for the purposes of aligning, placing and constraining windows. The window geometry is double buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. When maintaining a position, the compositor should treat the (x, y) coordinate of the window geometry as the top left corner of the window. A client changing the (x, y) window geometry coordinate should in general not alter the position of the window. Once the window geometry of the surface is set, it is not possible to unset it, and it will remain the same until set_window_geometry is called again, even if a new subsurface or buffer is attached. If never set, the value is the full bounds of the surface, including any subsurfaces. This updates dynamically on every commit. This unset is meant for extremely simple clients. The arguments are given in the surface-local coordinate space of the wl_surface associated with this xdg_surface. The width and height must be greater than zero. Setting an invalid size will raise an invalid_size error. When applied, the effective window geometry will be the set window geometry clamped to the bounding rectangle of the combined geometry of the surface of the xdg_surface and the associated subsurfaces. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. For instance, for toplevel surfaces the compositor might use this information to move a surface to the top left only when the client has drawn itself for the maximized or fullscreen state. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. Acking a configure event that was never sent raises an invalid_serial error. 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. Sending an ack_configure request consumes the serial number sent with the request, as well as serial numbers sent by all configure events sent on this xdg_surface prior to the configure event referenced by the committed serial. It is an error to issue multiple ack_configure requests referencing a serial from the same configure event, or to issue an ack_configure request referencing a serial from a configure event issued before the event identified by the last ack_configure request for the same xdg_surface. Doing so will raise an invalid_serial error. The configure event marks the end of a configure sequence. A configure sequence is a set of one or more events configuring the state of the xdg_surface, including the final xdg_surface.configure event. Where applicable, xdg_surface surface roles will during a configure sequence extend this event as a latched state sent as events before the xdg_surface.configure event. Such events should be considered to make up a set of atomically applied configuration states, where the xdg_surface.configure commits the accumulated state. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. If the client receives multiple configure events before it can respond to one, it is free to discard all but the last event it received. This interface defines an xdg_surface role which allows a surface to, among other things, set window-like properties such as maximize, fullscreen, and minimize, set application-specific metadata like title and id, and well as trigger user interactive operations such as interactive resize and move. Unmapping an xdg_toplevel means that the surface cannot be shown by the compositor until it is explicitly mapped again. All active operations (e.g., move, resize) are canceled and all attributes (e.g. title, state, stacking, ...) are discarded for an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to the state it had right after xdg_surface.get_toplevel. The client can re-map the toplevel by perfoming a commit without any buffer attached, waiting for a configure event and handling it as usual (see xdg_surface description). Attaching a null buffer to a toplevel unmaps the surface. This request destroys the role surface and unmaps the surface; see "Unmapping" behavior in interface section for details. Set the "parent" of this surface. This surface should be stacked above the parent surface and all other ancestor surfaces. Parent surfaces 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 surface unsets its parent. Setting a null parent for a surface which currently has no parent is a no-op. Only mapped surfaces can have child surfaces. Setting a parent which is not mapped is equivalent to setting a null parent. If a surface becomes unmapped, its children's parent is set to the parent of the now-unmapped surface. If the now-unmapped surface has no parent, its children's parent is unset. If the now-unmapped surface becomes mapped again, its parent-child relationship is not restored. The parent toplevel must not be one of the child toplevel's descendants, and the parent must be different from the child toplevel, otherwise the invalid_parent protocol error is raised. Set a short title for the surface. This string may be used to identify the surface in a task bar, window list, or other user interface elements provided by the compositor. The string must be encoded in UTF-8. Set an application identifier for the surface. The app ID identifies the general class of applications to which the surface belongs. The compositor can use this to group multiple surfaces together, or to determine how to launch a new application. For D-Bus activatable applications, the app ID is used as the D-Bus service name. The compositor shell will try to group application surfaces together by their app ID. As a best practice, it is suggested to select app ID's that match the basename of the application's .desktop file. For example, "org.freedesktop.FooViewer" where the .desktop file is "org.freedesktop.FooViewer.desktop". Like other properties, a set_app_id request can be sent after the xdg_toplevel has been mapped to update the property. 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] https://standards.freedesktop.org/desktop-entry-spec/ Clients implementing client-side decorations might want to show a context menu when right-clicking on the decorations, giving the user a menu that they can use to maximize or minimize the window. This request asks the compositor to pop up such a window menu at the given position, relative to the local surface coordinates of the parent surface. There are no guarantees as to what menu items the window menu contains, or even if a window menu will be drawn at all. This request must be used in response to some sort of user action like a button press, key press, or touch down event. Start an interactive, user-driven move of the surface. This request must be used in response to some sort of user action like a button press, key press, or touch down event. The passed serial is used to determine the type of interactive move (touch, pointer, etc). The server may ignore move requests depending on the state of the surface (e.g. fullscreen or maximized), or if the passed serial is no longer valid. If triggered, the surface will lose the focus of the device (wl_pointer, wl_touch, etc) used for the move. It is up to the compositor to visually indicate that the move is taking place, such as updating a pointer cursor, during the move. There is no guarantee that the device focus will return when the move is completed. These values are used to indicate which edge of a surface is being dragged in a resize operation. Start a user-driven, interactive resize of the surface. This request must be used in response to some sort of user action like a button press, key press, or touch down event. The passed serial is used to determine the type of interactive resize (touch, pointer, etc). The server may ignore resize requests depending on the state of the surface (e.g. fullscreen or maximized). If triggered, the client will receive configure events with the "resize" state enum value and the expected sizes. See the "resize" enum value for more details about what is required. The client must also acknowledge configure events using "ack_configure". After the resize is completed, the client will receive another "configure" event without the resize state. If triggered, the surface also will lose the focus of the device (wl_pointer, wl_touch, etc) used for the resize. It is up to the compositor to visually indicate that the resize is taking place, such as updating a pointer cursor, during the resize. There is no guarantee that the device focus will return when the resize is completed. The edges parameter specifies how the surface should be resized, and is one of the values of the resize_edge enum. Values not matching a variant of the enum will cause a protocol error. The compositor may use this information to update the surface position for example when dragging the top left corner. The compositor may also use this information to adapt its behavior, e.g. choose an appropriate cursor image. The different state values used on the surface. This is designed for state values like maximized, fullscreen. It is paired with the configure event to ensure that both the client and the compositor setting the state can be synchronized. States set in this way are double-buffered. They will get applied on the next commit. The surface is maximized. The window geometry specified in the configure event must be obeyed by the client. The client should draw without shadow or other decoration outside of the window geometry. The surface is fullscreen. The window geometry specified in the configure event is a maximum; the client cannot resize beyond it. For a surface to cover the whole fullscreened area, the geometry dimensions must be obeyed by the client. For more details, see xdg_toplevel.set_fullscreen. The surface is being resized. The window geometry specified in the configure event is a maximum; the client cannot resize beyond it. Clients that have aspect ratio or cell sizing configuration can use a smaller size, however. Client window decorations should be painted as if the window is active. Do not assume this means that the window actually has keyboard or pointer focus. The window is currently in a tiled layout and the left edge is considered to be adjacent to another part of the tiling grid. The window is currently in a tiled layout and the right edge is considered to be adjacent to another part of the tiling grid. The window is currently in a tiled layout and the top edge is considered to be adjacent to another part of the tiling grid. The window is currently in a tiled layout and the bottom edge is considered to be adjacent to another part of the tiling grid. 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 an invalid_size error. The width and height must be greater than or equal to zero. Using strictly negative values for width or height will result in a invalid_size error. Set a minimum size for the window. The client can specify a minimum size so that the compositor does not try to configure the window below this size. The width and height arguments are in window geometry coordinates. See xdg_surface.set_window_geometry. Values set in this way are double-buffered. They will get applied on the next commit. The compositor can use this information to allow or disallow different states like maximize or fullscreen and draw accurate animations. Similarly, a tiling window manager may use this information to place and resize client windows in a more effective way. The client should not rely on the compositor to obey the minimum size. The compositor may decide to ignore the values set by the client and request a smaller size. If never set, or a value of zero in the request, means that the client has no expected minimum size in the given dimension. As a result, a client wishing to reset the minimum size to an unspecified state can use zero for width and height in the request. Requesting a minimum size to be larger than the maximum size of a surface is illegal and will result in an invalid_size 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 invalid_size error. Maximize the surface. After requesting that the surface should be maximized, the compositor will respond by emitting a configure event. Whether this configure actually sets the window maximized is subject to compositor policies. The client must then update its content, drawing in the configured state. 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 may alter the state the surface is returned to when unmaximized unless overridden by the compositor. Unmaximize the surface. After requesting that the surface should be unmaximized, the compositor will respond by emitting a configure event. Whether this actually un-maximizes the window is subject to compositor policies. If available and applicable, 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 the configured state. 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 may alter the state the surface is returned to when unmaximized unless overridden by the compositor. Make the surface fullscreen. After requesting that the surface should be fullscreened, the compositor will respond by emitting a configure event. Whether the client is actually put into a fullscreen state is subject to compositor policies. The client must also acknowledge the configure when committing the new content (see ack_configure). The output passed by the request indicates the client's preference as to which display it should be set fullscreen on. If this value is NULL, it's up to the compositor to choose which display will be used to map this surface. If the surface doesn't cover the whole output, the compositor will position the surface in the center of the output and compensate with with border fill covering the rest of the output. The content of the border fill is undefined, but should be assumed to be in some way that attempts to blend into the surrounding area (e.g. solid black). If the fullscreened surface is not opaque, the compositor must make sure that other screen content not part of the same surface tree (made up of subsurfaces, popups or similarly coupled surfaces) are not visible below the fullscreened surface. Make the surface no longer fullscreen. After requesting that the surface should be unfullscreened, the compositor will respond by emitting a configure event. Whether this actually removes the fullscreen state of the client is subject to compositor policies. Making a surface unfullscreen sets states for the surface based on the following: * the state(s) it may have had before becoming fullscreen * any state(s) decided by the compositor * any state(s) requested by the client while the surface was fullscreen The compositor may include the previous window geometry dimensions in the configure event, if applicable. The client must also acknowledge the configure when committing the new content (see ack_configure). Request that the compositor minimize your surface. There is no way to know if the surface is currently minimized, nor is there any way to unset minimization on this surface. If you are looking to throttle redrawing when minimized, please instead use the wl_surface.frame event for this, as this will also work with live previews on windows in Alt-Tab, Expose or similar compositor features. This configure event asks the client to resize its toplevel surface or to change its state. The configured state should not be applied immediately. See xdg_surface.configure for details. The width and height arguments specify a hint to the window about how its surface should be resized in window geometry coordinates. See set_window_geometry. If the width or height arguments are zero, it means the client should decide its own window dimension. This may happen when the compositor needs to configure the state of the surface but doesn't have any information about any previous or expected dimension. The states listed in the event specify how the width/height arguments should be interpreted, and possibly how it should be drawn. Clients must send an ack_configure in response to this event. See xdg_surface.configure and xdg_surface.ack_configure for details. The close event is sent by the compositor when the user wants the surface to be closed. This should be equivalent to the user clicking the close button in client-side decorations, if your application has any. This is only a request that the user intends to close the window. The client may choose to ignore this request, or show a dialog to ask the user to save their data, etc. The configure_bounds event may be sent prior to a xdg_toplevel.configure event to communicate the bounds a window geometry size is recommended to constrain to. The passed width and height are in surface coordinate space. If width and height are 0, it means bounds is unknown and equivalent to as if no configure_bounds event was ever sent for this surface. The bounds can for example correspond to the size of a monitor excluding any panels or other shell components, so that a surface isn't created in a way that it cannot fit. The bounds may change at any point, and in such a case, a new xdg_toplevel.configure_bounds will be sent, followed by xdg_toplevel.configure and xdg_surface.configure. This event advertises the capabilities supported by the compositor. If a capability isn't supported, clients should hide or disable the UI elements that expose this functionality. For instance, if the compositor doesn't advertise support for minimized toplevels, a button triggering the set_minimized request should not be displayed. The compositor will ignore requests it doesn't support. For instance, a compositor which doesn't advertise support for minimized will ignore set_minimized requests. Compositors must send this event once before the first xdg_surface.configure event. When the capabilities change, compositors must send this event again and then send an xdg_surface.configure event. The configured state should not be applied immediately. See xdg_surface.configure for details. The capabilities are sent as an array of 32-bit unsigned integers in native endianness. 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. 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 client must call wl_surface.commit on the corresponding wl_surface for the xdg_popup state to take effect. This destroys the popup. Explicitly destroying the xdg_popup object will also dismiss the popup, and unmap the surface. If this xdg_popup is not the "topmost" popup, a protocol error will be sent. This request makes the created popup take an explicit grab. An explicit grab will be dismissed when the user dismisses the popup, or when the client destroys the xdg_popup. This can be done by the user clicking outside the surface, using the keyboard, or even locking the screen through closing the lid or a timeout. If the compositor denies the grab, the popup will be immediately dismissed. This request must be used in response to some sort of user action like a button press, key press, or touch down event. The serial number of the event should be passed as 'serial'. The parent of a grabbing popup must either be an xdg_toplevel surface or another xdg_popup with an explicit grab. If the parent is another xdg_popup it means that the popups are nested, with this popup now being the topmost popup. Nested popups must be destroyed in the reverse order they were created in, e.g. the only popup you are allowed to destroy at all times is the topmost one. When compositors choose to dismiss a popup, they may dismiss every nested grabbing popup as well. When a compositor dismisses popups, it will follow the same dismissing order as required from the client. If the topmost grabbing popup is destroyed, the grab will be returned to the parent of the popup, if that parent previously had an explicit grab. If the parent is a grabbing popup which has already been dismissed, this popup will be immediately dismissed. If the parent is a popup that did not take an explicit grab, an error will be raised. During a popup grab, the client owning the grab will receive pointer and touch events for all their surfaces as normal (similar to an "owner-events" grab in X11 parlance), while the top most grabbing popup will always have keyboard focus. This event asks the popup surface to configure itself given the configuration. The configured state should not be applied immediately. See xdg_surface.configure for details. The x and y arguments represent the position the popup was placed at given the xdg_positioner rule, relative to the upper left corner of the window geometry of the parent surface. For version 2 or older, the configure event for an xdg_popup is only ever sent once for the initial configuration. Starting with version 3, it may be sent again if the popup is setup with an xdg_positioner with set_reactive requested, or in response to xdg_popup.reposition requests. 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. Reposition an already-mapped popup. The popup will be placed given the details in the passed xdg_positioner object, and a xdg_popup.repositioned followed by xdg_popup.configure and xdg_surface.configure will be emitted in response. Any parameters set by the previous positioner will be discarded. The passed token will be sent in the corresponding xdg_popup.repositioned event. The new popup position will not take effect until the corresponding configure event is acknowledged by the client. See xdg_popup.repositioned for details. The token itself is opaque, and has no other special meaning. If multiple reposition requests are sent, the compositor may skip all but the last one. If the popup is repositioned in response to a configure event for its parent, the client should send an xdg_positioner.set_parent_configure and possibly an xdg_positioner.set_parent_size request to allow the compositor to properly constrain the popup. If the popup is repositioned together with a parent that is being resized, but not in response to a configure event, the client should send an xdg_positioner.set_parent_size request. The repositioned event is sent as part of a popup configuration sequence, together with xdg_popup.configure and lastly xdg_surface.configure to notify the completion of a reposition request. The repositioned event is to notify about the completion of a xdg_popup.reposition request. The token argument is the token passed in the xdg_popup.reposition request. Immediately after this event is emitted, xdg_popup.configure and xdg_surface.configure will be sent with the updated size and position, as well as a new configure serial. The client should optionally update the content of the popup, but must acknowledge the new popup configuration for the new position to take effect. See xdg_surface.ack_configure for details. wlcs-1.7.0/src/relative_pointer_unstable_v1.cpp000066400000000000000000000037161453657357500216730ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "relative_pointer_unstable_v1.h" #include "in_process_server.h" #include wlcs::ZwpRelativePointerManagerV1::ZwpRelativePointerManagerV1(Client& client) : manager{client.bind_if_supported(AnyVersion)} { } wlcs::ZwpRelativePointerManagerV1::~ZwpRelativePointerManagerV1() = default; wlcs::ZwpRelativePointerManagerV1::operator zwp_relative_pointer_manager_v1*() const { return manager; } wlcs::ZwpRelativePointerV1::ZwpRelativePointerV1(wlcs::ZwpRelativePointerManagerV1& manager, wl_pointer* pointer) : relative_pointer{zwp_relative_pointer_manager_v1_get_relative_pointer(manager, pointer)}, version{zwp_relative_pointer_v1_get_version(relative_pointer)} { zwp_relative_pointer_v1_set_user_data(relative_pointer, this); zwp_relative_pointer_v1_add_listener(relative_pointer, &listener, this); } wlcs::ZwpRelativePointerV1::~ZwpRelativePointerV1() { zwp_relative_pointer_v1_destroy(relative_pointer); } wlcs::ZwpRelativePointerV1::operator zwp_relative_pointer_v1*() const { return relative_pointer; } zwp_relative_pointer_v1_listener const wlcs::ZwpRelativePointerV1::listener = { [](void* self, auto*, auto... args) { static_cast(self)->relative_motion(args...); } }; wlcs-1.7.0/src/shared_library.cpp000066400000000000000000000027211453657357500170020ustar00rootroot00000000000000/* * Copyright © 2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2 or 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "shared_library.h" #include #include #include #include wlcs::SharedLibrary::SharedLibrary(char const* library_name) : so(dlopen(library_name, RTLD_NOW | RTLD_LOCAL)) { if (!so) { BOOST_THROW_EXCEPTION(std::runtime_error(dlerror())); } } wlcs::SharedLibrary::SharedLibrary(std::string const& library_name) : SharedLibrary(library_name.c_str()) {} wlcs::SharedLibrary::~SharedLibrary() noexcept { dlclose(so); } void* wlcs::SharedLibrary::load_symbol(char const* function_name) const { if (void* result = dlsym(so, function_name)) { return result; } else { BOOST_THROW_EXCEPTION(std::runtime_error(dlerror())); } } wlcs-1.7.0/src/surface_builder.cpp000066400000000000000000000125771453657357500171600ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "surface_builder.h" #include "xdg_shell_stable.h" auto wlcs::SurfaceBuilder::all_surface_types() -> std::vector> { return { std::make_shared(), std::make_shared(), std::make_shared(0, 0, 0, 0), std::make_shared(12, 5, 20, 6), std::make_shared(std::make_pair(0, 0)), std::make_shared(std::make_pair(7, 12))}; } auto wlcs::SurfaceBuilder::toplevel_surface_types() -> std::vector> { return { std::make_shared(), std::make_shared(), std::make_shared(0, 0, 0, 0)}; } auto wlcs::SurfaceBuilder::surface_builder_to_string( testing::TestParamInfo> builder) -> std::string { return builder.param->name; } auto wlcs::WlShellSurfaceBuilder::build( wlcs::Server& server, wlcs::Client& client, std::pair position, std::pair size) const -> std::unique_ptr { auto surface = std::make_unique( client.create_wl_shell_surface(size.first, size.second)); server.move_surface_to(*surface, position.first, position.second); return surface; } auto wlcs::XdgV6SurfaceBuilder::build( wlcs::Server& server, wlcs::Client& client, std::pair position, std::pair size) const -> std::unique_ptr { auto surface = std::make_unique( client.create_xdg_shell_v6_surface(size.first, size.second)); server.move_surface_to(*surface, position.first, position.second); return surface; } wlcs::XdgStableSurfaceBuilder::XdgStableSurfaceBuilder(int left_offset, int top_offset, int right_offset, int bottom_offset) : SurfaceBuilder{"xdg_surface_stable" + ((left_offset || top_offset || right_offset || bottom_offset) ? ("_" + std::to_string(left_offset) + "_" + std::to_string(top_offset) + "_" + std::to_string(right_offset) + "_" + std::to_string(bottom_offset)) : "")}, left_offset{left_offset}, top_offset{top_offset}, right_offset{right_offset}, bottom_offset{bottom_offset} { } auto wlcs::XdgStableSurfaceBuilder::build( wlcs::Server& server, wlcs::Client& client, std::pair position, std::pair size) const -> std::unique_ptr { auto surface = std::make_unique(client); auto xdg_surface = std::make_shared(client, *surface); auto xdg_toplevel = std::make_shared(*xdg_surface); // The logical window will be shrunk and moved based on the offset, but the underlying surface will not xdg_surface_set_window_geometry( *xdg_surface, left_offset, top_offset, size.first - left_offset - right_offset, size.second -bottom_offset - top_offset); surface->attach_visible_buffer(size.first, size.second); surface->run_on_destruction( [xdg_surface, xdg_toplevel]() mutable { xdg_toplevel.reset(); xdg_surface.reset(); }); server.move_surface_to(*surface, position.first + left_offset, position.second + top_offset); return surface; } wlcs::SubsurfaceBuilder::SubsurfaceBuilder(std::pair offset) : SurfaceBuilder{ "subsurface_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 position, std::pair size) const -> std::unique_ptr { auto main_surface = std::make_shared( client.create_visible_surface(80, 50)); server.move_surface_to( *main_surface, position.first - offset.first, position.second - offset.second); client.run_on_destruction( [main_surface]() mutable { main_surface.reset(); }); auto subsurface = std::make_unique(wlcs::Subsurface::create_visible( *main_surface, offset.first, offset.second, size.first, size.second)); // if subsurface is sync, tests would have to commit the parent to modify it // this is inconvenient to do in a generic way, so we make it desync wl_subsurface_set_desync(*subsurface); return subsurface; } std::ostream& std::operator<<(std::ostream& out, std::shared_ptr const& param) { return out << param->name; } wlcs-1.7.0/src/termcolor.hpp000066400000000000000000000365461453657357500160370ustar00rootroot00000000000000//! //! termcolor //! ~~~~~~~~~ //! //! termcolor is a header-only c++ library for printing colored messages //! to the terminal. Written just for fun with a help of the Force. //! //! :copyright: (c) 2013 by Ihor Kalnytskyi //! :license: BSD, see LICENSE for details //! #ifndef TERMCOLOR_HPP_ #define TERMCOLOR_HPP_ // the following snippet of code detects the current OS and // defines the appropriate macro that is used to wrap some // platform specific things #if defined(_WIN32) || defined(_WIN64) # define TERMCOLOR_OS_WINDOWS #elif defined(__APPLE__) # define TERMCOLOR_OS_MACOS #elif defined(__unix__) || defined(__unix) # define TERMCOLOR_OS_LINUX #else # error unsupported platform #endif // This headers provides the `isatty()`/`fileno()` functions, // which are used for testing whether a standart stream refers // to the terminal. As for Windows, we also need WinApi funcs // for changing colors attributes of the terminal. #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) # include #elif defined(TERMCOLOR_OS_WINDOWS) # include # include #endif #include #include namespace termcolor { // Forward declaration of the `_internal` namespace. // All comments are below. namespace _internal { // An index to be used to access a private storage of I/O streams. See // colorize / nocolorize I/O manipulators for details. static int colorize_index = std::ios_base::xalloc(); inline FILE* get_standard_stream(const std::ostream& stream); inline bool is_colorized(std::ostream& stream); inline bool is_atty(const std::ostream& stream); #if defined(TERMCOLOR_OS_WINDOWS) inline void win_change_attributes(std::ostream& stream, int foreground, int background=-1); #endif } inline std::ostream& colorize(std::ostream& stream) { stream.iword(_internal::colorize_index) = 1L; return stream; } inline std::ostream& nocolorize(std::ostream& stream) { stream.iword(_internal::colorize_index) = 0L; return stream; } inline std::ostream& reset(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[00m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, -1, -1); #endif } return stream; } inline std::ostream& bold(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[1m"; #elif defined(TERMCOLOR_OS_WINDOWS) #endif } return stream; } inline std::ostream& dark(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[2m"; #elif defined(TERMCOLOR_OS_WINDOWS) #endif } return stream; } inline std::ostream& underline(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[4m"; #elif defined(TERMCOLOR_OS_WINDOWS) #endif } return stream; } inline std::ostream& blink(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[5m"; #elif defined(TERMCOLOR_OS_WINDOWS) #endif } return stream; } inline std::ostream& reverse(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[7m"; #elif defined(TERMCOLOR_OS_WINDOWS) #endif } return stream; } inline std::ostream& concealed(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[8m"; #elif defined(TERMCOLOR_OS_WINDOWS) #endif } return stream; } inline std::ostream& grey(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[30m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, 0 // grey (black) ); #endif } return stream; } inline std::ostream& red(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[31m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, FOREGROUND_RED ); #endif } return stream; } inline std::ostream& green(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[32m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, FOREGROUND_GREEN ); #endif } return stream; } inline std::ostream& yellow(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[33m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, FOREGROUND_GREEN | FOREGROUND_RED ); #endif } return stream; } inline std::ostream& blue(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[34m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, FOREGROUND_BLUE ); #endif } return stream; } inline std::ostream& magenta(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[35m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, FOREGROUND_BLUE | FOREGROUND_RED ); #endif } return stream; } inline std::ostream& cyan(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[36m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, FOREGROUND_BLUE | FOREGROUND_GREEN ); #endif } return stream; } inline std::ostream& white(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[37m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED ); #endif } return stream; } inline std::ostream& on_grey(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[40m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, -1, 0 // grey (black) ); #endif } return stream; } inline std::ostream& on_red(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[41m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, -1, BACKGROUND_RED ); #endif } return stream; } inline std::ostream& on_green(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[42m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, -1, BACKGROUND_GREEN ); #endif } return stream; } inline std::ostream& on_yellow(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[43m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, -1, BACKGROUND_GREEN | BACKGROUND_RED ); #endif } return stream; } inline std::ostream& on_blue(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[44m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, -1, BACKGROUND_BLUE ); #endif } return stream; } inline std::ostream& on_magenta(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[45m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, -1, BACKGROUND_BLUE | BACKGROUND_RED ); #endif } return stream; } inline std::ostream& on_cyan(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[46m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, -1, BACKGROUND_GREEN | BACKGROUND_BLUE ); #endif } return stream; } inline std::ostream& on_white(std::ostream& stream) { if (_internal::is_colorized(stream)) { #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) stream << "\033[47m"; #elif defined(TERMCOLOR_OS_WINDOWS) _internal::win_change_attributes(stream, -1, BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED ); #endif } return stream; } //! Since C++ hasn't a way to hide something in the header from //! the outer access, I have to introduce this namespace which //! is used for internal purpose and should't be access from //! the user code. namespace _internal { //! Since C++ hasn't a true way to extract stream handler //! from the a given `std::ostream` object, I have to write //! this kind of hack. inline FILE* get_standard_stream(const std::ostream& stream) { if (&stream == &std::cout) return stdout; else if ((&stream == &std::cerr) || (&stream == &std::clog)) return stderr; return 0; } // Say whether a given stream should be colorized or not. It's always // true for ATTY streams and may be true for streams marked with // colorize flag. inline bool is_colorized(std::ostream& stream) { return is_atty(stream) || static_cast(stream.iword(colorize_index)); } //! Test whether a given `std::ostream` object refers to //! a terminal. inline bool is_atty(const std::ostream& stream) { FILE* std_stream = get_standard_stream(stream); // Unfortunately, fileno() ends with segmentation fault // if invalid file descriptor is passed. So we need to // handle this case gracefully and assume it's not a tty // if standard stream is not detected, and 0 is returned. if (!std_stream) return false; #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) return ::isatty(fileno(std_stream)); #elif defined(TERMCOLOR_OS_WINDOWS) return ::_isatty(_fileno(std_stream)); #endif } #if defined(TERMCOLOR_OS_WINDOWS) //! Change Windows Terminal colors attribute. If some //! parameter is `-1` then attribute won't changed. inline void win_change_attributes(std::ostream& stream, int foreground, int background) { // yeah, i know.. it's ugly, it's windows. static WORD defaultAttributes = 0; // Windows doesn't have ANSI escape sequences and so we use special // API to change Terminal output color. That means we can't // manipulate colors by means of "std::stringstream" and hence // should do nothing in this case. if (!_internal::is_atty(stream)) return; // get terminal handle HANDLE hTerminal = INVALID_HANDLE_VALUE; if (&stream == &std::cout) hTerminal = GetStdHandle(STD_OUTPUT_HANDLE); else if (&stream == &std::cerr) hTerminal = GetStdHandle(STD_ERROR_HANDLE); // save default terminal attributes if it unsaved if (!defaultAttributes) { CONSOLE_SCREEN_BUFFER_INFO info; if (!GetConsoleScreenBufferInfo(hTerminal, &info)) return; defaultAttributes = info.wAttributes; } // restore all default settings if (foreground == -1 && background == -1) { SetConsoleTextAttribute(hTerminal, defaultAttributes); return; } // get current settings CONSOLE_SCREEN_BUFFER_INFO info; if (!GetConsoleScreenBufferInfo(hTerminal, &info)) return; if (foreground != -1) { info.wAttributes &= ~(info.wAttributes & 0x0F); info.wAttributes |= static_cast(foreground); } if (background != -1) { info.wAttributes &= ~(info.wAttributes & 0xF0); info.wAttributes |= static_cast(background); } SetConsoleTextAttribute(hTerminal, info.wAttributes); } #endif // TERMCOLOR_OS_WINDOWS } // namespace _internal } // namespace termcolor #undef TERMCOLOR_OS_WINDOWS #undef TERMCOLOR_OS_MACOS #undef TERMCOLOR_OS_LINUX #endif // TERMCOLOR_HPP_ wlcs-1.7.0/src/test_c_compile.c000066400000000000000000000002441453657357500164370ustar00rootroot00000000000000#include "wlcs/display_server.h" #include "wlcs/pointer.h" #include "wlcs/touch.h" WlcsServerIntegration server_integration; WlcsPointer pointer; WlcsTouch touch; wlcs-1.7.0/src/thread_proxy.h000066400000000000000000000372401453657357500161710ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #ifndef WLCS_THREAD_PROXY_H_ #define WLCS_THREAD_PROXY_H_ #include #include #include #include #include #include #include #include /* * ThreadProxy: Take a Callable and run it on the Wayland event loop. * * There are two main parts here: some template metaprogramming, and * the ThreadProxy class. * * ThreadProxy maintains the infrastructure for taking Callables * and invoking them on a Wayland event loop. The meat here is * in register_op(), which takes a Callable and returns a * new Callable that, when invoked, will result in the original * Callable being called on the Wayland event loop. * * The mechanism used for invoking on the Wayland event loop is a * socket: arguments are serialised to the socket, and then deserialised * when processed by the event loop. * * The template metaprogramming is mostly for manipulating parameter * packs: serialising them to and from linear buffers (so that they * can be passed across a socket), and invoking a Callable with a * tuple of args, and deducing the arguments and return value of * a Callable * */ namespace { /* * Deduce the argument types and return values of a Callable. * * Notably this only works for Callables with exactly one implementation * of operator(), otherwise template deduction will fail as it is unable * to select which operator() to operate on. * * This isn't an issue for what we're using this with, which is primarly * lambdas. */ template struct callable_traits : public callable_traits {}; template struct callable_traits { using return_type = Returns; using args = typename std::tuple; }; /* * call_helper and call are downmarket versions of C++17's std::invoke() * * We don't yet require C++17, so downmarket it is! */ template Returns call_helper(Callable const& func, std::tuple const& args, std::index_sequence) { return func(std::get(args)...); } template Returns call(Callable const& func, std::tuple const& args) { return call_helper(func, args, std::index_sequence_for{}); } /* * tuple_from_buffer base case: To unpack a buffer containing no values, * return a std::tuple<>. */ template< typename... Args, typename std::enable_if::type = 0> std::tuple tuple_from_buffer(char*, std::tuple const*) { return std::tuple{}; } /* * Unpack a linear buffer into a std::tuple of its component types. */ template std::tuple tuple_from_buffer(char* buffer, std::tuple const*) { // We need to memcpy out of the buffer as there's no guarantee data in the buffer has correct // alignment for this type. Head aligned_arg; ::memcpy(&aligned_arg, buffer, sizeof(Head)); std::tuple const* type_deducer = nullptr; return std::tuple_cat(std::make_tuple(aligned_arg), tuple_from_buffer(buffer + sizeof(Head), type_deducer)); } std::array setup_socketpair() { std::array socket_fds; if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, socket_fds.data()) < 0) { BOOST_THROW_EXCEPTION(( std::system_error{ errno, std::system_category(), "Failed to create Wayland thread communication socket"})); } return socket_fds; } constexpr ssize_t arguments_size() { return 0; } template constexpr ssize_t arguments_size(T const& head, Remainder const&... tail) { return sizeof(head) + arguments_size(tail...); } /* * pack_buffer base case: To pack no arguments into a buffer, do nothing. */ void pack_buffer(void*) { } /* * Pack a any number of (TriviallyCopyable) arguments into a linear buffer. */ template void pack_buffer(char* buffer, T&& arg, Remainder&& ...args) { memcpy(buffer, &arg, sizeof(arg)); pack_buffer(buffer + sizeof(arg), std::forward(args)...); } /* * A bit more C++17 backporting... * * This bit of metaprogramming is in support of statically-asserting that * all the types passed through the proxy are TriviallyCopyable. */ template struct conjunction : std::true_type {}; template struct conjunction : Cond {}; template struct conjunction : std::conditional_t, Head> {}; template constexpr bool all_args_are_trivially_copyable(std::tuple const*) { return conjunction...>::value; } /* * ThreadProxy: a mechanism to take a functor from any thread, and turn it into * a functor that runs on the Wayland event loop. * * Theory of operation: * The inter-thread channel used is a SOCK_SEQPACKET socket. * * register_op() takes an (almost) arbitrary Callable, wraps it in a * std::function<> that recieves a pointer to a buffer of arguments, * unpacks the buffer into a std::tuple, then invokes the original * Callable, and adds this function to the handler array. * * register_op() then *returns* a function with the same signature that * copies the array index of the relevant handler into a falt buffer, * marshalls the arguments passed in into the buffer and then pushes that buffer * into the socket. * * On the Wayland event loop side, we add a fd event_source to the event_loop * which reads from the Wayland end of the socket, extracts the index into the * handler array, then calls the handler with the rest of the message buffer. */ class ThreadProxy : public std::enable_shared_from_this { private: template void send_message(uint32_t opcode, Args&& ...args) const { char buffer[max_message_size]; ssize_t const message_size = sizeof(uint32_t) + arguments_size(args...); *reinterpret_cast(buffer) = opcode; pack_buffer(buffer + sizeof(uint32_t), std::forward(args)...); auto const written = send(fds[Fd::Client], buffer, message_size, MSG_DONTWAIT); if (written < message_size) { if (written < 0) { BOOST_THROW_EXCEPTION(( std::system_error{ errno, std::system_category(), "Failed to send message to Wayland thread"})); } BOOST_THROW_EXCEPTION(( std::runtime_error{ "Failed to send whole message to Wayland thread"})); } } template T wait_for_reply() const { T buffer; // Wait for it to be processed auto const read = recv(fds[Fd::Client], &buffer, sizeof(buffer), 0); if (read < static_cast(sizeof(buffer))) { if (read < 0) { BOOST_THROW_EXCEPTION(( std::system_error{ errno, std::system_category(), "Failed to receive reply from Wayland thread"})); } BOOST_THROW_EXCEPTION(( std::runtime_error{ "Received short reply from Wayland thread"})); } return buffer; } template auto make_send_functor(uint32_t opcode, std::tuple const*) const { return [me = this->shared_from_this(), opcode](Args&& ...args) { // We technically don't need to serialise the send/receive, but // it's a bit easier if only one request is in flight at once. std::lock_guard lock{me->message_serialiser}; me->send_message(opcode, std::forward(args)...); me->wait_for_reply(); }; } template auto make_send_functor(uint32_t opcode, std::tuple const*) const { return [me = this->shared_from_this(), opcode](Args&& ...args) { // We technically don't need to serialise the send/receive, but // it's a bit easier if only one request is in flight at once. std::lock_guard lock{me->message_serialiser}; me->send_message(opcode, std::forward(args)...); return me->wait_for_reply(); }; } template< typename Callable, typename... Args, typename std::enable_if_t< std::is_same< typename callable_traits::type>::return_type, void>::value, int> = 0> auto make_recv_functor(Callable handler, std::tuple const*) { return [this, handler = std::move(handler)](void* data) { std::tuple const* type_resolver = nullptr; auto const args = tuple_from_buffer(static_cast(data), type_resolver); call(handler, args); // void functions send a dummy char, to notify the other end that // the call has completed. char const dummy{0}; send(fds[Fd::Wayland], &dummy, sizeof(dummy), 0); }; } template< typename Callable, typename... Args, typename std::enable_if_t< !std::is_same< typename callable_traits::type>::return_type, void>::value, int> = 0> auto make_recv_functor(Callable handler, std::tuple const*) { return [this, handler = std::move(handler)](void* data) { using traits = callable_traits::type>; std::tuple const* type_resolver = nullptr; auto const args = tuple_from_buffer(static_cast(data), type_resolver); auto const val = call(handler, args); send(this->fds[Fd::Wayland], &val, sizeof(val), 0); }; } public: template< typename Callable, typename std::enable_if_t< std::is_same< typename callable_traits::type>::return_type, void>::value, int> = 0> auto register_op(Callable handler) { using traits = callable_traits::type>; // This is technically too restrictive; std::tuple might have a size greater // than the sum of its parts. static_assert( sizeof(typename traits::args) < max_arguments_size, "Attempt to call function with too many arguments; bump max_message_size"); // Get template deduction to work by passing an (unused) argument of type // std::tuple*; we can't access Args... ourselves here. constexpr typename traits::args const* type_resolver = nullptr; static_assert( all_args_are_trivially_copyable(type_resolver), "All arguments of register_op must be TriviallyCopyable as they must support memcpy"); auto const next_opcode = handlers.size(); handlers.emplace_back(make_recv_functor(std::move(handler), type_resolver)); return make_send_functor(next_opcode, type_resolver); } template< typename Callable, typename std::enable_if_t< !std::is_same< typename callable_traits::type>::return_type, void>::value, int> = 0> auto register_op(Callable handler) { using traits = callable_traits::type>; static_assert( sizeof(typename traits::args) < max_arguments_size, "Attempt to call function with too many arguments; bump max_message_size"); static_assert( sizeof(typename traits::return_type) < max_message_size, "Attempt to call function with too large return value; bump max_message_size"); // Get template deduction to work by passing an (unused) argument of type // std::tuple; we can't access Args... ourselves here. constexpr typename traits::args const* type_resolver = nullptr; static_assert( all_args_are_trivially_copyable(type_resolver), "All arguments of register_op must be TriviallyCopyable as they must support memcpy"); auto const next_opcode = handlers.size(); handlers.emplace_back(make_recv_functor(std::move(handler), type_resolver)); return make_send_functor(next_opcode, type_resolver); } explicit ThreadProxy(struct wl_event_loop* event_loop) : fds{setup_socketpair()}, source{ wl_event_loop_add_fd( event_loop, fds[Fd::Wayland], WL_EVENT_READABLE, &ThreadProxy::socket_readable, this)}, // We have to manually implement the destructor() thunk rather than using register_op because // the send_functor returned by register_op takes shared reference to this, care of // shared_from_this(). But during construction `this` is not a shared_ptr, so shared_from_this() // isn't usable. destructor{ [this]() { send_message(0); }} { // We must run wl_event_source_remove() on the Wayland mainloop, too. handlers.emplace_back( [this](void*) { wl_event_source_remove(source); }); } ~ThreadProxy() { destructor(); close(fds[0]); close(fds[1]); } private: static int socket_readable(int fd, uint32_t /*mask*/, void* data) noexcept { auto const& me = *static_cast(data); char buffer[max_message_size]; recv(fd, buffer, sizeof(buffer), 0); auto const opcode = *reinterpret_cast(buffer); me.handlers[opcode](buffer + sizeof(uint32_t)); return 0; } static constexpr ssize_t max_arguments_size{1024}; static constexpr ssize_t max_message_size = max_arguments_size + sizeof(uint32_t); enum Fd { Wayland = 0, Client }; std::array const fds; struct wl_event_source* const source; std::mutex mutable message_serialiser; std::vector> handlers; std::function const destructor; }; } #endif //WLCS_THREAD_PROXY_H_ wlcs-1.7.0/src/version_specifier.cpp000066400000000000000000000047561453657357500175400ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #include "version_specifier.h" #include "boost/throw_exception.hpp" #include wlcs::ExactlyVersion::ExactlyVersion(uint32_t version) noexcept : version{version} { } auto wlcs::ExactlyVersion::select_version( uint32_t max_available_version, uint32_t max_supported_version) const -> std::optional { if (version > max_supported_version) { BOOST_THROW_EXCEPTION(std::logic_error( "Required version " + std::to_string(version) + " is higher than the highest version supported by WLCS (" + std::to_string(max_supported_version) + ")")); } else if (version > max_available_version) { return {}; } else { 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_available_version, uint32_t max_supported_version) const -> std::optional { if (version > max_supported_version) { BOOST_THROW_EXCEPTION(std::logic_error( "Required version " + std::to_string(version) + " is higher than the highest version supported by WLCS (" + std::to_string(max_supported_version) + ")")); } else if (version > max_available_version) { return {}; } else { return {std::min(max_available_version, max_supported_version)}; } } auto wlcs::AtLeastVersion::describe() const -> std::string { return std::string{">= "} + std::to_string(version); } wlcs::VersionSpecifier const& wlcs::AnyVersion = wlcs::AtLeastVersion{1}; wlcs-1.7.0/src/xdg_output_v1.cpp000066400000000000000000000077441453657357500166320ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "xdg_output_v1.h" #include "wl_handle.h" #include "version_specifier.h" #include struct wlcs::XdgOutputManagerV1::Impl { Impl(Client& client) : client{client}, manager{client.bind_if_supported(AnyVersion)} { } Client& client; WlHandle const manager; }; wlcs::XdgOutputManagerV1::XdgOutputManagerV1(Client& client) : impl{std::make_unique(client)} { } wlcs::XdgOutputManagerV1::~XdgOutputManagerV1() = default; wlcs::XdgOutputManagerV1::operator zxdg_output_manager_v1*() const { return impl->manager; } auto wlcs::XdgOutputManagerV1::client() const -> Client& { return impl->client; } struct wlcs::XdgOutputV1::Impl { Impl(XdgOutputManagerV1& manager, size_t output_index) : output{zxdg_output_manager_v1_get_xdg_output( manager, manager.client().output_state(output_index).output)}, version{zxdg_output_v1_get_version(output)} { zxdg_output_v1_add_listener(output, &Impl::listener, this); } ~Impl() { zxdg_output_v1_destroy(output); } zxdg_output_v1* const output; uint32_t const version; bool dirty{false}; State _state; static zxdg_output_v1_listener const listener; }; zxdg_output_v1_listener const wlcs::XdgOutputV1::Impl::listener = { [] /* logical_position */ (void *data, zxdg_output_v1 *, int32_t x, int32_t y) { auto const impl = static_cast(data); impl->_state.logical_position = std::make_pair(x, y); impl->dirty = true; }, [] /* logical_size */ (void *data, zxdg_output_v1 *, int32_t width, int32_t height) { auto const impl = static_cast(data); impl->_state.logical_size = std::make_pair(width, height); impl->dirty = true; }, [] /* done */ (void *data, zxdg_output_v1 *) { auto const impl = static_cast(data); if (impl->version < 3) impl->dirty = false; }, [] /* name */ (void *data, zxdg_output_v1 *, const char *name) { auto const impl = static_cast(data); std::string str{name}; impl->_state.name = str; impl->dirty = true; }, [] /* description */ (void *data, zxdg_output_v1 *, const char *description) { auto const impl = static_cast(data); impl->_state.description = std::string{description}; impl->dirty = true; }, }; wlcs::XdgOutputV1::XdgOutputV1(XdgOutputManagerV1& manager, size_t output_index) : impl{std::make_shared(manager, output_index)} { if (impl->version >= 3) { manager.client().add_output_done_notifier(output_index, [weak = std::weak_ptr(impl)]() { if (auto const impl = weak.lock()) { impl->dirty = false; } }); } } wlcs::XdgOutputV1::operator zxdg_output_v1*() const { return impl->output; } auto wlcs::XdgOutputV1::state() -> State const& { if (impl->dirty) { std::string done_event = (impl->version >= 3 ? "wl_output.done" : "zxdg_output_v1.done"); BOOST_THROW_EXCEPTION(std::logic_error("State change was not finished with a " + done_event + " event")); } return impl->_state; } wlcs-1.7.0/src/xdg_shell_stable.cpp000066400000000000000000000122111453657357500173060ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "xdg_shell_stable.h" using namespace testing; // XdgSurfaceStable wlcs::XdgSurfaceStable::XdgSurfaceStable(wlcs::Client& client, wlcs::Surface& surface) { EXPECT_CALL(*this, configure).Times(AnyNumber()); 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 = { [](void* data, auto, auto... args) { static_cast(data)->configure(args...); }}; xdg_surface_add_listener(shell_surface, &listener, this); } wlcs::XdgSurfaceStable::~XdgSurfaceStable() { xdg_surface_destroy(shell_surface); } // XdgToplevelStable wlcs::XdgToplevelStable::State::State(int32_t width, int32_t height, struct wl_array *states) : width{width}, height{height}, maximized{false}, fullscreen{false}, resizing{false}, activated{false} { if (!states) return; xdg_toplevel_state* item; for (item = static_cast(states->data); (char*)item < static_cast(states->data) + states->size; item++) { switch (*item) { case XDG_TOPLEVEL_STATE_MAXIMIZED: maximized = true; break; case XDG_TOPLEVEL_STATE_FULLSCREEN: fullscreen = true; break; case XDG_TOPLEVEL_STATE_RESIZING: resizing = true; break; case XDG_TOPLEVEL_STATE_ACTIVATED: activated = true; case XDG_TOPLEVEL_STATE_TILED_LEFT: case XDG_TOPLEVEL_STATE_TILED_RIGHT: case XDG_TOPLEVEL_STATE_TILED_BOTTOM: case XDG_TOPLEVEL_STATE_TILED_TOP: break; } } } wlcs::XdgToplevelStable::XdgToplevelStable(XdgSurfaceStable& shell_surface_) : shell_surface{&shell_surface_} { EXPECT_CALL(*this, configure).Times(AnyNumber()); EXPECT_CALL(*this, close).Times(AnyNumber()); EXPECT_CALL(*this, configure_bounds).Times(AnyNumber()); EXPECT_CALL(*this, wm_capabilities).Times(AnyNumber()); toplevel = xdg_surface_get_toplevel(*shell_surface); static struct xdg_toplevel_listener const listener = { [](void* data, auto, auto... args) { static_cast(data)->configure(args...); }, [](void* data, auto, auto... args) { static_cast(data)->close(args...); }, [](void* data, auto, auto... args) { static_cast(data)->configure_bounds(args...); }, [](void* data, auto, auto... args) { static_cast(data)->wm_capabilities(args...); }}; 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); } auto wlcs::XdgPositionerStable::setup_default(std::pair size) -> XdgPositionerStable& { xdg_positioner_set_size(positioner, size.first, size.second); xdg_positioner_set_anchor_rect(positioner, 0, 0, 1, 1); xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); xdg_positioner_set_gravity(positioner, XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT); return *this; } wlcs::XdgPopupStable::XdgPopupStable( XdgSurfaceStable& shell_surface_, std::optional parent, XdgPositionerStable& positioner) : shell_surface{&shell_surface_}, popup{xdg_surface_get_popup( *shell_surface, parent ? *parent.value() : (xdg_surface*)nullptr, positioner)} { EXPECT_CALL(*this, configure).Times(AnyNumber()); EXPECT_CALL(*this, done).Times(AnyNumber()); EXPECT_CALL(*this, repositioned).Times(AnyNumber()); static struct xdg_popup_listener const listener = { [](void* data, auto, auto... args) { static_cast(data)->configure(args...); }, [](void* data, auto, auto... args) { static_cast(data)->done(args...); }, [](void* data, auto, auto... args) { static_cast(data)->repositioned(args...); }}; xdg_popup_add_listener(popup, &listener, this); } wlcs::XdgPopupStable::~XdgPopupStable() { xdg_popup_destroy(popup); } wlcs-1.7.0/src/xdg_shell_v6.cpp000066400000000000000000000076431453657357500164040ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "xdg_shell_v6.h" using namespace testing; // XdgSurfaceV6 wlcs::XdgSurfaceV6::XdgSurfaceV6(wlcs::Client& client, wlcs::Surface& surface) { EXPECT_CALL(*this, configure).Times(AnyNumber()); 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 = { [](void* data, auto, auto... args) { static_cast(data)->configure(args...); }}; zxdg_surface_v6_add_listener(shell_surface, &listener, this); } wlcs::XdgSurfaceV6::~XdgSurfaceV6() { zxdg_surface_v6_destroy(shell_surface); } // XdgToplevelV6 wlcs::XdgToplevelV6::State::State(int32_t width, int32_t height, struct wl_array *states) : width{width}, height{height}, maximized{false}, fullscreen{false}, resizing{false}, activated{false} { if (!states) return; zxdg_toplevel_v6_state* item; for (item = static_cast(states->data); (char*)item < static_cast(states->data) + states->size; item++) { switch (*item) { case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED: maximized = true; break; case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN: fullscreen = true; break; case ZXDG_TOPLEVEL_V6_STATE_RESIZING: resizing = true; break; case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED: activated = true; break; } } } wlcs::XdgToplevelV6::XdgToplevelV6(XdgSurfaceV6& shell_surface_) : shell_surface{&shell_surface_} { EXPECT_CALL(*this, configure).Times(AnyNumber()); EXPECT_CALL(*this, close).Times(AnyNumber()); toplevel = zxdg_surface_v6_get_toplevel(*shell_surface); static struct zxdg_toplevel_v6_listener const listener = { [](void* data, auto, auto... args) { static_cast(data)->configure(args...); }, [](void* data, auto, auto... args) { static_cast(data)->close(args...); }}; 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)} { EXPECT_CALL(*this, configure).Times(AnyNumber()); EXPECT_CALL(*this, done).Times(AnyNumber()); static struct zxdg_popup_v6_listener const listener = { [](void* data, auto, auto... args) { static_cast(data)->configure(args...); }, [](void* data, auto, auto... args) { static_cast(data)->done(args...); }}; zxdg_popup_v6_add_listener(popup, &listener, this); } wlcs::XdgPopupV6::~XdgPopupV6() { zxdg_popup_v6_destroy(popup); } wlcs-1.7.0/src/xfail_supporting_test_listener.cpp000066400000000000000000000153551453657357500223600ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #include "xfail_supporting_test_listener.h" #include #include #include "termcolor.hpp" testing::XFailSupportingTestListenerWrapper::XFailSupportingTestListenerWrapper(std::unique_ptr&& wrapped) : delegate{std::move(wrapped)} { } void testing::XFailSupportingTestListenerWrapper::OnTestProgramStart(testing::UnitTest const& unit_test) { delegate->OnTestProgramStart(unit_test); } void testing::XFailSupportingTestListenerWrapper::OnTestIterationStart(testing::UnitTest const& unit_test, int iteration) { failed_test_names.clear(); skipped_test_names.clear(); delegate->OnTestIterationStart(unit_test, iteration); } void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsSetUpStart(testing::UnitTest const& unit_test) { delegate->OnEnvironmentsSetUpStart(unit_test); } void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsSetUpEnd(testing::UnitTest const& unit_test) { delegate->OnEnvironmentsSetUpEnd(unit_test); } void testing::XFailSupportingTestListenerWrapper::OnTestCaseStart(testing::TestCase const& test_case) { delegate->OnTestCaseStart(test_case); } void testing::XFailSupportingTestListenerWrapper::OnTestStart(testing::TestInfo const& test_info) { current_test_info = &test_info; current_test_start = std::chrono::steady_clock::now(); delegate->OnTestStart(test_info); } void testing::XFailSupportingTestListenerWrapper::OnTestPartResult(testing::TestPartResult const& test_part_result) { if (test_part_result.failed()) { // Search for expected-failure property for (int i= 0; i < current_test_info->result()->test_property_count() ; ++i) { auto prop = current_test_info->result()->GetTestProperty(i); if (strcmp(prop.key(), "wlcs-skip-test") == 0) { if (!current_skip_reasons) { current_skip_reasons = std::vector{}; } current_skip_reasons->push_back(prop.value()); } } if (current_skip_reasons) { skipped_test_names.insert(std::string{current_test_info->test_case_name()} + "." + current_test_info->name()); return; } failed_test_names.insert(std::string{current_test_info->test_case_name()} + "." + current_test_info->name()); } delegate->OnTestPartResult(test_part_result); } void testing::XFailSupportingTestListenerWrapper::OnTestEnd(testing::TestInfo const& test_info) { if (current_skip_reasons) { for (auto const& reason : *current_skip_reasons) { std::cout << termcolor::yellow << "[ ]" << termcolor::reset << " " << reason << std::endl; } auto elapsed_time = std::chrono::steady_clock::now() - current_test_start; std::cout << termcolor::yellow << "[ SKIP ]" << termcolor::reset << " " << test_info.test_case_name() << "." << test_info.name() << " (" << std::chrono::duration_cast(elapsed_time).count() << "ms)" << std::endl; } else { delegate->OnTestEnd(test_info); } current_skip_reasons = {}; } void testing::XFailSupportingTestListenerWrapper::OnTestCaseEnd(testing::TestCase const& test_case) { delegate->OnTestCaseEnd(test_case); } void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsTearDownStart(testing::UnitTest const& unit_test) { delegate->OnEnvironmentsTearDownStart(unit_test); } void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsTearDownEnd(testing::UnitTest const& unit_test) { delegate->OnEnvironmentsTearDownEnd(unit_test); } namespace { std::string singular_or_plural(char const* base, int count) { if (count == 1) { return base; } return std::string{base} + "s"; } } void testing::XFailSupportingTestListenerWrapper::OnTestIterationEnd(testing::UnitTest const& unit_test, int /*iteration*/) { std::cout << termcolor::green << "[==========] " << termcolor::reset << unit_test.test_to_run_count() << " tests from " << unit_test.test_case_to_run_count() << " test cases run. (" << unit_test.elapsed_time() << "ms total elapsed)" << std::endl; std::cout << termcolor::green << "[ PASSED ]" << termcolor::reset << " " << unit_test.successful_test_count() << singular_or_plural(" test", unit_test.successful_test_count()) << std::endl; auto const skipped_tests = skipped_test_names.size(); if (skipped_tests > 0) { std::cout << termcolor::yellow << "[ SKIPPED ] " << termcolor::reset << skipped_tests << singular_or_plural(" test", skipped_tests) << " skipped:" << std::endl; for (auto const& name : skipped_test_names) { std::cout << termcolor::yellow << "[ SKIPPED ] " << termcolor::reset << name << std::endl; } } auto const failed_tests = failed_test_names.size(); if (failed_tests > 0) { /* Mark the test run as a failure; if multiple iterations are run * only one might fail, making failed_test_names() an unreliable * indicator. */ failed_ = true; std::cout << termcolor::red << "[ FAILED ] " << termcolor::reset << failed_tests << singular_or_plural(" test", failed_tests) << " failed:" << std::endl; for (auto const& name : failed_test_names) { std::cout << termcolor::red << "[ FAILED ] " << termcolor::reset << name << std::endl; } } } void testing::XFailSupportingTestListenerWrapper::OnTestProgramEnd(testing::UnitTest const& unit_test) { delegate->OnTestProgramEnd(unit_test); } bool testing::XFailSupportingTestListenerWrapper::failed() const { return failed_; } wlcs-1.7.0/src/xfail_supporting_test_listener.h000066400000000000000000000051201453657357500220120ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Christopher James Halse Rogers */ #ifndef WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_ #define WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_ #include #include #include #include #include #include namespace testing { class XFailSupportingTestListenerWrapper : public testing::TestEventListener { public: explicit XFailSupportingTestListenerWrapper(std::unique_ptr&& wrapped); void OnTestProgramStart(testing::UnitTest const& unit_test) override; void OnTestIterationStart(testing::UnitTest const& unit_test, int iteration) override; void OnEnvironmentsSetUpStart(testing::UnitTest const& unit_test) override; void OnEnvironmentsSetUpEnd(testing::UnitTest const& unit_test) override; void OnTestCaseStart(testing::TestCase const& test_case) override; void OnTestStart(testing::TestInfo const& test_info) override; void OnTestPartResult(testing::TestPartResult const& test_part_result) override; void OnTestEnd(testing::TestInfo const& test_info) override; void OnTestCaseEnd(testing::TestCase const& test_case) override; void OnEnvironmentsTearDownStart(testing::UnitTest const& unit_test) override; void OnEnvironmentsTearDownEnd(testing::UnitTest const& unit_test) override; void OnTestIterationEnd(testing::UnitTest const& unit_test, int iteration) override; void OnTestProgramEnd(testing::UnitTest const& unit_test) override; bool failed() const; private: std::unique_ptr const delegate; std::chrono::steady_clock::time_point current_test_start; ::testing::TestInfo const* current_test_info; std::optional> current_skip_reasons; std::unordered_set failed_test_names; std::unordered_set skipped_test_names; bool failed_{false}; }; } #endif //WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_ wlcs-1.7.0/tests/000077500000000000000000000000001453657357500136555ustar00rootroot00000000000000wlcs-1.7.0/tests/copy_cut_paste.cpp000066400000000000000000000072271453657357500174120ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "data_device.h" #include "helpers.h" #include "in_process_server.h" #include "wl_handle.h" #include "version_specifier.h" #include #include using namespace testing; using namespace wlcs; namespace { auto static const any_width = 100; auto static const any_height = 100; auto static const any_mime_type = "AnyMimeType"; struct CCnPSource : Client { using Client::Client; Surface const surface{create_visible_surface(any_width, any_height)}; WlHandle const manager{ this->bind_if_supported(AnyVersion)}; DataDevice data_device{wl_data_device_manager_get_data_device(manager,seat())}; DataSource data{wl_data_device_manager_create_data_source(manager)}; void offer(char const* mime_type) { wl_data_source_offer(data, mime_type); // TODO: collect a serial from the "event that triggered" the selection // (This works while Mir fails to validate the serial) uint32_t const serial = 0; wl_data_device_set_selection(data_device, data, serial); roundtrip(); } }; struct MockDataDeviceListener : DataDeviceListener { using DataDeviceListener::DataDeviceListener; MOCK_METHOD2(data_offer, void(struct wl_data_device* wl_data_device, struct wl_data_offer* id)); }; struct MockDataOfferListener : DataOfferListener { using DataOfferListener::DataOfferListener; MOCK_METHOD2(offer, void(struct wl_data_offer* data_offer, char const* MimeType)); }; struct CCnPSink : Client { using Client::Client; WlHandle const manager{ this->bind_if_supported(AnyVersion)}; DataDevice sink_data{wl_data_device_manager_get_data_device(manager, seat())}; MockDataDeviceListener listener{sink_data}; Surface create_surface_with_focus() { return Surface{create_visible_surface(any_width, any_height)}; } }; struct CopyCutPaste : StartedInProcessServer { CCnPSource source{the_server()}; CCnPSink sink{the_server()}; MockDataOfferListener mdol; void TearDown() override { source.roundtrip(); sink.roundtrip(); StartedInProcessServer::TearDown(); } }; } TEST_F(CopyCutPaste, given_source_has_offered_when_sink_gets_focus_it_sees_offer) { source.offer(any_mime_type); EXPECT_CALL(mdol, offer(_, StrEq(any_mime_type))); EXPECT_CALL(sink.listener, data_offer(_,_)) .WillOnce(Invoke([&](struct wl_data_device*, struct wl_data_offer* id) { mdol.listen_to(id); })); sink.create_surface_with_focus(); } TEST_F(CopyCutPaste, given_sink_has_focus_when_source_makes_offer_sink_sees_offer) { auto sink_surface_with_focus = sink.create_surface_with_focus(); EXPECT_CALL(mdol, offer(_, StrEq(any_mime_type))); EXPECT_CALL(sink.listener, data_offer(_,_)) .WillOnce(Invoke([&](struct wl_data_device*, struct wl_data_offer* id) { mdol.listen_to(id); })); source.offer(any_mime_type); } wlcs-1.7.0/tests/frame_submission.cpp000066400000000000000000000111711453657357500177270ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "helpers.h" #include "in_process_server.h" #include using namespace testing; using namespace wlcs; namespace { struct FrameSubmission : StartedInProcessServer { Client client{the_server()}; Surface surface{client.create_visible_surface(200, 200)}; void submit_frame(bool& frame_consumed); void wait_for_frame(bool const& frame_consumed); }; void FrameSubmission::submit_frame(bool& consumed_flag) { static wl_callback_listener const frame_listener { [](void *data, struct wl_callback* callback, uint32_t /*callback_data*/) { *static_cast(data) = true; wl_callback_destroy(callback); } }; consumed_flag = false; wl_callback_add_listener(wl_surface_frame(surface), &frame_listener, &consumed_flag); auto buffer = std::make_shared(client, 200, 200); wl_surface_attach(surface, *buffer, 0, 0); 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) { 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)); } } // Regression test for https://github.com/MirServer/mir/issues/2960 TEST_F(FrameSubmission, test_buffer_can_be_deleted_after_attached) { using namespace testing; wlcs::Client client{the_server()}; auto surface = client.create_visible_surface(200, 200); auto buffer = std::make_shared(client, 200, 200); wl_surface_attach(surface, *buffer, 0, 0); buffer.reset(); /* Check that the destroyed buffer doesn't crash the server * It's not clear what the *correct* behaviour is in this case: * Weston treats this as committing a null buffer, wlroots appears to * treat this as committing the content of the buffer before deletion. * * *Whatever* the correct behaviour is, "crash" is *definitely* * incorrect. */ wl_surface_commit(surface); // Roundtrip to ensure the server has processed our weirdness client.roundtrip(); } /* Firefox has a lovely habit of sending an endless stream of * wl_surface.frame() requests (maybe it's trying to estimate vsync?!) * * If a compositor responds immediately to the frame on commit, Firefox * ends up looping endlessly. If the compositor *doesn't* respond to the frame * request, Firefox decides not to draw anything. 🤷‍♀️ */ TEST_F(FrameSubmission, when_client_endlessly_requests_frame_then_callbacks_are_throttled) { using namespace testing; using namespace std::chrono_literals; wlcs::Client annoying_client{the_server()}; auto surface = client.create_visible_surface(200, 200); bool frame_callback_called{false}; wl_callback_listener const listener = { .done = [](void* data, struct wl_callback* callback, [[maybe_unused]]uint32_t timestamp) { wl_callback_destroy(callback); auto callback_called = static_cast(data); *callback_called = true; } }; auto const timeout = std::chrono::steady_clock::now() + 10s; do { frame_callback_called = false; wl_callback_add_listener(wl_surface_frame(surface), &listener, &frame_callback_called); wl_surface_commit(surface); client.roundtrip(); /* This roundtrip ensures the server has processed everything prior. * In particular, if the server sends the frame callback in response to wl_surface.commit, that frame callback * will have been processed by now. */ } while (frame_callback_called && std::chrono::steady_clock::now() < timeout); EXPECT_THAT(std::chrono::steady_clock::now(), Lt(timeout)) << "Timed out looping in frame callback storm"; } wlcs-1.7.0/tests/gtk_primary_selection.cpp000066400000000000000000000161531453657357500207640ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "gtk_primary_selection.h" #include "in_process_server.h" #include "version_specifier.h" #include #include using namespace wlcs; using namespace testing; namespace { char const any_mime_type[] = "AnyMimeType"; char const any_mime_data[] = "AnyMimeData"; struct SourceApp : Client { using Client::Client; WlHandle manager{ this->bind_if_supported(AnyVersion)}; GtkPrimarySelectionSource source{manager}; GtkPrimarySelectionDevice device{manager, seat()}; void set_selection() { gtk_primary_selection_device_set_selection(device, source, 0); roundtrip(); } void offer(char const* mime_type) { gtk_primary_selection_source_offer(source, mime_type); roundtrip(); } }; struct SinkApp : Client { explicit SinkApp(Server& server) : Client{server} { roundtrip(); } WlHandle const manager{ this->bind_if_supported(AnyVersion)}; GtkPrimarySelectionDevice device{manager, seat()}; }; struct GtkPrimarySelection : StartedInProcessServer { SourceApp source_app{the_server()}; SinkApp sink_app{the_server()}; void TearDown() override { source_app.roundtrip(); sink_app.roundtrip(); StartedInProcessServer::TearDown(); } }; struct MockGtkPrimarySelectionDeviceListener : GtkPrimarySelectionDeviceListener { using GtkPrimarySelectionDeviceListener::GtkPrimarySelectionDeviceListener; MOCK_METHOD2(data_offer, void(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer)); MOCK_METHOD2(selection, void(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer)); }; struct MockGtkPrimarySelectionOfferListener : GtkPrimarySelectionOfferListener { using GtkPrimarySelectionOfferListener::GtkPrimarySelectionOfferListener; MOCK_METHOD2(offer, void(gtk_primary_selection_offer* offer, const char* mime_type)); }; struct MockGtkPrimarySelectionSourceListener : GtkPrimarySelectionSourceListener { using GtkPrimarySelectionSourceListener::GtkPrimarySelectionSourceListener; MOCK_METHOD3(send, void(gtk_primary_selection_source* source, const char* mime_type, int32_t fd)); MOCK_METHOD1(cancelled, void(gtk_primary_selection_source*)); }; struct StubGtkPrimarySelectionDeviceListener : GtkPrimarySelectionDeviceListener { StubGtkPrimarySelectionDeviceListener( gtk_primary_selection_device* device, GtkPrimarySelectionOfferListener& offer_listener) : GtkPrimarySelectionDeviceListener{device}, offer_listener{offer_listener} { } void data_offer(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer) override { offer_listener.listen_to(offer); GtkPrimarySelectionDeviceListener::data_offer(device, offer); } void selection(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer) override { selected = offer; GtkPrimarySelectionDeviceListener::selection(device, offer); } GtkPrimarySelectionOfferListener& offer_listener; gtk_primary_selection_offer* selected = nullptr; }; struct StubGtkPrimarySelectionSourceListener : GtkPrimarySelectionSourceListener { using GtkPrimarySelectionSourceListener::GtkPrimarySelectionSourceListener; void send(gtk_primary_selection_source*, const char*, int32_t fd) { ASSERT_THAT(write(fd, any_mime_data, sizeof any_mime_data), Eq(ssize_t(sizeof any_mime_data))); close(fd); } }; struct Pipe { int source; int sink; Pipe() { socketpair(AF_LOCAL, SOCK_STREAM, 0, &source); } Pipe(Pipe const&) = delete; Pipe& operator=(Pipe const&) = delete; ~Pipe() { close(source); close(sink); } }; } TEST_F(GtkPrimarySelection, source_can_offer) { source_app.offer(any_mime_type); source_app.set_selection(); } TEST_F(GtkPrimarySelection, sink_can_listen) { MockGtkPrimarySelectionDeviceListener device_listener{sink_app.device}; MockGtkPrimarySelectionOfferListener offer_listener; InSequence seq; EXPECT_CALL(device_listener, data_offer(_, _)) .WillOnce(Invoke([&](auto*, auto* id) { offer_listener.listen_to(id); })); EXPECT_CALL(offer_listener, offer(_, StrEq(any_mime_type))); EXPECT_CALL(device_listener, selection(_, _)); source_app.offer(any_mime_type); source_app.set_selection(); sink_app.roundtrip(); } TEST_F(GtkPrimarySelection, sink_can_request) { GtkPrimarySelectionOfferListener offer_listener; StubGtkPrimarySelectionDeviceListener device_listener{sink_app.device, offer_listener}; source_app.offer(any_mime_type); source_app.set_selection(); sink_app.roundtrip(); ASSERT_THAT(device_listener.selected, NotNull()); Pipe pipe; gtk_primary_selection_offer_receive(device_listener.selected, any_mime_type, pipe.source); sink_app.roundtrip(); } TEST_F(GtkPrimarySelection, source_sees_request) { MockGtkPrimarySelectionSourceListener source_listener{source_app.source}; GtkPrimarySelectionOfferListener offer_listener; StubGtkPrimarySelectionDeviceListener device_listener{sink_app.device, offer_listener}; source_app.offer(any_mime_type); source_app.set_selection(); sink_app.roundtrip(); ASSERT_THAT(device_listener.selected, NotNull()); EXPECT_CALL(source_listener, send(_, _, _)) .Times(1) .WillRepeatedly(Invoke([&](auto*, auto*, int fd) { close(fd); })); Pipe pipe; gtk_primary_selection_offer_receive(device_listener.selected, any_mime_type, pipe.source); sink_app.roundtrip(); source_app.roundtrip(); } TEST_F(GtkPrimarySelection, source_can_supply_request) { StubGtkPrimarySelectionSourceListener source_listener{source_app.source}; GtkPrimarySelectionOfferListener offer_listener; StubGtkPrimarySelectionDeviceListener device_listener{sink_app.device, offer_listener}; source_app.offer(any_mime_type); source_app.set_selection(); sink_app.roundtrip(); ASSERT_THAT(device_listener.selected, NotNull()); Pipe pipe; gtk_primary_selection_offer_receive(device_listener.selected, any_mime_type, pipe.source); sink_app.roundtrip(); source_app.roundtrip(); char buffer[128]; EXPECT_THAT(read(pipe.sink, buffer, sizeof buffer), Eq(ssize_t(sizeof any_mime_data))); EXPECT_THAT(buffer, StrEq(any_mime_data)); } wlcs-1.7.0/tests/pointer_constraints.cpp000066400000000000000000000213531453657357500204740ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "pointer_constraints_unstable_v1.h" #include "helpers.h" #include "in_process_server.h" #include #include using testing::AnyNumber; using testing::Eq; using testing::Ne; using testing::NotNull; 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 locked_ptr = nullptr; std::unique_ptr 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(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(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(); } } wlcs-1.7.0/tests/primary_selection.cpp000066400000000000000000000161631453657357500201200ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "primary_selection.h" #include "in_process_server.h" #include "version_specifier.h" #include #include using namespace wlcs; using namespace testing; namespace { char const any_mime_type[] = "AnyMimeType"; char const any_mime_data[] = "AnyMimeData"; struct SourceApp : Client { using Client::Client; WlHandle const manager{ this->bind_if_supported(AnyVersion)}; PrimarySelectionSource source{manager}; PrimarySelectionDevice device{manager, seat()}; void set_selection() { zwp_primary_selection_device_v1_set_selection(device, source, 0); roundtrip(); } void offer(char const* mime_type) { zwp_primary_selection_source_v1_offer(source, mime_type); roundtrip(); } }; struct SinkApp : Client { explicit SinkApp(Server& server) : Client{server} { roundtrip(); } WlHandle const manager{ this->bind_if_supported(AnyVersion)}; PrimarySelectionDevice device{manager, seat()}; }; struct PrimarySelection : StartedInProcessServer { SourceApp source_app{the_server()}; SinkApp sink_app{the_server()}; Surface surface{sink_app.create_visible_surface(10, 10)}; 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)); } wlcs-1.7.0/tests/relative_pointer.cpp000066400000000000000000000054021453657357500177350ustar00rootroot00000000000000/* * Copyright © 2020 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "relative_pointer_unstable_v1.h" #include "helpers.h" #include "in_process_server.h" #include using testing::AnyNumber; using testing::IsTrue; using testing::NotNull; using 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()); } wlcs-1.7.0/tests/self_test.cpp000066400000000000000000000144111453657357500163520ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Alan Griffiths */ #include "data_device.h" #include "helpers.h" #include "gtest_helpers.h" #include "in_process_server.h" #include "version_specifier.h" #include #include using namespace testing; using namespace wlcs; namespace { struct SelfTest : StartedInProcessServer { Client client1{the_server()}; }; auto static const any_width = 100; auto static const any_height = 100; } TEST_F(SelfTest, when_creating_second_client_nothing_bad_happens) { Client client2{the_server()}; } TEST_F(SelfTest, given_second_client_when_roundtripping_first_client_nothing_bad_happens) { Client client2{the_server()}; client1.roundtrip(); } TEST_F(SelfTest, given_second_client_when_roundtripping_both_clients_nothing_bad_happens) { Client client2{the_server()}; for (auto i = 0; i != 10; ++i) { client1.roundtrip(); client2.roundtrip(); } } TEST_F(SelfTest, when_a_client_creates_a_surface_nothing_bad_happens) { Surface const surface1{client1.create_visible_surface(any_width, any_height)}; client1.roundtrip(); } TEST_F(SelfTest, given_second_client_when_first_creates_a_surface_nothing_bad_happens) { Client client2{the_server()}; Surface const surface1{client1.create_visible_surface(any_width, any_height)}; for (auto i = 0; i != 10; ++i) { client1.roundtrip(); client2.roundtrip(); } } TEST_F(SelfTest, given_second_client_when_both_create_a_surface_nothing_bad_happens) { Client client2{the_server()}; Surface const surface1{client1.create_visible_surface(any_width, any_height)}; Surface const surface2{client2.create_visible_surface(any_width, any_height)}; for (auto i = 0; i != 10; ++i) { client1.roundtrip(); client2.roundtrip(); } } TEST_F(SelfTest, xfail_failure_is_noted) { ::testing::Test::RecordProperty("wlcs-skip-test", "Reason goes here"); FAIL() << "This message shouldn't be seen"; } TEST_F(SelfTest, expected_missing_extension_is_xfail) { throw wlcs::ExtensionExpectedlyNotSupported("xdg_not_really_an_extension", wlcs::AtLeastVersion{1}); } TEST_F(SelfTest, acquiring_unsupported_extension_is_xfail) { auto const extension_list = the_server().supported_extensions(); if (!extension_list) { ::testing::Test::RecordProperty("wlcs-skip-test", "Compositor Integration module is too old for expected extension failures"); FAIL() << "Requires unsupported feature from module under test"; } Client client{the_server()}; wl_interface unsupported_interface = wl_shell_interface; unsupported_interface.name = "wlcs_non_existent_extension"; client.bind_if_supported(unsupported_interface, AnyVersion); FAIL() << "We should have (x)failed at acquiring the interface"; } TEST_F(SelfTest, acquiring_unsupported_extension_version_is_xfail) { auto const extension_list = the_server().supported_extensions(); if (!extension_list) { ::testing::Test::RecordProperty("wlcs-skip-test", "Compositor Integration module is too old for expected extension failures"); FAIL() << "Requires unsupported feature from module under test"; } Client client{the_server()}; wl_interface interface_with_unsupported_version = wl_shell_interface; interface_with_unsupported_version.version += 1; client.bind_if_supported( interface_with_unsupported_version, AtLeastVersion{static_cast(interface_with_unsupported_version.version)}); FAIL() << "We should have (x)failed at acquiring the interface"; } TEST_F(SelfTest, does_not_acquire_version_newer_than_wlcs_supports) { 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()}; auto const proxy_latest = static_cast(client.bind_if_supported(wl_seat_interface, AnyVersion)); ASSERT_THAT(wl_seat_get_version(proxy_latest), Gt(1)); wl_interface interface_with_old_version = wl_seat_interface; interface_with_old_version.version = wl_seat_get_version(proxy_latest) - 1; auto const proxy_old = static_cast(client.bind_if_supported(interface_with_old_version, AnyVersion)); EXPECT_THAT(wl_seat_get_version(proxy_old), Eq(interface_with_old_version.version)); wl_seat_destroy(proxy_latest); wl_seat_destroy(proxy_old); } 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"; } wlcs-1.7.0/tests/subsurfaces.cpp000066400000000000000000001111421453657357500167060ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "helpers.h" #include "in_process_server.h" #include "xdg_shell_v6.h" #include #include using namespace testing; struct AbstractInputDevice { virtual void to_screen_position(int x, int y) = 0; virtual wl_surface* focused_window() = 0; virtual std::pair position_on_window() = 0; virtual ~AbstractInputDevice() = default; }; struct PointerInputDevice : AbstractInputDevice { PointerInputDevice(wlcs::Server& server, wlcs::Client& client): client{client}, pointer{server.create_pointer()} { } void to_screen_position(int x, int y) override { pointer.move_to(0, 0); pointer.move_to(x, y); } wl_surface* focused_window() override { return client.window_under_cursor(); } std::pair position_on_window() override { return client.pointer_position(); } wlcs::Client& client; wlcs::Pointer pointer; }; struct TouchInputDevice : AbstractInputDevice { TouchInputDevice(wlcs::Server& server, wlcs::Client& client): client{client}, touch{server.create_touch()} { } void to_screen_position(int x, int y) override { touch.up(); touch.down_at(x, y); } wl_surface* focused_window() override { return client.touched_window(); } std::pair position_on_window() override { return client.touch_position(); } wlcs::Client& client; wlcs::Touch touch; }; struct SubsurfaceTestParams { std::string name; std::function(wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)> make_surface; std::function(wlcs::Server& server, wlcs::Client& client)> make_input_device; }; std::ostream& operator<<(std::ostream& out, SubsurfaceTestParams const& param) { return out << param.name; } class SubsurfaceTest : public wlcs::StartedInProcessServer, public testing::WithParamInterface { public: static int const surface_width = 200, surface_height = 300; static int const subsurface_width = 50, subsurface_height = 50; static int const surface_x = 20, surface_y = 30; SubsurfaceTest(): client{the_server()}, main_surface{std::move(*GetParam().make_surface( *this, client, surface_x, surface_y, surface_width, surface_height))}, subsurface{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)}, input_device{GetParam().make_input_device(the_server(), client)} { client.roundtrip(); } void move_subsurface_to(int x, int y) { wl_subsurface_set_position(subsurface, x, y); wl_surface_commit(main_surface); client.roundtrip(); } wlcs::Client client{the_server()}; wlcs::Surface main_surface; wlcs::Subsurface subsurface; std::unique_ptr input_device; }; class SubsurfaceMultilevelTest : public wlcs::StartedInProcessServer, public testing::WithParamInterface { public: static int const surface_width = 200, surface_height = 300; static int const subsurface_width = 50, subsurface_height = 50; static int const surface_x = 20, surface_y = 30; SubsurfaceMultilevelTest(): client{the_server()}, main_surface{std::move(*GetParam().make_surface( *this, client, surface_x, surface_y, surface_width, surface_height))}, parent_subsurface{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)}, child_subsurface{wlcs::Subsurface::create_visible(parent_subsurface, 0, 0, subsurface_width, subsurface_height)}, input_device{GetParam().make_input_device(the_server(), client)} { client.roundtrip(); } wlcs::Client client{the_server()}; wlcs::Surface main_surface; wlcs::Subsurface parent_subsurface; wlcs::Subsurface child_subsurface; std::unique_ptr input_device; }; TEST_P(SubsurfaceTest, subsurface_has_correct_parent) { EXPECT_THAT(&subsurface.parent(), Eq(&main_surface)); } TEST_P(SubsurfaceTest, subsurface_gets_pointer_input) { int const pointer_x = surface_x + 10, pointer_y = surface_y + 5; input_device->to_screen_position(pointer_x, pointer_y); client.roundtrip(); EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface"; EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - surface_x), wl_fixed_from_int(pointer_y - surface_y)))); } TEST_P(SubsurfaceTest, pointer_input_correctly_offset_for_subsurface) { int const pointer_x = surface_x + 13, pointer_y = surface_y + 24; int const subsurface_x = 8, subsurface_y = 17; move_subsurface_to(subsurface_x, subsurface_y); input_device->to_screen_position(pointer_x, pointer_y); client.roundtrip(); EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface"; EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - surface_x - subsurface_x), wl_fixed_from_int(pointer_y - surface_y - subsurface_y)))); } TEST_P(SubsurfaceTest, sync_subsurface_moves_when_only_parent_committed) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y); // Position is applied when parent (main_surface) commits, so subsurface does not need to be committed wl_surface_commit(main_surface); client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))) << "Subsurface did not move after parent commit"; } TEST_P(SubsurfaceTest, desync_subsurface_moves_when_only_parent_committed) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_desync(subsurface); wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y); // Position is applied when parent (main_surface) commits, so subsurface does not need to be committed wl_surface_commit(main_surface); client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))) << "Subsurface did not move after parent commit"; } TEST_P(SubsurfaceTest, subsurface_does_not_move_when_parent_not_committed) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_desync(subsurface); wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y); wl_surface_commit(subsurface); // We don't call wl_surface_commit(main_surface), so position should not be applied client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))) << "Subsurface moved to new location without parent being committed"; } TEST_P(SubsurfaceTest, subsurface_extends_parent_input_region) { int const pointer_x = surface_x - 5, pointer_y = surface_y + surface_height + 8; int const subsurface_x = -10, subsurface_y = surface_height - 10; move_subsurface_to(subsurface_x, subsurface_y); input_device->to_screen_position(pointer_x, pointer_y); client.roundtrip(); EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface"; EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - surface_x - subsurface_x), wl_fixed_from_int(pointer_y - surface_y - subsurface_y)))); } TEST_P(SubsurfaceTest, input_falls_through_empty_subsurface_input_region) { int const pointer_x = surface_x + 10, pointer_y = surface_y + 5; auto const wl_region = wl_compositor_create_region(client.compositor()); wl_surface_set_input_region(subsurface, wl_region); wl_region_destroy(wl_region); wl_surface_commit(subsurface); wl_surface_commit(main_surface); client.roundtrip(); input_device->to_screen_position(pointer_x, pointer_y); client.roundtrip(); EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface)) << "input was incorrectly caught by subsurface"; EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - surface_x), wl_fixed_from_int(pointer_y - surface_y)))); } TEST_P(SubsurfaceTest, gets_input_over_surface_with_empty_region) { int const pointer_x = surface_x + 32, pointer_y = surface_y + 21; auto const wl_region = wl_compositor_create_region(client.compositor()); wl_surface_set_input_region(main_surface, wl_region); wl_region_destroy(wl_region); wl_surface_commit(main_surface); client.roundtrip(); input_device->to_screen_position(pointer_x, pointer_y); client.roundtrip(); EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface"; EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - surface_x), wl_fixed_from_int(pointer_y - surface_y)))); } TEST_P(SubsurfaceTest, one_subsurface_to_another_fallthrough) { int const pointer_x_0 = 3, pointer_y_0 = 3; int const pointer_x_1 = 3, pointer_y_1 = 10; int const pointer_x_2 = 10, pointer_y_2 = 3; int const subsurface_x = 0, subsurface_y = 5; int const subsurface_top_x = 5, subsurface_top_y = 0; move_subsurface_to(subsurface_x, subsurface_y); auto subsurface_top{wlcs::Subsurface::create_visible(main_surface, subsurface_top_x, subsurface_top_y, subsurface_width, subsurface_height)}; input_device->to_screen_position(pointer_x_0 + surface_x, pointer_y_0 + surface_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)) << "main surface not focused"; EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x_0), wl_fixed_from_int(pointer_y_0)))); input_device->to_screen_position(pointer_x_1 + surface_x, pointer_y_1 + surface_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "lower subsurface not focused"; EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x_1 - subsurface_x), wl_fixed_from_int(pointer_y_1 - subsurface_y)))); input_device->to_screen_position(pointer_x_2 + surface_x, pointer_y_2 + surface_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface_top)) << "upper subsurface not focused"; EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x_2 - subsurface_top_x), wl_fixed_from_int(pointer_y_2 - subsurface_top_y)))); } TEST_P(SubsurfaceTest, place_below_simple) { auto subsurface_moving_down{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)}; wl_subsurface_place_below(subsurface_moving_down, subsurface); wl_surface_commit(subsurface_moving_down); wl_surface_commit(subsurface); wl_surface_commit(main_surface); input_device->to_screen_position(5 + surface_x, 5 + surface_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface_moving_down)) << "subsurface.place_below() did not have an effect"; ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface)) << "wrong surface/subsurface on top"; } TEST_P(SubsurfaceTest, place_above_simple) { auto subsurface_being_covered{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)}; wl_subsurface_place_above(subsurface, subsurface_being_covered); wl_surface_commit(subsurface); wl_surface_commit(subsurface_being_covered); wl_surface_commit(main_surface); input_device->to_screen_position(5 + surface_x, 5 + surface_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface_being_covered)) << "subsurface.place_above() did not have an effect"; ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface)) << "wrong surface/subsurface on top"; } TEST_P(SubsurfaceTest, subsurface_of_a_subsurface_handled) { int const pointer_x_0 = 3, pointer_y_0 = 3; int const pointer_x_1 = 3, pointer_y_1 = 10; int const pointer_x_2 = 10, pointer_y_2 = 3; int const subsurface_x = 0, subsurface_y = 5; int const subsurface_top_x = 5, subsurface_top_y = -5; move_subsurface_to(subsurface_x, subsurface_y); auto subsurface_top{wlcs::Subsurface::create_visible(subsurface, subsurface_top_x, subsurface_top_y, subsurface_width, subsurface_height)}; input_device->to_screen_position(pointer_x_0 + surface_x, pointer_y_0 + surface_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)) << "main surface not focused"; EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x_0), wl_fixed_from_int(pointer_y_0)))); input_device->to_screen_position(pointer_x_1 + surface_x, pointer_y_1 + surface_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "lower subsurface not focused"; EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x_1 - subsurface_x), wl_fixed_from_int(pointer_y_1 - subsurface_y)))); input_device->to_screen_position(pointer_x_2 + surface_x, pointer_y_2 + surface_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface_top)) << "subsurface of subsurface not focused"; EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x_2 - subsurface_top_x - subsurface_x), wl_fixed_from_int(pointer_y_2 - subsurface_top_y - subsurface_y)))); } TEST_P(SubsurfaceTest, subsurface_moves_under_input_device_once) { int const input_x = surface_x + 10, input_y = surface_y + 5; int const subsurface_x = -23, subsurface_y = -17; input_device->to_screen_position(input_x, input_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "precondition failed"; ASSERT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(input_x - surface_x), wl_fixed_from_int(input_y - surface_y)))) << "precondition failed"; wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y); wl_surface_commit(subsurface); wl_surface_commit(main_surface); client.roundtrip(); EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "subsurface not focuesed"; EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(input_x - surface_x), wl_fixed_from_int(input_y - surface_y)))) << "input device did not get new location"; EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(input_x - surface_x - subsurface_x), wl_fixed_from_int(input_y - surface_y - subsurface_y)))) << "input device in wrong location"; } TEST_P(SubsurfaceTest, subsurface_moves_under_input_device_twice) { int const input_x = surface_x + 10, input_y = surface_y + 5; int const subsurface_x_0 = 4, subsurface_y_0 = 2; int const subsurface_x_1 = -23, subsurface_y_1 = -17; input_device->to_screen_position(input_x, input_y); wl_subsurface_set_position(subsurface, subsurface_x_0, subsurface_y_0); wl_surface_commit(subsurface); wl_surface_commit(main_surface); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "precondition failed"; ASSERT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(input_x - surface_x - subsurface_x_0), wl_fixed_from_int(input_y - surface_y - subsurface_y_0)))) << "precondition failed"; wl_subsurface_set_position(subsurface, subsurface_x_1, subsurface_y_1); wl_surface_commit(subsurface); wl_surface_commit(main_surface); client.roundtrip(); EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "subsurface not focuesed"; EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(input_x - surface_x - subsurface_x_0), wl_fixed_from_int(input_y - surface_y - subsurface_y_0)))) << "input device did not get new location"; EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(input_x - surface_x - subsurface_x_1), wl_fixed_from_int(input_y - surface_y - subsurface_y_1)))) << "input device in wrong location"; } TEST_P(SubsurfaceTest, subsurface_moves_out_from_under_input_device) { int const input_x = surface_x + 10, input_y = surface_y + 5; int const subsurface_x = input_x - surface_x + 10, subsurface_y = input_y - surface_y + 10; input_device->to_screen_position(input_x, input_y); client.roundtrip(); ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "precondition failed"; ASSERT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(input_x - surface_x), wl_fixed_from_int(input_y - surface_y)))) << "precondition failed"; wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y); wl_surface_commit(subsurface); wl_surface_commit(main_surface); client.roundtrip(); EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)) << "main surface not focuesed"; EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(input_x - surface_x), wl_fixed_from_int(input_y - surface_y)))) << "input device in wrong location"; } INSTANTIATE_TEST_SUITE_P( WlShellSubsurfaces, SubsurfaceTest, testing::Values( SubsurfaceTestParams{ "wl_shell_surface", [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height) -> std::unique_ptr { auto surface = client.create_wl_shell_surface( width, height); server.the_server().move_surface_to(surface, x, y); return std::make_unique(std::move(surface)); }, [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr { return std::make_unique(server, client); } } )); INSTANTIATE_TEST_SUITE_P( XdgShellV6Subsurfaces, SubsurfaceTest, testing::Values( SubsurfaceTestParams{ "xdg_v6_surface", [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height) -> std::unique_ptr { auto surface = client.create_xdg_shell_v6_surface( width, height); server.the_server().move_surface_to(surface, x, y); return std::make_unique(std::move(surface)); }, [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr { return std::make_unique(server, client); } } )); INSTANTIATE_TEST_SUITE_P( XdgShellStableSubsurfaces, SubsurfaceTest, testing::Values( SubsurfaceTestParams{ "xdg_stable_surface", [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height) -> std::unique_ptr { auto surface = client.create_xdg_shell_stable_surface( width, height); server.the_server().move_surface_to(surface, x, y); return std::make_unique(std::move(surface)); }, [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr { return std::make_unique(server, client); } } )); INSTANTIATE_TEST_SUITE_P( TouchInputSubsurfaces, SubsurfaceTest, testing::Values( SubsurfaceTestParams{ "touch_input_subsurfaces", [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height) -> std::unique_ptr { auto surface = client.create_xdg_shell_v6_surface( width, height); server.the_server().move_surface_to(surface, x, y); return std::make_unique(std::move(surface)); }, [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr { return std::make_unique(server, client); } } )); TEST_P(SubsurfaceMultilevelTest, subsurface_with_sync_parent_does_not_move_when_only_grandparent_committed) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y); // We don't call wl_surface_commit(parent_subsurface), so position should not be applied wl_surface_commit(main_surface); client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))) << "Subsurface moved without parent being committed"; } TEST_P(SubsurfaceMultilevelTest, subsurface_with_desync_parent_does_not_move_when_only_grandparent_committed) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_desync(parent_subsurface); wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y); // We don't call wl_surface_commit(parent_subsurface), so position should not be applied wl_surface_commit(main_surface); client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))) << "Subsurface moved without parent being committed"; } TEST_P(SubsurfaceMultilevelTest, subsurface_with_sync_parent_does_not_move_when_only_parent_committed) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y); wl_surface_commit(parent_subsurface); // We don't call wl_surface_commit(main_surface), which should be required before position is applied because // parent_subsurface is sync client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))) << "Subsurface of sync parent moved without grandparent being committed"; } TEST_P(SubsurfaceMultilevelTest, subsurface_with_desync_parent_moves_when_only_parent_committed) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_desync(parent_subsurface); wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y); wl_surface_commit(parent_subsurface); // wl_surface_commit(main_surface) should NOT be required for position to be applied because parent_subsurface is // desync client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))) << "Did not move on desync parent commit"; } TEST_P(SubsurfaceMultilevelTest, subsurface_does_not_move_when_grandparent_commit_is_before_sync_parent_commit) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y); wl_surface_commit(main_surface); wl_surface_commit(parent_subsurface); // Committing main_surface would need to happend AFTER parent_subsurface in order for position to be applied client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))) << "Subsurface moved when hierarchy commits were in the wrong order"; } TEST_P(SubsurfaceMultilevelTest, subsurface_moves_after_both_sync_parent_and_grandparent_commit) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y); wl_surface_commit(parent_subsurface); wl_surface_commit(main_surface); client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))) << "Did not move after parent and grandparent both comitted"; } TEST_P(SubsurfaceMultilevelTest, by_default_subsurface_is_sync) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_x); wl_surface_commit(parent_subsurface); // Not calling wl_surface_commit(main_surface), so new position should not be applied client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))) << "Subsurface moved without parent commit (it should have been 'sync' by default, but is acting as desync)"; } TEST_P(SubsurfaceMultilevelTest, subsurface_can_be_set_to_sync) { int const pointer_x = 30, pointer_y = 30; int const subsurface_x = 20, subsurface_y = 20; wl_subsurface_set_desync(child_subsurface); wl_subsurface_set_sync(child_subsurface); wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_x); wl_surface_commit(parent_subsurface); // Not calling wl_surface_commit(main_surface), so new position should not be applied client.roundtrip(); input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y); client.roundtrip(); EXPECT_THAT(input_device->position_on_window(), Eq(std::make_pair( wl_fixed_from_int(pointer_x), wl_fixed_from_int(pointer_y)))); EXPECT_THAT(input_device->position_on_window(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - subsurface_x), wl_fixed_from_int(pointer_y - subsurface_y)))) << "Subsurface moved without parent commit (it should have been 'sync' by default, but is acting as desync)"; } INSTANTIATE_TEST_SUITE_P( WlShellSubsurfaces, SubsurfaceMultilevelTest, testing::Values( SubsurfaceTestParams{ "wl_shell_surface", [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height) -> std::unique_ptr { auto surface = client.create_wl_shell_surface( width, height); server.the_server().move_surface_to(surface, x, y); return std::make_unique(std::move(surface)); }, [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr { return std::make_unique(server, client); } } )); INSTANTIATE_TEST_SUITE_P( XdgShellV6Subsurfaces, SubsurfaceMultilevelTest, testing::Values( SubsurfaceTestParams{ "xdg_v6_surface", [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height) -> std::unique_ptr { auto surface = client.create_xdg_shell_v6_surface( width, height); server.the_server().move_surface_to(surface, x, y); return std::make_unique(std::move(surface)); }, [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr { return std::make_unique(server, client); } } )); INSTANTIATE_TEST_SUITE_P( XdgShellStableSubsurfaces, SubsurfaceMultilevelTest, testing::Values( SubsurfaceTestParams{ "xdg_stable_surface", [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height) -> std::unique_ptr { auto surface = client.create_xdg_shell_stable_surface( width, height); server.the_server().move_surface_to(surface, x, y); return std::make_unique(std::move(surface)); }, [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr { return std::make_unique(server, client); } } )); INSTANTIATE_TEST_SUITE_P( TouchInputSubsurfaces, SubsurfaceMultilevelTest, testing::Values( SubsurfaceTestParams{ "touch_input_subsurfaces", [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height) -> std::unique_ptr { auto surface = client.create_xdg_shell_v6_surface( width, height); server.the_server().move_surface_to(surface, x, y); return std::make_unique(std::move(surface)); }, [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr { return std::make_unique(server, client); } } )); // TODO: combinations of sync and desync at various levels of the tree // TODO: "bad_surface" error // TODO: seitch to the new surface/input method abstraction wlcs-1.7.0/tests/surface_input_regions.cpp000066400000000000000000000727311453657357500207700ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "helpers.h" #include "in_process_server.h" #include "surface_builder.h" #include "input_method.h" #include #include #include #include using namespace testing; enum class RegionAction { add, subtract, }; struct Region { struct Element { RegionAction action; std::pair top_left; std::pair size; }; std::string name; std::pair surface_size; std::vector elements; void apply_to_surface(wlcs::Client& client, wl_surface* surface) const { if (elements.empty()) return; auto const wl_region = wl_compositor_create_region(client.compositor()); for (auto const& e: elements) { switch(e.action) { case RegionAction::add: wl_region_add(wl_region, e.top_left.first, e.top_left.second, e.size.first, e.size.second); break; case RegionAction::subtract: wl_region_subtract(wl_region, e.top_left.first, e.top_left.second, e.size.first, e.size.second); break; } } wl_surface_set_input_region(surface, wl_region); wl_region_destroy(wl_region); wl_surface_commit(surface); client.roundtrip(); } }; struct RegionWithTestPoints { RegionWithTestPoints() : name{"default constructed"}, region{"default constructed", {0, 0}, {}}, on_surface{0, 0}, off_surface{0, 0} { } RegionWithTestPoints( std::string const& name, Region region, std::pair on_surface, std::pair delta) : name{name}, region{region}, on_surface{on_surface}, off_surface{on_surface.first + delta.first, on_surface.second + delta.second} { } std::string name; Region region; std::pair on_surface; std::pair off_surface; }; std::ostream& operator<<(std::ostream& out, RegionWithTestPoints const& param) { return out << param.region.name << " " << param.name; } auto const all_surface_types = ValuesIn(wlcs::SurfaceBuilder::all_surface_types()); auto const toplevel_surface_types = ValuesIn(wlcs::SurfaceBuilder::toplevel_surface_types()); auto const xdg_stable_surface_type = Values( std::static_pointer_cast(std::make_shared(0, 0, 0, 0))); auto const all_input_types = ValuesIn(wlcs::InputMethod::all_input_methods()); class RegionSurfaceInputCombinations : public wlcs::InProcessServer, public testing::WithParamInterface, std::shared_ptr>> { }; auto const surface_size{std::make_pair(215, 108)}; Region const default_region{"default", surface_size, {}}; auto const default_edges = Values( RegionWithTestPoints{"left edge", default_region, {0, surface_size.second / 2}, {-1, 0}}, RegionWithTestPoints{"bottom edge", default_region, {surface_size.first / 2, surface_size.second - 1}, {0, 1}}, RegionWithTestPoints{"right edge", default_region, {surface_size.first - 1, surface_size.second / 2}, {1, 0}}, RegionWithTestPoints{"top edge", default_region, {surface_size.first / 2, 0}, {0, -1}}); Region const full_surface_region{"explicitly specified full surface", surface_size, { {RegionAction::add, {0, 0}, surface_size}}}; auto const full_surface_edges = Values( RegionWithTestPoints{"left edge", full_surface_region, {0, surface_size.second / 2}, {-1, 0}}, RegionWithTestPoints{"bottom edge", full_surface_region, {surface_size.first / 2, surface_size.second - 1}, {0, 1}}, RegionWithTestPoints{"right edge", full_surface_region, {surface_size.first - 1, surface_size.second / 2}, {1, 0}}, RegionWithTestPoints{"top edge", full_surface_region, {surface_size.first / 2, 0}, {0, -1}}); auto const region_inset = std::make_pair(12, 17); Region const smaller_region{"smaller", surface_size, { {RegionAction::add, region_inset, { surface_size.first - region_inset.first * 2, surface_size.second - region_inset.second * 2}}}}; auto const smaller_region_edges = Values( RegionWithTestPoints{"left edge", smaller_region, {region_inset.first, surface_size.second / 2}, {-1, 0}}, RegionWithTestPoints{"bottom edge", smaller_region, {surface_size.first / 2, surface_size.second - region_inset.second - 1}, {0, 1}}, RegionWithTestPoints{"right edge", smaller_region, {surface_size.first - region_inset.first - 1, surface_size.second / 2}, {1, 0}}, RegionWithTestPoints{"top edge", smaller_region, {surface_size.first / 2, region_inset.second}, {0, -1}}); // If a region is larger then the surface it should be clipped auto const region_outset = std::make_pair(12, 17); Region const larger_region{"larger", surface_size, { {RegionAction::add, {-region_outset.first, -region_outset.second}, { surface_size.first + region_inset.first * 2, surface_size.second + region_inset.second * 2}}}}; auto const larger_region_edges = Values( RegionWithTestPoints{"left edge", larger_region, {0, surface_size.second / 2}, {-1, 0}}, RegionWithTestPoints{"bottom edge", larger_region, {surface_size.first / 2, surface_size.second - 1}, {0, 1}}, RegionWithTestPoints{"right edge", larger_region, {surface_size.first - 1, surface_size.second / 2}, {1, 0}}, RegionWithTestPoints{"top edge", larger_region, {surface_size.first / 2, 0}, {0, -1}}); int const small_rect_inset = 16; // Looks something like this: // (dotted line is real surface, solid line is input region rectangles) // _______A_______ // | | // B| |C // |_D___________E_| // : | | : // : F| |G : // '---|---H---|---' // |_______| // I Region const multi_rect_region{"multi-rect", surface_size, { // upper rect {RegionAction::add, {0, 0}, {surface_size.first, surface_size.second / 2}}, // lower rect {RegionAction::add, {small_rect_inset, surface_size.second / 2}, {surface_size.first - small_rect_inset * 2, surface_size.second / 2 + 20}}}}; auto const multi_rect_edges = Values( RegionWithTestPoints{"top region edge at surface top edge", multi_rect_region, // A in diagram {surface_size.first / 2, 0}, {0, -1}}, RegionWithTestPoints{"right region edge at surface right edge", multi_rect_region, // C in diagram {surface_size.first - 1, surface_size.second / 4}, {1, 0}}, RegionWithTestPoints{"left region edge inside surface", multi_rect_region, // F in diagram {small_rect_inset, surface_size.second * 3 / 4}, {-1, 0}}, RegionWithTestPoints{"step edge", multi_rect_region, // D in diagram {small_rect_inset / 2, surface_size.second / 2 - 1}, {0, 1}}, RegionWithTestPoints{"bottom clipped edge", multi_rect_region, // I in diagram {surface_size.first / 2, surface_size.second - 1}, {0, 1}}); auto const multi_rect_corners = Values( RegionWithTestPoints{ "top-left corner", multi_rect_region, // AxB in diagram {0, 0}, {-1, -1}}, RegionWithTestPoints{ "top-right corner", multi_rect_region, // AxC in diagram {surface_size.first - 1, 0}, {1, -1}}, RegionWithTestPoints{ "bottom-left corner", multi_rect_region, // HxF in diagram {small_rect_inset, surface_size.second - 1}, {-1, 1}}, RegionWithTestPoints{ "bottom-right corner", multi_rect_region, // HxG in diagram {surface_size.first - small_rect_inset - 1, surface_size.second - 1}, {1, 1}}, RegionWithTestPoints{ "left interior corner", multi_rect_region, // HxF in diagram {small_rect_inset, surface_size.second / 2 - 1}, {-1, 1}}, RegionWithTestPoints{ "right interior corner", multi_rect_region, // HxG in diagram {surface_size.first - small_rect_inset - 1, surface_size.second / 2 - 1}, {1, 1}}); // TODO: test subtract // TODO: test empty region // TODO: test default region TEST_P(RegionSurfaceInputCombinations, input_inside_region_seen) { auto const top_left = std::make_pair(64, 49); wlcs::Client client{the_server()}; RegionWithTestPoints region; std::shared_ptr builder; std::shared_ptr input; std::tie(region, builder, input) = GetParam(); auto surface = builder->build( the_server(), client, top_left, region.region.surface_size); region.region.apply_to_surface(client, *surface); struct wl_surface* const wl_surface = *surface; auto const device = input->create_device(the_server()); device->down_at({ top_left.first + region.on_surface.first, top_left.second + region.on_surface.second}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Eq(wl_surface)) << input << " not seen by "<< builder << " when inside " << region.name << " of " << region.region.name << " region"; if (input->current_surface(client) == wl_surface) { EXPECT_THAT(input->position_on_surface(client), Eq(region.on_surface)) << input << " in the wrong place over " << builder << " while testing " << region; } } TEST_P(RegionSurfaceInputCombinations, input_not_seen_after_leaving_region) { auto const top_left = std::make_pair(64, 49); wlcs::Client client{the_server()}; RegionWithTestPoints region; std::shared_ptr builder; std::shared_ptr input; std::tie(region, builder, input) = GetParam(); auto surface = builder->build( the_server(), client, top_left, region.region.surface_size); region.region.apply_to_surface(client, *surface); struct wl_surface* const wl_surface = *surface; auto const device = input->create_device(the_server()); device->down_at({ top_left.first + region.on_surface.first, top_left.second + region.on_surface.second}); client.roundtrip(); device->up(); device->down_at({ top_left.first + region.off_surface.first, top_left.second + region.off_surface.second}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(wl_surface)) << input << " seen by " << builder << " when outside " << region.name << " of " << region.region.name << " region"; } class SurfaceInputCombinations : public wlcs::InProcessServer, public testing::WithParamInterface, std::shared_ptr>> { }; TEST_P(SurfaceInputCombinations, input_not_seen_in_region_after_null_buffer_committed) { auto const top_left = std::make_pair(64, 49); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto const region = full_surface_region; auto surface = builder->build( the_server(), client, top_left, region.surface_size); region.apply_to_surface(client, *surface); struct wl_surface* const wl_surface = *surface; wl_surface_attach(wl_surface, nullptr, 0, 0); wl_surface_commit(wl_surface); client.roundtrip(); auto const device = input->create_device(the_server()); device->down_at(top_left); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(wl_surface)) << input << " seen by " << builder << " after null buffer committed"; } TEST_P(SurfaceInputCombinations, input_not_seen_in_surface_without_region_after_null_buffer_committed) { auto const top_left = std::make_pair(64, 49); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto surface = builder->build( the_server(), client, top_left, surface_size); struct wl_surface* const wl_surface = *surface; wl_surface_attach(wl_surface, nullptr, 0, 0); wl_surface_commit(wl_surface); client.roundtrip(); auto const device = input->create_device(the_server()); device->down_at(top_left); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(wl_surface)) << input << " seen by " << builder << " after null buffer committed"; } TEST_P(SurfaceInputCombinations, input_not_seen_over_empty_region) { auto const top_left = std::make_pair(64, 49); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto surface = builder->build( the_server(), client, top_left, surface_size); struct wl_surface* const wl_surface = *surface; auto const wl_region = wl_compositor_create_region(client.compositor()); wl_surface_set_input_region(*surface, wl_region); wl_region_destroy(wl_region); wl_surface_commit(*surface); client.roundtrip(); auto const device = input->create_device(the_server()); device->down_at({top_left.first + 4, top_left.second + 4}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(wl_surface)) << input << " seen by " << builder << " with empty input region"; } TEST_P(SurfaceInputCombinations, input_hits_parent_after_falling_through_subsurface) { auto const top_left = std::make_pair(64, 49); auto const input_offset = std::make_pair(4, 4); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto parent = builder->build( the_server(), client, top_left, surface_size); struct wl_surface* const parent_wl_surface = *parent; auto subsurface = std::make_unique(wlcs::Subsurface::create_visible( *parent, 0, 0, surface_size.first, surface_size.second)); wl_subsurface_set_desync(*subsurface); struct wl_surface* const sub_wl_surface = *subsurface; Region const region{"region", surface_size, {{RegionAction::add, {0, 0}, {1, 1}}}}; region.apply_to_surface(client, sub_wl_surface); auto const device = input->create_device(the_server()); device->down_at({top_left.first + input_offset.first, top_left.second + input_offset.second}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface)) << input << " seen by subsurface when not over region"; EXPECT_THAT(input->current_surface(client), Eq(parent_wl_surface)) << input << " not seen by " << builder << " when it should have fallen through the subsurface input region"; EXPECT_THAT(input->position_on_surface(client), Eq(input_offset)) << input << " seen in the wrong place"; } TEST_P(SurfaceInputCombinations, unmapping_parent_stops_subsurface_getting_input) { auto const top_left = std::make_pair(64, 49); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto parent = builder->build( the_server(), client, top_left, surface_size); struct wl_surface* const parent_wl_surface = *parent; auto subsurface = std::make_unique(wlcs::Subsurface::create_visible( *parent, 0, 0, surface_size.first, surface_size.second)); wl_subsurface_set_desync(*subsurface); struct wl_surface* const sub_wl_surface = *subsurface; client.roundtrip(); wl_surface_attach(parent_wl_surface, nullptr, 0, 0); wl_surface_commit(parent_wl_surface); client.roundtrip(); auto const device = input->create_device(the_server()); device->down_at({top_left.first + 4, top_left.second + 4}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface)) << input << " seen by " << builder << " after it was unmapped"; EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface)) << input << " seen by subsurface after parent " << builder << " was unmapped"; } TEST_P(SurfaceInputCombinations, input_falls_through_subsurface_when_unmapped) { auto const top_left = std::make_pair(200, 49); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto lower = client.create_visible_surface(surface_size.first, surface_size.second); the_server().move_surface_to(lower, top_left.first - 100, top_left.second); struct wl_surface* const lower_wl_surface = lower; auto parent = builder->build( the_server(), client, top_left, surface_size); ASSERT_THAT(surface_size.first, Gt(100)); struct wl_surface* const parent_wl_surface = *parent; auto subsurface = std::make_unique(wlcs::Subsurface::create_visible( *parent, -100, 0, surface_size.first, surface_size.second)); wl_subsurface_set_desync(*subsurface); struct wl_surface* const sub_wl_surface = *subsurface; client.roundtrip(); wl_surface_attach(sub_wl_surface, nullptr, 0, 0); wl_surface_commit(sub_wl_surface); client.roundtrip(); auto const device = input->create_device(the_server()); device->down_at({top_left.first - 90, top_left.second + 10}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface)) << input << " seen by subsurface after it was unmapped"; EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface)) << input << " seen by " << builder << " even through it shouldn't have been over it's input region"; EXPECT_THAT(input->current_surface(client), Eq(lower_wl_surface)) << input << " not seen by lower surface"; } TEST_P(SurfaceInputCombinations, input_falls_through_subsurface_when_parent_unmapped) { auto const top_left = std::make_pair(200, 49); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto lower = client.create_visible_surface(surface_size.first, surface_size.second); the_server().move_surface_to(lower, top_left.first - 100, top_left.second); struct wl_surface* const lower_wl_surface = lower; auto parent = builder->build( the_server(), client, top_left, surface_size); ASSERT_THAT(surface_size.first, Gt(100)); struct wl_surface* const parent_wl_surface = *parent; auto subsurface = std::make_unique(wlcs::Subsurface::create_visible( *parent, -100, 0, surface_size.first, surface_size.second)); wl_subsurface_set_desync(*subsurface); struct wl_surface* const sub_wl_surface = *subsurface; client.roundtrip(); wl_surface_attach(parent_wl_surface, nullptr, 0, 0); wl_surface_commit(parent_wl_surface); client.roundtrip(); auto const device = input->create_device(the_server()); device->down_at({top_left.first - 90, top_left.second + 10}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface)) << input << " seen by subsurface after parent was unmapped"; EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface)) << input << " seen by " << builder << " after being unmapped (also input should have gone to subsurface)"; EXPECT_THAT(input->current_surface(client), Eq(lower_wl_surface)) << input << " not seen by lower surface"; } TEST_P(SurfaceInputCombinations, input_seen_after_surface_unmapped_and_remapped) { auto const top_left = std::make_pair(200, 49); auto const input_offset = std::make_pair(4, 4); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto surface = builder->build( the_server(), client, top_left, surface_size); struct wl_surface* const wl_surface = *surface; wl_surface_attach(wl_surface, nullptr, 0, 0); wl_surface_commit(wl_surface); client.roundtrip(); surface->attach_visible_buffer(surface_size.first, surface_size.second); auto const device = input->create_device(the_server()); device->down_at({top_left.first + input_offset.first, top_left.second + input_offset.second}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Eq(wl_surface)) << input << " not seen by " << builder << " after it was unmapped and remapped"; EXPECT_THAT(input->position_on_surface(client), Eq(input_offset)) << input << " seen in the wrong place"; } TEST_P(SurfaceInputCombinations, input_seen_by_subsurface_after_parent_unmapped_and_remapped) { auto const top_left = std::make_pair(200, 49); auto const input_offset = std::make_pair(-90, 10); auto const subsurface_offset = std::make_pair(-100, 0); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto parent = builder->build( the_server(), client, top_left, surface_size); ASSERT_THAT(surface_size.first, Gt(100)); struct wl_surface* const parent_wl_surface = *parent; auto subsurface = std::make_unique(wlcs::Subsurface::create_visible( *parent, subsurface_offset.first, subsurface_offset.second, surface_size.first, surface_size.second)); wl_subsurface_set_desync(*subsurface); struct wl_surface* const sub_wl_surface = *subsurface; client.roundtrip(); wl_surface_attach(parent_wl_surface, nullptr, 0, 0); wl_surface_commit(parent_wl_surface); client.roundtrip(); parent->attach_visible_buffer(surface_size.first, surface_size.second); //the_server().move_surface_to(*parent, top_left.first, top_left.second); auto const device = input->create_device(the_server()); device->down_at({top_left.first + input_offset.first, top_left.second + input_offset.second}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface)) << input << " seen by " << builder << " when it should be seen by it's subsurface"; EXPECT_THAT(input->current_surface(client), Eq(sub_wl_surface)) << input << " not seen by subsurface after parent was unmapped and remapped"; EXPECT_THAT(input->position_on_surface(client), Eq( std::make_pair(input_offset.first - subsurface_offset.first, input_offset.second - subsurface_offset.second))) << input << " seen in the wrong place"; } TEST_P(SurfaceInputCombinations, input_seen_after_dragged_off_surface) { auto const top_left = std::make_pair(200, 49); auto const input_offset = std::make_pair(-5, 5); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto other = client.create_visible_surface(100, 100); the_server().move_surface_to(other, top_left.first - 102, top_left.second); struct wl_surface* const other_wl_surface = other; auto main = builder->build( the_server(), client, top_left, surface_size); struct wl_surface* const main_wl_surface = *main; client.roundtrip(); auto const device = input->create_device(the_server()); device->down_at({top_left.first + 5, top_left.second + 5}); client.roundtrip(); device->move_to({top_left.first + input_offset.first, top_left.second + input_offset.second}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(other_wl_surface)) << input << " seen by second surface event though it was dragged from first"; EXPECT_THAT(input->current_surface(client), Eq(main_wl_surface)) << input << " not seen by " << builder << " after being dragged away"; EXPECT_THAT(input->position_on_surface(client), Eq(input_offset)) << input << " not seen by " << builder << " after being dragged away"; } TEST_P(SurfaceInputCombinations, input_seen_by_second_surface_after_drag_off_first_and_up) { auto const top_left = std::make_pair(200, 49); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto other = builder->build( the_server(), client, std::make_pair(top_left.first - 102, top_left.second), std::make_pair(100, 100)); struct wl_surface* const other_wl_surface = *other; auto main = builder->build( the_server(), client, top_left, surface_size); struct wl_surface* const main_wl_surface = *main; client.roundtrip(); auto const device = input->create_device(the_server()); device->down_at({top_left.first + 5, top_left.second + 5}); client.roundtrip(); device->move_to({top_left.first - 80, top_left.second + 5}); client.roundtrip(); device->up(); device->down_at({top_left.first - 80, top_left.second + 5}); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(main_wl_surface)) << input << " seen by first " << builder << " after being up"; EXPECT_THAT(input->current_surface(client), Eq(other_wl_surface)) << input << " not seen by second " << builder << " after being up"; } // Will only be instantiated with toplevel surfaces class ToplevelInputCombinations : public wlcs::InProcessServer, public testing::WithParamInterface, std::shared_ptr>> { }; TEST_P(ToplevelInputCombinations, input_falls_through_surface_without_region_after_null_buffer_committed) { auto const top_left = std::make_pair(64, 49); wlcs::Client client{the_server()}; std::shared_ptr builder; std::shared_ptr input; std::tie(builder, input) = GetParam(); auto lower = client.create_visible_surface(surface_size.first, surface_size.second); the_server().move_surface_to(lower, top_left.first, top_left.second); struct wl_surface* const lower_wl_surface = lower; auto upper = builder->build( the_server(), client, top_left, surface_size); struct wl_surface* const upper_wl_surface = *upper; wl_surface_attach(upper_wl_surface, nullptr, 0, 0); wl_surface_commit(upper_wl_surface); client.roundtrip(); auto const device = input->create_device(the_server()); device->down_at(top_left); client.roundtrip(); EXPECT_THAT(input->current_surface(client), Ne(upper_wl_surface)) << input << " seen by " << builder << " after null buffer committed"; EXPECT_THAT(input->current_surface(client), Eq(lower_wl_surface)) << input << " not seen by lower toplevel after null buffer committed to " << builder; } // There's way too many region edges/surface type/input device combinations, so we don't run all of them // multi_rect_edges covers most cases, so we test it against all surface type/input device combinations // We test the rest against just XDG toplevel INSTANTIATE_TEST_SUITE_P( MultiRectEdges, RegionSurfaceInputCombinations, Combine(multi_rect_edges, all_surface_types, all_input_types)); INSTANTIATE_TEST_SUITE_P( DefaultEdges, RegionSurfaceInputCombinations, Combine(default_edges, all_surface_types, all_input_types)); INSTANTIATE_TEST_SUITE_P( FullSurface, RegionSurfaceInputCombinations, Combine(full_surface_edges, xdg_stable_surface_type, all_input_types)); INSTANTIATE_TEST_SUITE_P( SmallerRegion, RegionSurfaceInputCombinations, Combine(smaller_region_edges, xdg_stable_surface_type, all_input_types)); INSTANTIATE_TEST_SUITE_P( ClippedLargerRegion, RegionSurfaceInputCombinations, Combine(larger_region_edges, xdg_stable_surface_type, all_input_types)); INSTANTIATE_TEST_SUITE_P( MultiRectCorners, RegionSurfaceInputCombinations, Combine(multi_rect_corners, xdg_stable_surface_type, all_input_types)); INSTANTIATE_TEST_SUITE_P( SurfaceInputRegions, SurfaceInputCombinations, Combine(all_surface_types, all_input_types)); INSTANTIATE_TEST_SUITE_P( ToplevelInputRegions, ToplevelInputCombinations, Combine(toplevel_surface_types, all_input_types)); wlcs-1.7.0/tests/test_bad_buffer.cpp000066400000000000000000000122661453657357500175060ustar00rootroot00000000000000/* * Copyright © 2012 Intel Corporation * Copyright © 2013 Collabora, Ltd. * Copyright © 2017 Canonical Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include "helpers.h" #include "in_process_server.h" /* tests, that attempt to crash the compositor on purpose */ static struct wl_buffer * create_bad_shm_buffer(wlcs::Client& client, int width, int height) { struct wl_shm *shm = client.shm(); int stride = width * 4; int size = stride * height; struct wl_shm_pool *pool; struct wl_buffer *buffer; int fd; fd = wlcs::helpers::create_anonymous_file(size); pool = wl_shm_create_pool(shm, fd, size); buffer = wl_shm_pool_create_buffer( pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); wl_shm_pool_destroy(pool); /* Truncate the file to a small size, so that the compositor * will access it out-of-bounds, and hit SIGBUS. */ assert(ftruncate(fd, 12) == 0); close(fd); return buffer; } using BadBufferTest = wlcs::InProcessServer; TEST_F(BadBufferTest, test_truncated_shm_file) { using namespace testing; wlcs::Client client{the_server()}; bool buffer_consumed{false}; auto surface = client.create_visible_surface(200, 200); wl_buffer* bad_buffer = create_bad_shm_buffer(client, 200, 200); wl_surface_attach(surface, bad_buffer, 0, 0); wl_surface_damage(surface, 0, 0, 200, 200); surface.add_frame_callback([&buffer_consumed](int) { buffer_consumed = true; }); wl_surface_commit(surface); try { client.dispatch_until([&buffer_consumed]() { return buffer_consumed; }); } catch (wlcs::ProtocolError const& err) { wl_buffer_destroy(bad_buffer); EXPECT_THAT(err.error_code(), Eq(WL_SHM_ERROR_INVALID_FD)); EXPECT_THAT(err.interface(), Eq(&wl_buffer_interface)); return; } FAIL() << "Expected protocol error not raised"; } TEST_F(BadBufferTest, client_lies_about_buffer_size) { using namespace testing; wlcs::Client client{the_server()}; 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); try { /* Buffer creation should fail, so all we need is for the create_buffer * call to be processed */ client.roundtrip(); } 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_shm_pool_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"; } wlcs-1.7.0/tests/test_surface_events.cpp000066400000000000000000000412671453657357500204460ustar00rootroot00000000000000/* * Copyright © 2012 Intel Corporation * Copyright © 2013 Collabora, Ltd. * Copyright © 2017 Canonical Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "helpers.h" #include "in_process_server.h" #include #include #include #include #include using ClientSurfaceEventsTest = wlcs::InProcessServer; // //static void //check_pointer(struct client *client, int x, int y) //{ // int sx, sy; // // /* check that the client got the global pointer update */ // assert(client->test->pointer_x == x); // assert(client->test->pointer_y == y); // // /* Does global pointer map onto the surface? */ // if (surface_contains(client->surface, x, y)) { // /* check that the surface has the pointer focus */ // assert(client->input->pointer->focus == client->surface); // // /* // * check that the local surface pointer maps // * to the global pointer. // */ // sx = client->input->pointer->x + client->surface->x; // sy = client->input->pointer->y + client->surface->y; // assert(sx == x); // assert(sy == y); // } else { // /* // * The global pointer does not map onto surface. So // * check that it doesn't have the pointer focus. // */ // assert(client->input->pointer->focus == NULL); // } //} // //static void //check_pointer_move(struct client *client, int x, int y) //{ // weston_test_move_pointer(client->test->weston_test, x, y); // client_roundtrip(client); // check_pointer(client, x, y); //} // //TEST(test_pointer_surface_move) //{ // struct client *client; // // client = create_client_and_test_surface(100, 100, 100, 100); // assert(client); // // /* move pointer outside of client */ // assert(!surface_contains(client->surface, 50, 50)); // check_pointer_move(client, 50, 50); // // /* move client center to pointer */ // move_client(client, 0, 0); // assert(surface_contains(client->surface, 50, 50)); // check_pointer(client, 50, 50); //} // //static int //output_contains_client(struct client *client) //{ // struct output *output = client->output; // struct surface *surface = client->surface; // // return !(output->x >= surface->x + surface->width // || output->x + output->width <= surface->x // || output->y >= surface->y + surface->height // || output->y + output->height <= surface->y); //} // //static void //check_client_move(struct client *client, int x, int y) //{ // move_client(client, x, y); // // if (output_contains_client(client)) { // assert(client->surface->output == client->output); // } else { // assert(client->surface->output == NULL); // } //} // //TEST(test_surface_output) //{ // struct client *client; // int x, y; // // client = create_client_and_test_surface(100, 100, 100, 100); // assert(client); // // assert(output_contains_client(client)); // // /* not visible */ // x = 0; // y = -client->surface->height; // check_client_move(client, x, y); // // /* visible */ // check_client_move(client, x, ++y); // // /* not visible */ // x = -client->surface->width; // y = 0; // check_client_move(client, x, y); // // /* visible */ // check_client_move(client, ++x, y); // // /* not visible */ // x = client->output->width; // y = 0; // check_client_move(client, x, y); // // /* visible */ // check_client_move(client, --x, y); // assert(output_contains_client(client)); // // /* not visible */ // x = 0; // y = client->output->height; // check_client_move(client, x, y); // assert(!output_contains_client(client)); // // /* visible */ // check_client_move(client, x, --y); // assert(output_contains_client(client)); //} struct PointerMotion { static int constexpr window_width = 231; static int constexpr window_height = 220; std::string name; int initial_x, initial_y; // Relative to surface top-left int dx, dy; }; std::ostream& operator<<(std::ostream& out, PointerMotion const& motion) { return out << motion.name; } class SurfacePointerMotionTest : public wlcs::InProcessServer, public testing::WithParamInterface { }; TEST_P(SurfacePointerMotionTest, pointer_movement) { using namespace testing; auto pointer = the_server().create_pointer(); wlcs::Client client{the_server()}; auto const params = GetParam(); auto surface = client.create_visible_surface( params.window_width, params.window_height); int const top_left_x = 23, top_left_y = 231; the_server().move_surface_to(surface, top_left_x, top_left_y); auto const wl_surface = static_cast(surface); pointer.move_to(top_left_x + params.initial_x, top_left_y + params.initial_y); client.roundtrip(); EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface)); /* move pointer; it should now be inside the surface */ pointer.move_by(params.dx, params.dy); client.roundtrip(); EXPECT_THAT(client.window_under_cursor(), Eq(wl_surface)); EXPECT_THAT(client.pointer_position(), Eq(std::make_pair( wl_fixed_from_int(params.initial_x + params.dx), wl_fixed_from_int(params.initial_y + params.dy)))); /* move pointer back; it should now be outside the surface */ pointer.move_by(-params.dx, -params.dy); client.roundtrip(); EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface)); } INSTANTIATE_TEST_SUITE_P( PointerCrossingSurfaceCorner, SurfacePointerMotionTest, testing::Values( PointerMotion{"Top-left", -1, -1, 1, 1}, PointerMotion{"Bottom-left", -1, PointerMotion::window_height, 1, -1}, PointerMotion{"Bottom-right", PointerMotion::window_width, PointerMotion::window_height, -1, -1}, PointerMotion{"Top-right", PointerMotion::window_width, -1, -1, 1} )); INSTANTIATE_TEST_SUITE_P( PointerCrossingSurfaceEdge, SurfacePointerMotionTest, testing::Values( PointerMotion{ "Centre-left", -1, PointerMotion::window_height / 2, 1, 0}, PointerMotion{ "Bottom-centre", PointerMotion::window_width / 2, PointerMotion::window_height, 0, -1}, PointerMotion{ "Centre-right", PointerMotion::window_width, PointerMotion::window_height / 2, -1, 0}, PointerMotion{ "Top-centre", PointerMotion::window_width / 2, -1, 0, 1} )); TEST_F(ClientSurfaceEventsTest, surface_moves_under_pointer) { using namespace testing; auto pointer = the_server().create_pointer(); wlcs::Client client{the_server()}; auto surface = client.create_visible_surface(100, 100); auto const wl_surface = static_cast(surface); /* Set up the pointer outside the surface */ the_server().move_surface_to(surface, 0, 0); pointer.move_to(500, 500); client.roundtrip(); EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface)); /* move the surface so that it is under the pointer */ the_server().move_surface_to(surface, 450, 450); client.dispatch_until( [wl_surface, &client]() { return client.window_under_cursor() == wl_surface; }); EXPECT_THAT(client.window_under_cursor(), Eq(wl_surface)); EXPECT_THAT(client.pointer_position(), Eq(std::make_pair( wl_fixed_from_int(50), wl_fixed_from_int(50)))); } TEST_F(ClientSurfaceEventsTest, surface_moves_over_surface_under_pointer) { using namespace testing; auto pointer = the_server().create_pointer(); wlcs::Client client{the_server()}; auto first_surface = client.create_visible_surface(100, 100); auto second_surface = client.create_visible_surface(100, 100); /* Set up the pointer outside the surface */ the_server().move_surface_to(first_surface, 0, 0); the_server().move_surface_to(second_surface, 0, 0); pointer.move_to(500, 500); client.roundtrip(); /* move the first surface so that it is under the pointer */ the_server().move_surface_to(first_surface, 450, 450); bool first_surface_focused{false}; client.add_pointer_enter_notification( [&first_surface_focused, &first_surface](wl_surface* surf, auto, auto) { if (surf == first_surface) { first_surface_focused = true; } return false; }); // Wait until the first surface is focused client.dispatch_until( [&first_surface_focused]() { return first_surface_focused; }); client.add_pointer_leave_notification( [&first_surface_focused, &first_surface](wl_surface* surf) { if (surf == first_surface) { first_surface_focused = false; } return false; }); bool second_surface_focused{false}; client.add_pointer_enter_notification( [&first_surface_focused, &second_surface_focused, &second_surface](auto surf, auto x, auto y) { if (surf == second_surface) { /* * Protocol requires that the pointer-leave event is sent before pointer-enter */ EXPECT_FALSE(first_surface_focused); second_surface_focused = true; EXPECT_THAT(x, Eq(wl_fixed_from_int(50))); EXPECT_THAT(y, Eq(wl_fixed_from_int(50))); } return false; }); the_server().move_surface_to(second_surface, 450, 450); client.dispatch_until( [&second_surface_focused]() { return second_surface_focused; }); } TEST_F(ClientSurfaceEventsTest, surface_resizes_under_pointer) { using namespace testing; auto pointer = the_server().create_pointer(); wlcs::Client client{the_server()}; auto surface = client.create_visible_surface(100, 100); /* Set up the pointer outside the surface */ the_server().move_surface_to(surface, 400, 400); pointer.move_to(500, 500); client.roundtrip(); ASSERT_THAT(client.window_under_cursor(), Ne(static_cast(surface))); bool surface_entered{false}; client.add_pointer_enter_notification( [&surface_entered, &surface](wl_surface* entered_surf, wl_fixed_t x, wl_fixed_t y) { EXPECT_THAT(surface, Eq(entered_surf)); EXPECT_THAT(x, Eq(wl_fixed_from_int(100))); EXPECT_THAT(y, Eq(wl_fixed_from_int(100))); surface_entered = true; return false; }); client.add_pointer_leave_notification( [&surface_entered, &surface](wl_surface* left_surf) { EXPECT_THAT(surface, Eq(left_surf)); surface_entered = false; return false; }); auto larger_buffer = wlcs::ShmBuffer{client, 200, 200}; auto smaller_buffer = wlcs::ShmBuffer{client, 50, 50}; // Resize the surface so that the pointer is now over the top... wl_surface_attach(surface, larger_buffer, 0, 0); wl_surface_commit(surface); client.dispatch_until( [&surface_entered]() { return surface_entered; }); // Resize the surface so that the pointer is no longer over the top... wl_surface_attach(surface, smaller_buffer, 0, 0); wl_surface_commit(surface); client.dispatch_until( [&surface_entered]() { return !surface_entered; }); } TEST_F(ClientSurfaceEventsTest, surface_moves_while_under_pointer) { using namespace testing; auto pointer = the_server().create_pointer(); wlcs::Client client{the_server()}; auto surface = client.create_visible_surface(100, 100); the_server().move_surface_to(surface, 450, 450); pointer.move_to(500, 500); std::deque> surface_movements = { std::make_pair(445, 455), std::make_pair(460, 405), std::make_pair(420, 440), std::make_pair(430, 460), std::make_pair(0, 0) // The last motion is not checked for }; client.dispatch_until( [&client, &surface]() { if (client.window_under_cursor() == surface) { EXPECT_THAT(client.pointer_position().first, Eq(wl_fixed_from_int(50))); EXPECT_THAT(client.pointer_position().second, Eq(wl_fixed_from_int(50))); return true; } return false; }); int expected_x{55}, expected_y{45}; client.add_pointer_motion_notification( [this, &surface, &surface_movements, &expected_x, &expected_y] (wl_fixed_t x, wl_fixed_t y) { EXPECT_THAT(x, Eq(wl_fixed_from_int(expected_x))); EXPECT_THAT(y, Eq(wl_fixed_from_int(expected_y))); auto next_movement = surface_movements.front(); surface_movements.pop_front(); the_server().move_surface_to( surface, next_movement.first, next_movement.second); expected_x = 500 - next_movement.first; expected_y = 500 - next_movement.second; return true; }); // Do the initial surface move the_server().move_surface_to( surface, surface_movements.front().first, surface_movements.front().second); surface_movements.pop_front(); client.dispatch_until( [&surface_movements]() { return surface_movements.empty(); }); } TEST_F(ClientSurfaceEventsTest, frame_timestamp_increases) { using namespace testing; using namespace std::chrono_literals; wlcs::Client client{the_server()}; auto surface = client.create_visible_surface(100, 100); std::array buffers = {{ wlcs::ShmBuffer{client, 100, 100}, wlcs::ShmBuffer{client, 100, 100}, wlcs::ShmBuffer{client, 100, 100} }}; /* * The first buffer must never be released, since it is replaced before * it is committed, therefore it never becomes busy. */ wl_surface_attach(surface, buffers[0], 0, 0); wl_surface_attach(surface, buffers[1], 0, 0); uint32_t prev_frame_time = 0; int frame_callback_count = 0; auto const check_time_and_increment_count = [&](uint32_t timestamp) { EXPECT_THAT(timestamp, Gt(prev_frame_time)); prev_frame_time = timestamp; frame_callback_count++; }; surface.add_frame_callback(check_time_and_increment_count); wl_surface_commit(surface); /* We don't need to wait for the server, but we *do* need * the server to see this commit */ client.flush(); /** * When run against a *real* compositor we should not need any * delay here - when running on a real display, we would expect * the second commit to wait for the next refresh cycle. * * But we're probably not running on a real display, so make * things easier for the integration by waiting a simulated * refresh cycle (at 60Hz) before submitting the next buffer. */ std::this_thread::sleep_for(std::chrono::ceil(1.0s/60)); wl_surface_attach(surface, buffers[2], 0, 0); surface.add_frame_callback(check_time_and_increment_count); 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 wlcs-1.7.0/tests/text_input_v2_with_input_method_v1.cpp000066400000000000000000000273211453657357500234200ustar00rootroot00000000000000/* * Copyright © 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 . */ #include "in_process_server.h" #include "mock_text_input_v2.h" #include "mock_input_method_v1.h" #include "version_specifier.h" #include "method_event_impl.h" #include // NOTE: In this file, the ordering of app_client.roundtrip() and input_client.roundtrip() // is important. If we want the text input to respond to an event triggered by the input method, // we should do: // input_client.roundtrip(); // app_client.roundtrip(); // Inversely, we would swap the order if we want the input method to respond to an event triggered // by the text input. using namespace testing; struct TextInputV2WithInputMethodV1Test : wlcs::StartedInProcessServer { TextInputV2WithInputMethodV1Test() : pointer{the_server().create_pointer()}, app_client{the_server()}, input_client{the_server()}, text_input_manager{app_client.bind_if_supported(wlcs::AnyVersion)}, text_input{zwp_text_input_manager_v2_get_text_input(text_input_manager, app_client.seat())}, input_method{input_client.bind_if_supported(wlcs::AnyVersion)} { zwp_input_method_v1_add_listener(input_method, &listener, this); } void create_focused_surface() { app_surface.emplace(app_client.create_visible_surface(100, 100)); the_server().move_surface_to(app_surface.value(), 0, 0); pointer.move_to(10, 10); pointer.left_click(); app_client.roundtrip(); } MOCK_METHOD1(on_activate, void(zwp_input_method_context_v1*)); void activate(zwp_input_method_context_v1* context) { input_method_context = std::make_unique>(context); on_activate(context); } MOCK_METHOD1(deactivate, void(zwp_input_method_context_v1*)); static zwp_input_method_v1_listener constexpr listener { wlcs::method_event_impl<&TextInputV2WithInputMethodV1Test::activate>, wlcs::method_event_impl<&TextInputV2WithInputMethodV1Test::deactivate> }; void input_client_wait_for_app_client_roundtrip(std::function predicate) { app_client.roundtrip(); input_client.roundtrip(); input_client.dispatch_until(predicate); } void app_client_wait_for_input_client_roundtrip(std::function predicate) { input_client.roundtrip(); app_client.roundtrip(); app_client.dispatch_until(predicate); } void enable_text_input() { wl_surface* entered{nullptr}; EXPECT_CALL(text_input, on_enter(_, _)) .WillOnce(SaveArg<1>(&entered)); EXPECT_CALL(*this, on_activate(_)); create_focused_surface(); zwp_text_input_v2_enable(text_input, app_surface->wl_surface()); zwp_text_input_v2_update_state(text_input, text_input.serial, 0); input_client_wait_for_app_client_roundtrip([&]() { return entered != nullptr; }); } wlcs::Pointer pointer; wlcs::Client app_client; wlcs::Client input_client; wlcs::WlHandle text_input_manager; NiceMock text_input; wlcs::WlHandle input_method; std::unique_ptr> input_method_context; std::optional app_surface; }; TEST_F(TextInputV2WithInputMethodV1Test, text_input_enters_surface_on_focus) { wl_surface* entered{nullptr}; EXPECT_CALL(text_input, on_enter(_, _)) .WillOnce(SaveArg<1>(&entered)); create_focused_surface(); EXPECT_THAT(entered, Eq(app_surface.value().wl_surface())); } TEST_F(TextInputV2WithInputMethodV1Test, text_input_activates_context_on_enable) { zwp_input_method_context_v1* context = nullptr; EXPECT_CALL(*this, on_activate(_)) .WillOnce(SaveArg<0>(&context)); create_focused_surface(); zwp_text_input_v2_enable(text_input, app_surface->wl_surface()); zwp_text_input_v2_update_state(text_input, text_input.serial, 0); input_client_wait_for_app_client_roundtrip([&]() { return context != nullptr; }); } TEST_F(TextInputV2WithInputMethodV1Test, text_input_deactivates_context_on_disable) { create_focused_surface(); zwp_input_method_context_v1* context = nullptr; EXPECT_CALL(*this, on_activate(_)) .WillOnce(SaveArg<0>(&context)); zwp_text_input_v2_enable(text_input, app_surface->wl_surface()); zwp_text_input_v2_update_state(text_input, text_input.serial, 0); input_client_wait_for_app_client_roundtrip([&]() { return context != nullptr; }); bool is_deactivated = false; EXPECT_CALL(*this, deactivate(_)) .WillOnce(Invoke([&]{ is_deactivated = true; })); zwp_text_input_v2_disable(text_input, app_surface->wl_surface()); zwp_text_input_v2_update_state(text_input, text_input.serial, 0); input_client_wait_for_app_client_roundtrip([&]() { return is_deactivated; }); } TEST_F(TextInputV2WithInputMethodV1Test, setting_surrounding_text_on_text_input_triggers_a_surround_text_event_on_input_method) { auto const text = "hello"; auto const cursor = 2; auto const anchor = 1; enable_text_input(); bool is_triggered = false; EXPECT_CALL(*input_method_context, surrounding_text(text, cursor, anchor)) .WillOnce(Invoke([&]{ is_triggered = true; })); zwp_text_input_v2_set_surrounding_text(text_input, text, cursor, anchor); zwp_text_input_v2_update_state(text_input, text_input.serial, 0); input_client_wait_for_app_client_roundtrip([&]() { return is_triggered; }); } TEST_F(TextInputV2WithInputMethodV1Test, input_method_can_change_text) { auto const text = "hello"; enable_text_input(); bool has_been_committed = false; EXPECT_CALL(text_input, commit_string(text)) .WillOnce(Invoke([&]{ has_been_committed = true; })); zwp_input_method_context_v1_commit_string( *input_method_context, input_method_context->serial, text); app_client_wait_for_input_client_roundtrip([&]() { return has_been_committed; }); } TEST_F(TextInputV2WithInputMethodV1Test, input_method_can_delete_text) { const int EXPECTED_CALLBACK_COUNT = 3; auto const text = "some text"; int32_t const index = 1; int32_t const length = 2; enable_text_input(); int callback_count = 0; EXPECT_CALL(text_input, commit_string(text)) .WillOnce(Invoke([&]{ callback_count++; })); EXPECT_CALL(text_input, cursor_position(index, 0)) .WillOnce(Invoke([&]{ callback_count++; })); EXPECT_CALL(text_input, delete_surrounding_text(0, length)) .WillOnce(Invoke([&]{ callback_count++; })); input_client.roundtrip(); zwp_input_method_context_v1_delete_surrounding_text(*input_method_context, index, length); zwp_input_method_context_v1_commit_string(*input_method_context, input_method_context->serial, text); app_client_wait_for_input_client_roundtrip([&]() { return callback_count == EXPECTED_CALLBACK_COUNT; }); } TEST_F(TextInputV2WithInputMethodV1Test, input_method_can_send_keysym) { uint32_t time = 0; uint32_t sym = 65; uint32_t state = 1; uint32_t modifiers = 0; enable_text_input(); bool has_received_keysym = false; EXPECT_CALL(text_input, keysym(time, sym, state, modifiers)) .WillOnce(Invoke([&]{ has_received_keysym = true; })); zwp_input_method_context_v1_keysym( *input_method_context, input_method_context->serial, time, sym, state, modifiers); app_client_wait_for_input_client_roundtrip([&]() { return has_received_keysym; }); } TEST_F(TextInputV2WithInputMethodV1Test, input_method_can_set_preedit_string) { auto const preedit_text = "some text"; auto const preedit_commit = "some fallback text"; enable_text_input(); bool has_received_preedit_string = false; EXPECT_CALL(text_input, preedit_string(preedit_text, preedit_commit)) .WillOnce(Invoke([&]{ has_received_preedit_string = true; })); zwp_input_method_context_v1_preedit_string( *input_method_context, input_method_context->serial, preedit_text, preedit_commit); app_client_wait_for_input_client_roundtrip([&]() { return has_received_preedit_string; }); } TEST_F(TextInputV2WithInputMethodV1Test, input_method_can_set_preedit_style) { auto const preedit_text = "some text"; auto const preedit_commit = "some fallback text"; auto const index = 0; auto const length = 3; auto const style = 1; enable_text_input(); bool has_received_preedit_style = false; EXPECT_CALL(text_input, predit_styling(index, length, style)) .WillOnce(Invoke([&]{ has_received_preedit_style = true; })); zwp_input_method_context_v1_preedit_styling( *input_method_context, index, length, style); zwp_input_method_context_v1_preedit_string( *input_method_context, input_method_context->serial, preedit_text, preedit_commit); app_client_wait_for_input_client_roundtrip([&]() { return has_received_preedit_style; }); } TEST_F(TextInputV2WithInputMethodV1Test, input_method_can_set_preedit_cursor) { auto const preedit_text = "some text"; auto const preedit_commit = "some fallback text"; auto const index = 3; enable_text_input(); bool has_received_preedit_cursor = false; EXPECT_CALL(text_input, preedit_cursor(index)) .WillOnce(Invoke([&]{ has_received_preedit_cursor = true; })); zwp_input_method_context_v1_preedit_cursor( *input_method_context, index); zwp_input_method_context_v1_preedit_string( *input_method_context, input_method_context->serial, preedit_text, preedit_commit); app_client_wait_for_input_client_roundtrip([&]() { return has_received_preedit_cursor; }); } TEST_F(TextInputV2WithInputMethodV1Test, input_method_can_set_modifiers_map) { // Note: This example data was taken from the maliit-keyboard example auto const text = "hello"; const size_t data_length = 33; wl_array map; wl_array_init(&map); auto data = static_cast(wl_array_add(&map, sizeof(char) * data_length)); *data = "Shift\0Control\0Mod1\0Mod4\0Num Lock\0"; enable_text_input(); bool has_received_modifier_map = false; EXPECT_CALL(text_input, modifiers_map(_)) .WillOnce(Invoke([&]{ has_received_modifier_map = true; })); zwp_input_method_context_v1_modifiers_map( *input_method_context, &map); zwp_input_method_context_v1_commit_string(*input_method_context, input_method_context->serial, text); app_client_wait_for_input_client_roundtrip([&]() { return has_received_modifier_map; }); wl_array_release(&map); } TEST_F(TextInputV2WithInputMethodV1Test, input_method_can_set_direction) { uint32_t direction = 1; enable_text_input(); bool has_received_text_direction = false; EXPECT_CALL(text_input, text_direction(direction)) .WillOnce(Invoke([&]{ has_received_text_direction = true; })); zwp_input_method_context_v1_text_direction( *input_method_context, input_method_context->serial, direction); app_client_wait_for_input_client_roundtrip([&]() { return has_received_text_direction; }); }wlcs-1.7.0/tests/text_input_v3_with_input_method_v2.cpp000066400000000000000000000301121453657357500234120ustar00rootroot00000000000000/* * Copyright © 2021 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 "in_process_server.h" #include "mock_text_input_v3.h" #include "mock_input_method_v2.h" #include "version_specifier.h" #include "xdg_shell_stable.h" #include #include using namespace testing; struct TextInputV3WithInputMethodV2Test : wlcs::StartedInProcessServer { TextInputV3WithInputMethodV2Test() : StartedInProcessServer{}, pointer{the_server().create_pointer()}, app_client{the_server()}, text_input_manager{app_client.bind_if_supported(wlcs::AnyVersion)}, text_input{zwp_text_input_manager_v3_get_text_input(text_input_manager, app_client.seat())}, input_client{the_server()}, input_method_manager{input_client.bind_if_supported(wlcs::AnyVersion)}, input_method{zwp_input_method_manager_v2_get_input_method(input_method_manager, input_client.seat())} { } void create_focussed_surface() { app_surface.emplace(app_client.create_visible_surface(100, 100)); the_server().move_surface_to(app_surface.value(), 0, 0); pointer.move_to(10, 10); pointer.left_click(); app_client.roundtrip(); } wlcs::Pointer pointer; wlcs::Client app_client; wlcs::WlHandle text_input_manager; NiceMock text_input; std::optional app_surface; wlcs::Client input_client; wlcs::WlHandle input_method_manager; NiceMock input_method; }; TEST_F(TextInputV3WithInputMethodV2Test, text_input_enters_surface_on_focus) { wl_surface* entered{nullptr}; EXPECT_CALL(text_input, enter(_)) .WillOnce(SaveArg<0>(&entered)); create_focussed_surface(); EXPECT_THAT(entered, Eq(app_surface.value().wl_surface())); } TEST_F(TextInputV3WithInputMethodV2Test, text_input_leaves_surface_on_unfocus) { create_focussed_surface(); // Text input will not .enter() other surface because it belongs to a different client EXPECT_CALL(text_input, enter(_)).Times(0); EXPECT_CALL(text_input, leave(app_surface.value().wl_surface())); // Create a 2nd client with a focused surface wlcs::Client other_client{the_server()}; auto other_surface = other_client.create_visible_surface(100, 100); the_server().move_surface_to(other_surface, 200, 200); pointer.move_to(210, 210); pointer.left_click(); app_client.roundtrip(); } TEST_F(TextInputV3WithInputMethodV2Test, input_method_can_be_enabled) { create_focussed_surface(); InSequence seq; EXPECT_CALL(input_method, activate()); EXPECT_CALL(input_method, done()); zwp_text_input_v3_enable(text_input); zwp_text_input_v3_commit(text_input); app_client.roundtrip(); input_client.roundtrip(); } TEST_F(TextInputV3WithInputMethodV2Test, input_method_can_be_disabled) { create_focussed_surface(); InSequence seq; EXPECT_CALL(input_method, activate()); EXPECT_CALL(input_method, done()); zwp_text_input_v3_enable(text_input); zwp_text_input_v3_commit(text_input); app_client.roundtrip(); input_client.roundtrip(); EXPECT_CALL(input_method, deactivate()); EXPECT_CALL(input_method, done()); zwp_text_input_v3_disable(text_input); zwp_text_input_v3_commit(text_input); app_client.roundtrip(); input_client.roundtrip(); } TEST_F(TextInputV3WithInputMethodV2Test, input_method_disabled_when_text_input_destroyed) { create_focussed_surface(); { NiceMock text_input{ zwp_text_input_manager_v3_get_text_input(text_input_manager, app_client.seat())}; InSequence seq; EXPECT_CALL(input_method, activate()); EXPECT_CALL(input_method, done()); zwp_text_input_v3_enable(text_input); zwp_text_input_v3_commit(text_input); app_client.roundtrip(); input_client.roundtrip(); EXPECT_CALL(input_method, deactivate()); EXPECT_CALL(input_method, done()); } app_client.roundtrip(); input_client.roundtrip(); } TEST_F(TextInputV3WithInputMethodV2Test, text_field_state_can_be_set) { auto const text = "some text"; auto const cursor = 2; auto const anchor = 1; auto const change_cause = ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER; auto const content_hint = ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA; auto const content_purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME; create_focussed_surface(); Expectation a = EXPECT_CALL(input_method, activate()); Expectation b = EXPECT_CALL(input_method, surrounding_text(text, cursor, anchor)).After(a); Expectation c = EXPECT_CALL(input_method, text_change_cause(change_cause)).After(a); Expectation d = EXPECT_CALL(input_method, content_type(content_hint, content_purpose)).After(a); EXPECT_CALL(input_method, done()).After(a, b, c, d); zwp_text_input_v3_enable(text_input); zwp_text_input_v3_set_surrounding_text(text_input, text, cursor, anchor); zwp_text_input_v3_set_text_change_cause(text_input, change_cause); zwp_text_input_v3_set_content_type(text_input, content_hint, content_purpose); zwp_text_input_v3_commit(text_input); app_client.roundtrip(); input_client.roundtrip(); } TEST_F(TextInputV3WithInputMethodV2Test, input_method_can_send_text) { auto const text = "some text"; auto const delete_left = 1; auto const delete_right = 2; create_focussed_surface(); zwp_text_input_v3_enable(text_input); zwp_text_input_v3_commit(text_input); app_client.roundtrip(); input_client.roundtrip(); Expectation a = EXPECT_CALL(text_input, commit_string(text)); Expectation b = EXPECT_CALL(text_input, delete_surrounding_text(delete_left, delete_right)); // Expected serial is 1 because we've sent exactly 1 commit EXPECT_CALL(text_input, done(1)).After(a, b); zwp_input_method_v2_commit_string(input_method, text); zwp_input_method_v2_delete_surrounding_text(input_method, delete_left, delete_right); zwp_input_method_v2_commit(input_method, input_method.done_count()); input_client.roundtrip(); app_client.roundtrip(); } TEST_F(TextInputV3WithInputMethodV2Test, input_method_can_send_preedit) { auto const text = "preedit"; auto const cursor_begin = 1; auto const cursor_end = 2; create_focussed_surface(); zwp_text_input_v3_enable(text_input); zwp_text_input_v3_commit(text_input); app_client.roundtrip(); input_client.roundtrip(); InSequence seq; EXPECT_CALL(text_input, preedit_string(text, cursor_begin, cursor_end)); // Expected serial is 1 because we've sent exactly 1 commit EXPECT_CALL(text_input, done(1)); zwp_input_method_v2_set_preedit_string(input_method, text, cursor_begin, cursor_end); zwp_input_method_v2_commit(input_method, input_method.done_count()); input_client.roundtrip(); app_client.roundtrip(); } TEST_F(TextInputV3WithInputMethodV2Test, text_input_does_not_enter_non_grabbing_popup) { auto parent_surface = std::make_unique(app_client); EXPECT_CALL(text_input, enter(parent_surface->wl_surface())); auto parent_xdg_surface = std::make_shared(app_client, *parent_surface); auto parent_xdg_toplevel = std::make_shared(*parent_xdg_surface); parent_surface->attach_visible_buffer(20, 20); app_client.roundtrip(); Mock::VerifyAndClearExpectations(&text_input); auto child_surface = std::make_unique(app_client); EXPECT_CALL(text_input, leave(_)).Times(0); EXPECT_CALL(text_input, enter(_)).Times(0); auto child_xdg_surface = std::make_shared(app_client, *child_surface); auto child_xdg_popup = std::make_shared( *child_xdg_surface, parent_xdg_surface.get(), wlcs::XdgPositionerStable{app_client}.setup_default({20, 20})); child_surface->attach_visible_buffer(20, 20); app_client.roundtrip(); } TEST_F(TextInputV3WithInputMethodV2Test, text_input_enters_grabbing_popup) { InSequence seq; auto parent_surface = std::make_unique(app_client); EXPECT_CALL(text_input, enter(parent_surface->wl_surface())); auto parent_xdg_surface = std::make_shared(app_client, *parent_surface); auto parent_xdg_toplevel = std::make_shared(*parent_xdg_surface); parent_surface->attach_visible_buffer(20, 20); the_server().move_surface_to(*parent_surface, 0, 0); app_client.roundtrip(); Mock::VerifyAndClearExpectations(&text_input); // This is needed to get a serial, which will be used later on auto pointer = the_server().create_pointer(); pointer.move_to(2, 2); pointer.left_click(); app_client.roundtrip(); auto child_surface = std::make_unique(app_client); EXPECT_CALL(text_input, leave(parent_surface->wl_surface())); EXPECT_CALL(text_input, enter(child_surface->wl_surface())); auto child_xdg_surface = std::make_shared(app_client, *child_surface); auto child_xdg_popup = std::make_shared( *child_xdg_surface, parent_xdg_surface.get(), wlcs::XdgPositionerStable{app_client}.setup_default({20, 20})); xdg_popup_grab(*child_xdg_popup, app_client.seat(), app_client.latest_serial().value()); child_surface->attach_visible_buffer(20, 20); app_client.roundtrip(); } /// Regression test for https://github.com/MirServer/mir/issues/2189 TEST_F(TextInputV3WithInputMethodV2Test, text_input_enters_parent_surface_after_child_destroyed) { InSequence seq; auto parent_surface = std::make_unique(app_client); EXPECT_CALL(text_input, enter(parent_surface->wl_surface())); auto parent_xdg_surface = std::make_shared(app_client, *parent_surface); auto parent_xdg_toplevel = std::make_shared(*parent_xdg_surface); parent_surface->attach_visible_buffer(20, 20); app_client.roundtrip(); Mock::VerifyAndClearExpectations(&text_input); { auto child_surface = std::make_unique(app_client); EXPECT_CALL(text_input, leave(parent_surface->wl_surface())); EXPECT_CALL(text_input, enter(child_surface->wl_surface())); auto child_xdg_surface = std::make_shared(app_client, *child_surface); auto child_xdg_toplevel = std::make_shared(*child_xdg_surface); xdg_toplevel_set_parent(*child_xdg_toplevel, *parent_xdg_toplevel); child_surface->attach_visible_buffer(20, 20); app_client.roundtrip(); Mock::VerifyAndClearExpectations(&text_input); EXPECT_CALL(text_input, leave(nullptr)); // Child surface will be destroyed by the time the message comes in EXPECT_CALL(text_input, enter(parent_surface->wl_surface())); } app_client.roundtrip(); } wlcs-1.7.0/tests/touches.cpp000066400000000000000000000151461453657357500160420ustar00rootroot00000000000000/* * Copyright © 2018 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "helpers.h" #include "in_process_server.h" #include "xdg_shell_stable.h" #include "surface_builder.h" #include #include #include using namespace testing; class TouchTest: public wlcs::InProcessServer, public testing::WithParamInterface> { }; TEST_P(TouchTest, touch_on_surface_seen) { int const window_width = 300, window_height = 300; int const window_top_left_x = 64, window_top_left_y = 7; wlcs::Client client{the_server()}; auto const surface = GetParam()->build( the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height}); auto const wl_surface = static_cast(*surface); auto touch = the_server().create_touch(); int touch_x = window_top_left_x + 27; int touch_y = window_top_left_y + 8; touch.down_at(touch_x, touch_y); client.roundtrip(); ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface"; ASSERT_THAT(client.touch_position(), Eq(std::make_pair( wl_fixed_from_int(touch_x - window_top_left_x), wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch came down in the wrong place"; touch.up(); client.roundtrip(); } TEST_P(TouchTest, touch_and_drag_on_surface_seen) { int const window_width = 300, window_height = 300; int const window_top_left_x = 64, window_top_left_y = 12; int const touch_x = window_top_left_x + 27, touch_y = window_top_left_y + 140; int const dx = 37, dy = -52; wlcs::Client client{the_server()}; auto const surface = GetParam()->build( the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height}); auto const wl_surface = static_cast(*surface); auto touch = the_server().create_touch(); touch.down_at(touch_x, touch_y); client.roundtrip(); ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface"; ASSERT_THAT(client.touch_position(), Eq(std::make_pair( wl_fixed_from_int(touch_x - window_top_left_x), wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch came down in the wrong place"; touch.move_to(touch_x + dx, touch_y + dy); client.roundtrip(); ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "surface was unfocused when it shouldn't have been"; ASSERT_THAT(client.touch_position(), Ne(std::make_pair( wl_fixed_from_int(touch_x - window_top_left_x), wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch did not move"; ASSERT_THAT(client.touch_position(), Eq(std::make_pair( wl_fixed_from_int(touch_x - window_top_left_x + dx), wl_fixed_from_int(touch_y - window_top_left_y + dy)))) << "touch did not end up in the right place"; touch.up(); client.roundtrip(); } TEST_P(TouchTest, touch_drag_outside_of_surface_and_back_not_lost) { int const window_width = 300, window_height = 300; int const window_top_left_x = 64, window_top_left_y = 12; int const touch_a_x = window_top_left_x + 27, touch_a_y = window_top_left_y + 12; int const touch_b_x = window_top_left_x - 6, touch_b_y = window_top_left_y + window_width + 8; wlcs::Client client{the_server()}; auto const surface = GetParam()->build( the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height}); auto const wl_surface = static_cast(*surface); auto touch = the_server().create_touch(); touch.down_at(touch_a_x, touch_a_y); client.roundtrip(); ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface"; ASSERT_THAT(client.touch_position(), Eq(std::make_pair( wl_fixed_from_int(touch_a_x - window_top_left_x), wl_fixed_from_int(touch_a_y - window_top_left_y)))) << "touch came down in the wrong place"; touch.move_to(touch_b_x, touch_b_y); client.roundtrip(); EXPECT_THAT(client.touched_window(), Eq(wl_surface)) << "touch was lost when it moved out of the surface"; if (client.touched_window() == wl_surface) { EXPECT_THAT(client.touch_position(), Eq(std::make_pair( wl_fixed_from_int(touch_b_x - window_top_left_x), wl_fixed_from_int(touch_b_y - window_top_left_y)))) << "touch did not end up in the right place outside of the surface"; } touch.move_to(touch_a_x, touch_a_y); client.roundtrip(); EXPECT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not come back onto surface"; EXPECT_THAT(client.touch_position(), Eq(std::make_pair( wl_fixed_from_int(touch_a_x - window_top_left_x), wl_fixed_from_int(touch_a_y - window_top_left_y)))) << "touch came back in the wrong place"; touch.up(); client.roundtrip(); } TEST_P(TouchTest, sends_touch_up_on_surface_destroy) { int const window_width = 300, window_height = 300; int const window_top_left_x = 64, window_top_left_y = 7; wlcs::Client client{the_server()}; auto surface = GetParam()->build( the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height}); auto touch = the_server().create_touch(); int touch_x = window_top_left_x + 27; int touch_y = window_top_left_y + 8; touch.down_at(touch_x, touch_y); client.roundtrip(); surface.reset(); client.roundtrip(); ASSERT_THAT(client.touched_window(), Eq(nullptr)) << "Touch did not leave surface when surface was destroyed"; } INSTANTIATE_TEST_SUITE_P( AllSurfaceTypes, TouchTest, ValuesIn(wlcs::SurfaceBuilder::all_surface_types()), wlcs::SubsurfaceBuilder::surface_builder_to_string); wlcs-1.7.0/tests/wl_output.cpp000066400000000000000000000031231453657357500164220ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "helpers.h" #include "in_process_server.h" #include "version_specifier.h" #include #include using namespace testing; using namespace wlcs; using WlOutputTest = wlcs::InProcessServer; TEST_F(WlOutputTest, wl_output_properties_set) { wlcs::Client client{the_server()}; ASSERT_THAT(client.output_count(), Ge(1u)); auto output = client.output_state(0); EXPECT_THAT((bool)output.geometry_position, Eq(true)); EXPECT_THAT((bool)output.mode_size, Eq(true)); EXPECT_THAT((bool)output.scale, Eq(true)); } TEST_F(WlOutputTest, wl_output_release) { wlcs::Client client{the_server()}; { // Acquire *any* wl_output; we don't care which auto const output = client.bind_if_supported(wlcs::AtLeastVersion{WL_OUTPUT_RELEASE_SINCE_VERSION}); client.roundtrip(); } // output is now released client.roundtrip(); } wlcs-1.7.0/tests/wlr_foreign_toplevel_management_v1.cpp000066400000000000000000000711721453657357500234220ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "helpers.h" #include "version_specifier.h" #include "in_process_server.h" #include "xdg_shell_stable.h" #include "generated/wlr-foreign-toplevel-management-unstable-v1-client.h" #include #include #include using namespace testing; namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_foreign_toplevel_manager_v1) WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_foreign_toplevel_handle_v1) } namespace { int const w = 10, h = 15; class ForeignToplevelHandle { public: ForeignToplevelHandle(zwlr_foreign_toplevel_handle_v1* handle); ForeignToplevelHandle(ForeignToplevelHandle const&) = delete; ForeignToplevelHandle& operator=(ForeignToplevelHandle const&) = delete; auto is_dirty() const { return dirty_; } auto title() const { return title_; } auto app_id() const { return app_id_; } auto outputs() const -> std::vector const& { return outputs_; } auto maximized() const { return maximized_; } auto minimized() const { return minimized_; } auto activated() const { return activated_; } auto fullscreen() const { return fullscreen_; } auto destroyed() const { return destroyed_; } operator zwlr_foreign_toplevel_handle_v1*() const { return handle; } wlcs::WlHandle const handle; private: static auto get_self(void* data) -> ForeignToplevelHandle* { return static_cast(data); } bool dirty_{false}; std::optional title_; std::optional app_id_; std::vector outputs_; bool maximized_{false}, minimized_{false}, activated_{false}, fullscreen_{false}; bool destroyed_{false}; }; ForeignToplevelHandle::ForeignToplevelHandle(zwlr_foreign_toplevel_handle_v1* handle) : handle{handle} { static zwlr_foreign_toplevel_handle_v1_listener const listener = { [] /* title */ ( void* data, zwlr_foreign_toplevel_handle_v1*, char const* title) { auto self = get_self(data); self->title_ = title; self->dirty_ = true; }, [] /* app_id */ ( void* data, zwlr_foreign_toplevel_handle_v1*, char const* app_id) { auto self = get_self(data); self->app_id_ = app_id; self->dirty_ = true; }, [] /* output_enter */ ( void* data, zwlr_foreign_toplevel_handle_v1*, wl_output* output) { auto self = get_self(data); self->outputs_.push_back(output); self->dirty_ = true; }, [] /* output_leave */ ( void* data, zwlr_foreign_toplevel_handle_v1*, wl_output* output) { auto self = get_self(data); std::remove( self->outputs_.begin(), self->outputs_.end(), output); self->dirty_ = true; }, [] /*state */ ( void* data, zwlr_foreign_toplevel_handle_v1*, wl_array* state) { auto self = get_self(data); self->maximized_ = false; self->minimized_ = false; self->activated_ = false; self->fullscreen_ = false; for (auto item = static_cast(state->data); (char*)item < static_cast(state->data) + state->size; item++) { switch (*item) { case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: self->maximized_ = true; break; case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: self->minimized_ = true; break; case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: self->activated_ = true; break; case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: self->fullscreen_ = true; break; } } self->dirty_ = true; }, [] /* done */ ( void* data, zwlr_foreign_toplevel_handle_v1*) { auto self = get_self(data); self->dirty_ = false; }, [] /* closed */ ( void* data, zwlr_foreign_toplevel_handle_v1*) { auto self = get_self(data); self->destroyed_ = true; self->dirty_ = false; } }; zwlr_foreign_toplevel_handle_v1_add_listener(handle, &listener, this); } class ForeignToplevelManager { public: ForeignToplevelManager(wlcs::Client& client); auto toplevels() -> std::vector> const& { toplevels_.erase( std::remove_if( toplevels_.begin(), toplevels_.end(), [](auto const& toplevel) { return toplevel->destroyed(); }), toplevels_.end()); return toplevels_; } wlcs::WlHandle const manager; private: std::vector> toplevels_; }; ForeignToplevelManager::ForeignToplevelManager(wlcs::Client& client) : manager{client.bind_if_supported(wlcs::AnyVersion)} { static zwlr_foreign_toplevel_manager_v1_listener const listener = { +[] /* toplevel */ ( void* data, zwlr_foreign_toplevel_manager_v1*, zwlr_foreign_toplevel_handle_v1* toplevel) { auto self = static_cast(data); auto handle = std::make_unique(toplevel); self->toplevels_.push_back(std::move(handle)); }, +[] /* finished */ ( void* /*data*/, zwlr_foreign_toplevel_manager_v1 *) { } }; zwlr_foreign_toplevel_manager_v1_add_listener(manager, &listener, this); } class ForeignToplevelManagerTest : public wlcs::StartedInProcessServer { }; class ForeignToplevelHandleTest : public wlcs::StartedInProcessServer { public: ForeignToplevelHandleTest() : client{the_server()}, manager{client}, surface{client}, xdg_surface{client, surface}, xdg_toplevel{xdg_surface} { } auto toplevel() -> ForeignToplevelHandle const& { if (manager.toplevels().empty()) BOOST_THROW_EXCEPTION(std::runtime_error("Manager does not know about any toplevels")); if (manager.toplevels().size() > 1u) BOOST_THROW_EXCEPTION(std::runtime_error( "Manager knows about " + std::to_string(manager.toplevels().size()) + " toplevels")); if (manager.toplevels()[0]->is_dirty()) BOOST_THROW_EXCEPTION(std::runtime_error("Toplevel has pending updates")); return *manager.toplevels()[0]; } auto toplevel(std::string const& app_id) -> ForeignToplevelHandle const& { std::optional match; for (auto const& i : manager.toplevels()) { if (i->app_id() == app_id) { if (match) BOOST_THROW_EXCEPTION(std::runtime_error("Multiple toplevels have the same app ID " + app_id)); else match = i.get(); } } if (!match) BOOST_THROW_EXCEPTION(std::runtime_error("No toplevels have the app ID " + app_id)); if (match.value()->is_dirty()) BOOST_THROW_EXCEPTION(std::runtime_error("Toplevel has pending updates")); return *match.value(); } wlcs::Client client; ForeignToplevelManager manager; wlcs::Surface surface; wlcs::XdgSurfaceStable xdg_surface; wlcs::XdgToplevelStable xdg_toplevel; }; } TEST_F(ForeignToplevelManagerTest, does_not_detect_toplevels_when_test_creates_none) { wlcs::Client client{the_server()}; ForeignToplevelManager manager{client}; client.roundtrip(); ASSERT_THAT(manager.toplevels().size(), Eq(0u)); } TEST_F(ForeignToplevelManagerTest, detects_toplevel_from_same_client) { wlcs::Client client{the_server()}; auto surface{client.create_visible_surface(w, h)}; ForeignToplevelManager manager{client}; client.roundtrip(); ASSERT_THAT(manager.toplevels().size(), Eq(1u)); } TEST_F(ForeignToplevelManagerTest, detects_toplevel_from_different_client) { wlcs::Client foreign_client{the_server()}; wlcs::Client observer_client{the_server()}; auto surface{foreign_client.create_visible_surface(w, h)}; ForeignToplevelManager manager{observer_client}; observer_client.roundtrip(); ASSERT_THAT(manager.toplevels().size(), Eq(1u)); } TEST_F(ForeignToplevelManagerTest, detects_toplevel_created_after_manager) { wlcs::Client client{the_server()}; ForeignToplevelManager manager{client}; client.roundtrip(); ASSERT_THAT(manager.toplevels().size(), Eq(0u)); auto surface{client.create_visible_surface(w, h)}; client.roundtrip(); ASSERT_THAT(manager.toplevels().size(), Eq(1u)); } TEST_F(ForeignToplevelManagerTest, detects_multiple_toplevels_from_multiple_clients) { wlcs::Client foreign_client{the_server()}; wlcs::Client observer_client{the_server()}; auto foreign_surface{foreign_client.create_visible_surface(w, h)}; auto observer_surface{observer_client.create_visible_surface(w, h)}; ForeignToplevelManager manager{observer_client}; observer_client.roundtrip(); ASSERT_THAT(manager.toplevels().size(), Eq(2u)); } TEST_F(ForeignToplevelManagerTest, detects_toplevel_closed) { wlcs::Client client{the_server()}; ForeignToplevelManager manager{client}; client.roundtrip(); EXPECT_THAT(manager.toplevels().size(), Eq(0u)); { wlcs::Surface other{client}; wlcs::XdgSurfaceStable other_xdg{client, other}; wlcs::XdgToplevelStable other_toplevel{other_xdg}; other.attach_visible_buffer(w, h); client.roundtrip(); EXPECT_THAT(manager.toplevels().size(), Eq(1u)); } client.roundtrip(); ASSERT_THAT(manager.toplevels().size(), Eq(0u)); } TEST_F(ForeignToplevelHandleTest, gets_title) { std::string title = "Test Title @!\\-"; xdg_toplevel_set_title(xdg_toplevel, title.c_str()); surface.attach_visible_buffer(w, h); client.roundtrip(); ASSERT_THAT((bool)toplevel().title(), Eq(true)); EXPECT_THAT(toplevel().title().value(), Eq(title)); } TEST_F(ForeignToplevelHandleTest, title_gets_updated) { std::string title_a = "Test Title @!\\-"; std::string title_b = "Title 2"; xdg_toplevel_set_title(xdg_toplevel, title_a.c_str()); surface.attach_visible_buffer(w, h); client.roundtrip(); ASSERT_THAT((bool)toplevel().title(), Eq(true)); ASSERT_THAT(toplevel().title().value(), Eq(title_a)); xdg_toplevel_set_title(xdg_toplevel, title_b.c_str()); surface.attach_visible_buffer(w, h); client.roundtrip(); ASSERT_THAT((bool)toplevel().title(), Eq(true)); EXPECT_THAT(toplevel().title().value(), Eq(title_b)); } TEST_F(ForeignToplevelHandleTest, gets_app_id) { std::string app_id = "fake.wlcs.app.id"; xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str()); surface.attach_visible_buffer(w, h); client.roundtrip(); ASSERT_THAT((bool)toplevel().app_id(), Eq(true)); EXPECT_THAT(toplevel().app_id().value(), Eq(app_id)); } TEST_F(ForeignToplevelHandleTest, gets_maximized) { xdg_toplevel_set_maximized(xdg_toplevel); surface.attach_visible_buffer(w, h); client.roundtrip(); EXPECT_THAT(toplevel().maximized(), Eq(true)); xdg_toplevel_unset_maximized(xdg_toplevel); surface.attach_visible_buffer(w, h); client.roundtrip(); EXPECT_THAT(toplevel().maximized(), Eq(false)); } TEST_F(ForeignToplevelHandleTest, gets_minimized) { surface.attach_visible_buffer(w, h); client.roundtrip(); EXPECT_THAT(toplevel().minimized(), Eq(false)); xdg_toplevel_set_minimized(xdg_toplevel); wl_surface_commit(surface); client.roundtrip(); EXPECT_THAT(toplevel().minimized(), Eq(true)); } TEST_F(ForeignToplevelHandleTest, gets_fullscreen) { xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr); surface.attach_visible_buffer(w, h); client.roundtrip(); EXPECT_THAT(toplevel().fullscreen(), Eq(true)); xdg_toplevel_unset_fullscreen(xdg_toplevel); surface.attach_visible_buffer(w, h); client.roundtrip(); EXPECT_THAT(toplevel().maximized(), Eq(false)); } TEST_F(ForeignToplevelHandleTest, gets_activated) { std::string app_id = "fake.wlcs.app.id"; xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str()); surface.attach_visible_buffer(w, h); client.roundtrip(); EXPECT_THAT(toplevel().activated(), Eq(true)); std::string other_app_id = "other.app.id"; wlcs::Surface other{client}; wlcs::XdgSurfaceStable other_xdg{client, other}; wlcs::XdgToplevelStable other_toplevel{other_xdg}; xdg_toplevel_set_app_id(other_toplevel, other_app_id.c_str()); other.attach_visible_buffer(w, h); client.roundtrip(); EXPECT_THAT(toplevel(app_id).activated(), Eq(false)); EXPECT_THAT(toplevel(other_app_id).activated(), Eq(true)); } TEST_F(ForeignToplevelHandleTest, can_maximize_foreign) { wlcs::XdgToplevelStable::State state{0, 0, nullptr}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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}; ON_CALL(xdg_toplevel, configure).WillByDefault([&state](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); 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) { EXPECT_CALL(xdg_toplevel, close()).Times(0); surface.attach_visible_buffer(w, h); client.roundtrip(); EXPECT_CALL(xdg_toplevel, close()).Times(1); zwlr_foreign_toplevel_handle_v1_close(toplevel()); client.roundtrip(); } // 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 wlcs-1.7.0/tests/wlr_layer_shell_v1.cpp000066400000000000000000001246171453657357500201710ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "helpers.h" #include "in_process_server.h" #include "layer_shell_v1.h" #include "xdg_shell_stable.h" #include "version_specifier.h" #include "geometry/rectangle.h" #include using namespace testing; using wlcs::X, wlcs::Y, wlcs::DeltaX, wlcs::DeltaY, wlcs::Width, wlcs::Height, wlcs::Size, wlcs::Point, wlcs::Rectangle; 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(Point pos, wlcs::Surface const& expected) { auto pointer = the_server().create_pointer(); pointer.move_to(pos.x.as_int() + 10, pos.y.as_int() + 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(Point pos) { expect_surface_is_at_position(pos, surface); } auto output_rect() const -> Rectangle { 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(); } auto pos = output_state.geometry_position.value(); return {{pos.first, pos.second}, {size.first, size.second}}; } wlcs::Client client; wlcs::Surface surface; wlcs::LayerSurfaceV1 layer_surface; Size static constexpr default_size{200, 300}; int static const default_width = default_size.width.as_int(); int static const default_height = default_size.height.as_int(); }; struct LayerSurfaceLayout { struct Anchor { bool left, right, top, bottom; }; struct Margin { DeltaX left, right; DeltaY top, bottom; }; static auto get_all() -> std::vector { bool const range[]{false, true}; std::vector result; for (auto left: range) { for (auto right: range) { for (auto top: range) { for (auto bottom: range) { result.emplace_back(Anchor{left, right, top, bottom}); result.emplace_back( Anchor{left, right, top, bottom}, Margin{DeltaX{6}, DeltaX{9}, DeltaY{12}, DeltaY{15}}); } } } } return result; } LayerSurfaceLayout(Anchor anchor) : anchor{anchor}, margin{{}, {}, {}, {}} { } LayerSurfaceLayout(Anchor anchor, Margin 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(Rectangle const& output) const -> Rectangle { auto const config_size = configure_size(output); auto const width = config_size.width.as_int() ? config_size.width : LayerSurfaceTest::default_size.width; auto const height = config_size.height.as_int() ? config_size.height : LayerSurfaceTest::default_size.height; X const x = (anchor.left ? output.top_left.x + margin.left : (anchor.right ? (output.top_left.x + as_delta(output.size.width) - as_delta(width) - margin.right) : (output.top_left.x + (output.size.width - width) / 2) ) ); Y const y = (anchor.top ? output.top_left.y + margin.top : (anchor.bottom ? (output.top_left.y + as_delta(output.size.height) - as_delta(height) - margin.bottom) : (output.top_left.y + (output.size.height - height) / 2) ) ); return {{x, y}, {width, height}}; } auto request_size() const -> Size { return { h_expand() ? Width{} : LayerSurfaceTest::default_size.width, v_expand() ? Height{} : LayerSurfaceTest::default_size.height}; } auto configure_size(Rectangle const& output) const -> Size { auto const configure_width = h_expand() ? output.size.width - margin.left - margin.right : Width{}; auto const configure_height = v_expand() ? output.size.height - margin.top - margin.bottom : Height{}; return {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; } Anchor const anchor; Margin const margin; }; static void invoke_zwlr_layer_surface_v1_set_margin( zwlr_layer_surface_v1* layer_surface, LayerSurfaceLayout::Margin const& margin) { zwlr_layer_surface_v1_set_margin( layer_surface, margin.top.as_int(), margin.right.as_int(), margin.bottom.as_int(), margin.left.as_int()); } std::ostream& operator<<(std::ostream& os, const LayerSurfaceLayout& layout) { std::vector strs; if (layout.anchor.left) strs.emplace_back("left"); if (layout.anchor.right) strs.emplace_back("right"); if (layout.anchor.top) strs.emplace_back("top"); if (layout.anchor.bottom) strs.emplace_back("bottom"); if (strs.empty()) strs.emplace_back("none"); os << "Anchor{"; for (auto i = 0u; i < strs.size(); i++) { if (i > 0) os << " | "; os << strs[i]; } os << "}, Margin{"; if (layout.margin.left.as_int() || layout.margin.right.as_int() || layout.margin.top.as_int() || layout.margin.bottom.as_int()) { os << "l: " << layout.margin.left << ", "; os << "r: " << layout.margin.right << ", "; os << "t: " << layout.margin.top << ", "; os << "b: " << layout.margin.bottom; } os << "}"; return os; } class LayerSurfaceLayoutTest: public LayerSurfaceTest, public testing::WithParamInterface { }; struct LayerLayerParams { std::optional below; std::optional above; }; std::ostream& operator<<(std::ostream& os, const LayerLayerParams& layer) { os << "layers:"; for (auto const& i : {layer.below, layer.above}) { os << " "; os << "Layer{"; if (i) { switch (auto const value = i.value()) { case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: os << "background"; break; case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: os << "bottom"; break; case ZWLR_LAYER_SHELL_V1_LAYER_TOP: os << "top"; break; case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: os << "overlay"; break; default: os << "INVALID(" << value << ")"; } } else { os << "none"; } os << "}"; } return os; } class LayerSurfaceLayerTest: public wlcs::StartedInProcessServer, public testing::WithParamInterface { public: struct SurfaceOnLayer { SurfaceOnLayer( wlcs::Client& client, std::optional layer) : surface{layer ? wlcs::Surface{client} : client.create_visible_surface(width, height)} { if (layer) { layer_surface.emplace(client, surface, layer.value()); zwlr_layer_surface_v1_set_size(layer_surface.value(), width, height); surface.attach_visible_buffer(width, height); } } wlcs::Surface surface; std::optional layer_surface; }; LayerSurfaceLayerTest() : client(the_server()) { } static const int width{200}, height{100}; wlcs::Client client; }; struct SizeAndAnchors { uint32_t width, height; LayerSurfaceLayout anchors; }; class LayerSurfaceErrorsTest: public LayerSurfaceTest, public testing::WithParamInterface { }; } TEST_F(LayerSurfaceTest, specifying_no_size_without_anchors_is_an_error) { try { // Protocol specifies that a size of (0,0) is the default commit_and_wait_for_configure(); } catch (wlcs::ProtocolError const& err) { EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface)); // The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE)); return; } FAIL() << "Expected protocol error not raised"; } TEST_F(LayerSurfaceTest, specifying_zero_size_without_anchors_is_an_error) { try { zwlr_layer_surface_v1_set_size(layer_surface, 0, 0); commit_and_wait_for_configure(); } catch (wlcs::ProtocolError const& err) { EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface)); // The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE)); return; } FAIL() << "Expected protocol error not raised"; } TEST_P(LayerSurfaceErrorsTest, specifying_zero_size_without_corresponding_anchors_is_an_error) { try { zwlr_layer_surface_v1_set_size(layer_surface, GetParam().width, GetParam().height); zwlr_layer_surface_v1_set_anchor(layer_surface, GetParam().anchors); commit_and_wait_for_configure(); } catch (wlcs::ProtocolError const& err) { EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface)); // The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE)); return; } FAIL() << "Expected protocol error not raised"; } INSTANTIATE_TEST_SUITE_P( Anchors, LayerSurfaceErrorsTest, testing::Values( SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, false, false}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, false, false}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, false, false}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, false, false}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, true, false}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, true, false}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, true, false}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, true, false}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, false, true}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, false, true}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, false, true}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, false, true}}}, SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, true, true}}}, SizeAndAnchors{200, 0, LayerSurfaceLayout{{false, false, false, true}}}, SizeAndAnchors{200, 0, LayerSurfaceLayout{{false, false, true, false}}}, SizeAndAnchors{0, 200, LayerSurfaceLayout{{true, false, false, true}}}, SizeAndAnchors{0, 200, LayerSurfaceLayout{{false, true, true, false}}} )); TEST_F(LayerSurfaceTest, can_open_layer_surface) { zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height); commit_and_wait_for_configure(); } TEST_F(LayerSurfaceTest, gets_configured_with_supplied_size_when_set) { int width = 123, height = 546; zwlr_layer_surface_v1_set_size(layer_surface, width, height); commit_and_wait_for_configure(); EXPECT_THAT(layer_surface.last_size(), Eq(Size{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(layer_surface.last_size(), Eq(Size{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(); ASSERT_THAT(layer_surface.last_size(), Eq(output_rect().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(layer_surface.last_size().width, Gt(Width{})); EXPECT_THAT(layer_surface.last_size().height, Gt(Height{})); } TEST_F(LayerSurfaceTest, destroy_request_supported) { wlcs::Client client{the_server()}; { auto const layer_shell = client.bind_if_supported( 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( wlcs::ExactlyVersion{ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION - 1}); client.roundtrip(); } // layer_shell is now destroyed client.roundtrip(); } TEST_F(LayerSurfaceTest, does_not_take_keyboard_focus_without_keyboard_interactivity) { auto normal_surface = client.create_visible_surface(100, 100); the_server().move_surface_to(normal_surface, 0, default_height); auto pointer = the_server().create_pointer(); pointer.move_to(5, default_height + 5); pointer.left_click(); client.roundtrip(); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(normal_surface))) << "Could not run test because normal surface was not given keyboeard focus"; zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height); zwlr_layer_surface_v1_set_anchor( layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); commit_and_wait_for_configure(); surface.attach_visible_buffer(default_width, default_height); EXPECT_THAT(client.keyboard_focused_window(), Eq(static_cast(normal_surface))) << "Normal surface lost keyboard focus when non-keyboard layer surface appeared"; } TEST_F(LayerSurfaceTest, takes_keyboard_focus_with_exclusive_keyboard_interactivity) { auto normal_surface = client.create_visible_surface(100, 100); the_server().move_surface_to(normal_surface, 0, default_height); auto pointer = the_server().create_pointer(); pointer.move_to(5, default_height + 5); pointer.left_click(); client.roundtrip(); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(normal_surface))) << "Could not run test because normal surface was not given keyboeard focus"; zwlr_layer_surface_v1_set_keyboard_interactivity( layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE); zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height); zwlr_layer_surface_v1_set_anchor( layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); commit_and_wait_for_configure(); surface.attach_visible_buffer(default_width, default_height); EXPECT_THAT(client.keyboard_focused_window(), Eq(static_cast(surface))) << "Layer surface not given keyboard focus in exclusive mode"; } TEST_F(LayerSurfaceTest, takes_keyboard_focus_after_click_with_on_demand_keyboard_interactivity) { auto normal_surface = client.create_visible_surface(100, 100); the_server().move_surface_to(normal_surface, 0, default_height); auto pointer = the_server().create_pointer(); pointer.move_to(5, default_height + 5); pointer.left_click(); client.roundtrip(); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(normal_surface))) << "Could not run test because normal surface was not given keyboeard focus"; { wlcs::Client client{the_server()}; auto const layer_shell = client.bind_if_supported( wlcs::AtLeastVersion{ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION}); client.roundtrip(); } zwlr_layer_surface_v1_set_keyboard_interactivity( layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND); zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height); zwlr_layer_surface_v1_set_anchor( layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); commit_and_wait_for_configure(); surface.attach_visible_buffer(default_width, default_height); client.roundtrip(); pointer.move_to(5, 5); pointer.left_click(); client.roundtrip(); EXPECT_THAT(client.keyboard_focused_window(), Eq(static_cast(surface))) << "Layer surface not given keyboard focus in on-demand mode"; } TEST_F(LayerSurfaceTest, does_not_lose_keyboard_focus_with_exclusive_keyboard_interactivity) { zwlr_layer_surface_v1_set_keyboard_interactivity( layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE); zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height); zwlr_layer_surface_v1_set_anchor( layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); commit_and_wait_for_configure(); surface.attach_visible_buffer(default_width, default_height); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(surface))) << "Layer surface not given keyboard focus in exclusive mode"; auto normal_surface = client.create_visible_surface(100, 100); the_server().move_surface_to(normal_surface, 0, default_height); auto pointer = the_server().create_pointer(); pointer.move_to(5, default_height + 5); pointer.left_click(); client.roundtrip(); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(surface))) << "Creating a normal surface caused the layer surface in exclusive mode to lose keyboard focus"; } TEST_F(LayerSurfaceTest, can_lose_keyboard_focus_with_on_demand_keyboard_interactivity) { { wlcs::Client client{the_server()}; auto const layer_shell = client.bind_if_supported( wlcs::AtLeastVersion{ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION}); client.roundtrip(); } zwlr_layer_surface_v1_set_keyboard_interactivity( layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND); zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height); zwlr_layer_surface_v1_set_anchor( layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); commit_and_wait_for_configure(); surface.attach_visible_buffer(default_width, default_height); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(surface))) << "Layer surface not given keyboard focus in exclusive mode"; auto normal_surface = client.create_visible_surface(100, 100); the_server().move_surface_to(normal_surface, 0, default_height); auto pointer = the_server().create_pointer(); pointer.move_to(5, default_height + 5); pointer.left_click(); client.roundtrip(); ASSERT_THAT(client.keyboard_focused_window(), Ne(static_cast(surface))) << "Creating a normal surface did not cause the layer surface in on-demand mode to lose keyboard focus"; } TEST_F(LayerSurfaceTest, takes_keyboard_focus_when_interactivity_changes_to_exclusive) { auto normal_surface = client.create_visible_surface(100, 100); the_server().move_surface_to(normal_surface, 0, default_height); auto pointer = the_server().create_pointer(); pointer.move_to(5, default_height + 5); pointer.left_click(); client.roundtrip(); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(normal_surface))) << "Could not run test because normal surface was not given keyboeard focus"; zwlr_layer_surface_v1_set_keyboard_interactivity( layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height); zwlr_layer_surface_v1_set_anchor( layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); commit_and_wait_for_configure(); surface.attach_visible_buffer(default_width, default_height); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(normal_surface))) << "Could not run test because normal surface was not given keyboeard focus"; zwlr_layer_surface_v1_set_keyboard_interactivity( layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE); wl_surface_commit(surface); client.roundtrip(); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(surface))) << "Layer surface did not take keyboard focus when mode changed to exclusive"; pointer.left_click(); client.roundtrip(); EXPECT_THAT(client.keyboard_focused_window(), Eq(static_cast(surface))) << "Layer surface did not hold exclusive keyboard focus"; } TEST_F(LayerSurfaceTest, loses_keybaord_focus_when_interactivity_changes_to_none) { auto normal_surface = client.create_visible_surface(100, 100); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(normal_surface))) << "Could not run test because normal surface was not given keyboeard focus"; zwlr_layer_surface_v1_set_keyboard_interactivity( layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE); zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height); zwlr_layer_surface_v1_set_anchor( layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); commit_and_wait_for_configure(); surface.attach_visible_buffer(default_width, default_height); ASSERT_THAT(client.keyboard_focused_window(), Eq(static_cast(surface))) << "Could not run test because surface not given keyboard focus despite exclusive interactivity"; zwlr_layer_surface_v1_set_keyboard_interactivity( layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); wl_surface_commit(surface); client.roundtrip(); ASSERT_THAT(client.keyboard_focused_window(), Ne(static_cast(surface))) << "Layer surface did not lose keyboard focus when interactivity changed to none"; } 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.width.as_int(), request_size.height.as_int()); commit_and_wait_for_configure(); auto configure_size = layout.configure_size(output); if (configure_size.width.as_int()) { EXPECT_THAT(layer_surface.last_size().width, Eq(configure_size.width)); } if (configure_size.height.as_int()) { EXPECT_THAT(layer_surface.last_size().height, Eq(configure_size.height)); } surface.attach_visible_buffer(rect.size.width.as_int(), rect.size.height.as_int()); expect_surface_is_at_position(rect.top_left); } 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.width.as_int(), request_size.height.as_int()); commit_and_wait_for_configure(); surface.attach_visible_buffer(initial_width, initial_height); expect_surface_is_at_position(rect.top_left); } 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.width.as_int(), request_size.height.as_int()); surface.attach_visible_buffer(result_rect.size.width.as_int(), result_rect.size.height.as_int()); 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.width.as_int()) { EXPECT_THAT(layer_surface.last_size().width, Eq(configure_size.width)); } if (configure_size.height.as_int()) { EXPECT_THAT(layer_surface.last_size().height, Eq(configure_size.height)); } expect_surface_is_at_position(result_rect.top_left); } 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.width.as_int(), request_size.height.as_int()); surface.attach_visible_buffer(result_rect.size.width.as_int(), result_rect.size.height.as_int()); 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(layer_surface.last_size().width, Eq(default_size.width)); EXPECT_THAT(layer_surface.last_size().height, Eq(default_size.height)); auto const expected_top_left = output.top_left + as_displacement( as_size(as_displacement(output.size) - as_displacement(default_size)) / 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.width.as_int(), request_size.height.as_int()); commit_and_wait_for_configure(); surface.attach_visible_buffer(initial_rect.size.width.as_int(), initial_rect.size.height.as_int()); // 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(); auto non_exlusive_zone = output_rect(); non_exlusive_zone.top_left.x += DeltaX{exclusive}; non_exlusive_zone.top_left.y += DeltaY{exclusive}; non_exlusive_zone.size.width -= DeltaX{exclusive}; non_exlusive_zone.size.height -= DeltaY{exclusive}; auto expected_config_size = layout.configure_size(non_exlusive_zone); if (expected_config_size.width.as_int()) { EXPECT_THAT(layer_surface.last_size().width, Eq(expected_config_size.width)); } if (expected_config_size.height.as_int()) { EXPECT_THAT(layer_surface.last_size().height, Eq(expected_config_size.height)); } auto const expected_placement = layout.placement_rect(non_exlusive_zone); surface.attach_visible_buffer(expected_placement.size.width.as_int(), expected_placement.size.height.as_int()); expect_surface_is_at_position(expected_placement.top_left); } 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}; ON_CALL(toplevel, configure).WillByDefault([&](auto w, auto 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); } }); ON_CALL(xdg_surface, configure).WillByDefault([&](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.width.as_int(), request_size.height.as_int()); commit_and_wait_for_configure(); surface.attach_visible_buffer(rect.size.width.as_int(), rect.size.height.as_int()); 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.as_int(); break; case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: expected_width -= exclusive_zone + layout.margin.right.as_int(); break; case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: expected_height -= exclusive_zone + layout.margin.top.as_int(); break; case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: expected_height -= exclusive_zone + layout.margin.bottom.as_int(); 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.width.as_int(), layer_surface_request_size.height.as_int()); commit_and_wait_for_configure(); surface.attach_visible_buffer(layer_surface_rect.size.width.as_int(), layer_surface_rect.size.height.as_int()); 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.size.width.as_int() - 10, layer_surface_rect.size.height.as_int() - 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::nullopt, positioner}; zwlr_layer_surface_v1_get_popup(layer_surface, popup_xdg_popup); int popup_surface_configure_count = 0; Point popup_configured_position; ON_CALL(popup_xdg_surface, configure).WillByDefault([&](uint32_t serial) { xdg_surface_ack_configure(popup_xdg_surface, serial); popup_surface_configure_count++; }); ON_CALL(popup_xdg_popup, configure).WillByDefault([&](int32_t x, int32_t y, int32_t, int32_t) { popup_configured_position.x = X{x}; popup_configured_position.y = Y{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(as_point(layer_surface_rect.size / 2))); expect_surface_is_at_position( layer_surface_rect.top_left + as_displacement(layer_surface_rect.size / 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(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::nullopt}, LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, std::nullopt}, LayerLayerParams{std::nullopt, ZWLR_LAYER_SHELL_V1_LAYER_TOP}, LayerLayerParams{std::nullopt, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY} )); // TODO: test it gets put on a specified output // TODO: test margin // TODO: test keyboard interactivity wlcs-1.7.0/tests/wlr_virtual_pointer_v1.cpp000066400000000000000000000400001453657357500210730ustar00rootroot00000000000000/* * Copyright © 2022 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "in_process_server.h" #include "version_specifier.h" #include "xdg_output_v1.h" #include "generated/wlr-virtual-pointer-unstable-v1-client.h" #include "linux/input.h" #include #include using namespace testing; using namespace std::chrono_literals; namespace wlcs { WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_virtual_pointer_manager_v1) WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_virtual_pointer_v1) } namespace { class PointerListener { public: PointerListener(wl_seat* seat); ~PointerListener(); MOCK_METHOD(void, enter, (uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y)); MOCK_METHOD(void, leave, (uint32_t serial, wl_surface* surface)); MOCK_METHOD(void, motion, (uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y)); MOCK_METHOD(void, button, (uint32_t serial, uint32_t time, uint32_t button, uint32_t state)); MOCK_METHOD(void, axis, (uint32_t time, uint32_t axis, wl_fixed_t value)); MOCK_METHOD(void, frame, ()); MOCK_METHOD(void, axis_source, (uint32_t axis_source)); MOCK_METHOD(void, axis_stop, (uint32_t time, uint32_t axis)); MOCK_METHOD(void, axis_discrete, (uint32_t axis, int32_t discrete)); private: wl_pointer* const proxy; }; PointerListener::PointerListener(wl_seat* seat) : proxy{wl_seat_get_pointer(seat)} { #define FORWARD_TO_MOCK(method) \ [](void* data, wl_pointer*, auto... args){ static_cast(data)->method(args...); } static const wl_pointer_listener listener = { FORWARD_TO_MOCK(enter), FORWARD_TO_MOCK(leave), FORWARD_TO_MOCK(motion), FORWARD_TO_MOCK(button), FORWARD_TO_MOCK(axis), FORWARD_TO_MOCK(frame), FORWARD_TO_MOCK(axis_source), FORWARD_TO_MOCK(axis_stop), FORWARD_TO_MOCK(axis_discrete), }; #undef FORWARD_TO_MOCK wl_pointer_add_listener(proxy, &listener, this); } PointerListener::~PointerListener() { wl_pointer_destroy(proxy); } class VirtualPointerV1Test: public wlcs::StartedInProcessServer { public: VirtualPointerV1Test() : receive_client{the_server()}, send_client{the_server()}, surface{receive_client.create_visible_surface(surface_width, surface_height)}, pointer{the_server().create_pointer()}, listener{receive_client.seat()}, manager{send_client.bind_if_supported(wlcs::AnyVersion)} { EXPECT_CALL(listener, enter(_, surface.wl_surface(), _, _)); EXPECT_CALL(listener, motion(_, _, _)).Times(AnyNumber()); EXPECT_CALL(listener, frame()).Times(AnyNumber()); the_server().move_surface_to(surface, 0, 0); pointer.move_to(pointer_start_x, pointer_start_y); send_client.roundtrip(); receive_client.roundtrip(); Mock::VerifyAndClearExpectations(&listener); } wlcs::Client receive_client; wlcs::Client send_client; wlcs::Surface surface; wlcs::Pointer pointer; StrictMock listener; wlcs::WlHandle manager; static int const surface_width = 400; static int const surface_height = 400; static int const pointer_start_x = 20; static int const pointer_start_y = 30; }; } TEST_F(VirtualPointerV1Test, when_virtual_pointer_is_moved_client_sees_motion) { int const motion_x = 7; int const motion_y = 22; EXPECT_CALL(listener, motion(_, wl_fixed_from_int(pointer_start_x + motion_x), wl_fixed_from_int(pointer_start_y + motion_y))); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_motion(handle, 0, wl_fixed_from_int(motion_x), wl_fixed_from_int(motion_y)); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_is_moved_multiple_times_client_sees_motion) { int const motion1_x = 7; int const motion1_y = 22; int const motion2_x = 5; int const motion2_y = -12; auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); EXPECT_CALL(listener, motion(_, _, _)).Times(AnyNumber()); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AnyNumber()).WillOnce(Invoke([&]{ recieved_frame = true; })); zwlr_virtual_pointer_v1_motion(handle, 0, wl_fixed_from_int(motion1_x), wl_fixed_from_int(motion1_y)); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); EXPECT_CALL(listener, motion(_, wl_fixed_from_int(pointer_start_x + motion1_x + motion2_x), wl_fixed_from_int(pointer_start_y + motion1_y + motion2_y))); recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); zwlr_virtual_pointer_v1_motion(handle, 0, wl_fixed_from_int(motion2_x), wl_fixed_from_int(motion2_y)); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_left_clicks_client_sees_button_down) { EXPECT_CALL(listener, button(_, _, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED)); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_left_releases_client_sees_button_up) { EXPECT_CALL(listener, button(_, _, _, _)).Times(AnyNumber()); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AnyNumber()).WillOnce(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); Mock::VerifyAndClearExpectations(&listener); EXPECT_CALL(listener, button(_, _, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED)); recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_given_multiple_button_presses_at_once_client_sees_all) { EXPECT_CALL(listener, button(_, _, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED)); EXPECT_CALL(listener, button(_, _, BTN_MIDDLE, WL_POINTER_BUTTON_STATE_PRESSED)); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); zwlr_virtual_pointer_v1_button(handle, 0, BTN_MIDDLE, WL_POINTER_BUTTON_STATE_PRESSED); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_presses_and_releases_different_buttons_on_same_frame_client_sees_correct_events) { EXPECT_CALL(listener, button(_, _, _, _)).Times(AnyNumber()); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AnyNumber()).WillRepeatedly(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); Mock::VerifyAndClearExpectations(&listener); EXPECT_CALL(listener, button(_, _, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED)); EXPECT_CALL(listener, button(_, _, BTN_RIGHT, WL_POINTER_BUTTON_STATE_PRESSED)); recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); zwlr_virtual_pointer_v1_button(handle, 0, BTN_RIGHT, WL_POINTER_BUTTON_STATE_PRESSED); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); // This is a hack around https://github.com/MirServer/mir/issues/2971 std::this_thread::sleep_for(1ms); receive_client.roundtrip(); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_scrolls_client_sees_axis) { EXPECT_CALL(listener, axis(_, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5))); EXPECT_CALL(listener, axis_source(_)).Times(AnyNumber()); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_axis(handle, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5)); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_scrolls_with_steps_client_sees_axis_descrete) { EXPECT_CALL(listener, axis(_, WL_POINTER_AXIS_HORIZONTAL_SCROLL, wl_fixed_from_int(5))); EXPECT_CALL(listener, axis_discrete(WL_POINTER_AXIS_HORIZONTAL_SCROLL, 4)); EXPECT_CALL(listener, axis_source(_)).Times(AnyNumber()); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_axis_discrete(handle, 0, WL_POINTER_AXIS_HORIZONTAL_SCROLL, wl_fixed_from_int(5), 4); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_specifies_axis_source_client_sees_axis_source) { EXPECT_CALL(listener, axis(_, _, _)).Times(AnyNumber()); EXPECT_CALL(listener, axis_source(WL_POINTER_AXIS_SOURCE_CONTINUOUS)); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_axis(handle, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5)); zwlr_virtual_pointer_v1_axis_source(handle, WL_POINTER_AXIS_SOURCE_CONTINUOUS); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); auto axis_source = false; EXPECT_CALL(listener, axis_source(WL_POINTER_AXIS_SOURCE_WHEEL)).WillOnce(Invoke([&]{ axis_source = true; })); zwlr_virtual_pointer_v1_axis(handle, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5)); zwlr_virtual_pointer_v1_axis_source(handle, WL_POINTER_AXIS_SOURCE_WHEEL); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return axis_source; }); } TEST_F(VirtualPointerV1Test, if_frame_is_not_sent_client_sees_no_events) { auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_motion(handle, 0, wl_fixed_from_int(6), wl_fixed_from_int(7)); zwlr_virtual_pointer_v1_motion_absolute(handle, 0, 2, 4, 10, 10); zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); zwlr_virtual_pointer_v1_axis(handle, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5)); zwlr_virtual_pointer_v1_axis_source(handle, WL_POINTER_AXIS_SOURCE_WHEEL); // Should produce no events because there has been no frame EXPECT_CALL(listener, motion(_,_,_)).Times(0); EXPECT_CALL(listener, button(_,_,_,_)).Times(0); EXPECT_CALL(listener, axis(_,_,_)).Times(0); EXPECT_CALL(listener, axis_source(_)).Times(0); EXPECT_CALL(listener, frame()).Times(0); send_client.roundtrip(); std::this_thread::sleep_for(1ms); receive_client.roundtrip(); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_is_moved_with_absolute_coordinates_with_the_extent_of_the_output_client_sees_motion) { ASSERT_THAT(send_client.output_count(), Ge(1u)); wlcs::XdgOutputManagerV1 xdg_output_manager{send_client}; wlcs::XdgOutputV1 xdg_output{xdg_output_manager, 0}; send_client.roundtrip(); auto const& output_state = xdg_output.state(); ASSERT_THAT(output_state.logical_size.operator bool(), Eq(true)); auto const output_size = output_state.logical_size.value(); int const move_to_x = 22; int const move_to_y = 33; EXPECT_CALL(listener, motion(_, wl_fixed_from_int(move_to_x), wl_fixed_from_int(move_to_y))); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_motion_absolute(handle, 0, move_to_x, move_to_y, output_size.first, output_size.second); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); } TEST_F(VirtualPointerV1Test, when_virtual_pointer_is_moved_with_absolute_coordinates_with_the_extent_twice_of_the_output_client_sees_motion) { ASSERT_THAT(send_client.output_count(), Ge(1u)); wlcs::XdgOutputManagerV1 xdg_output_manager{send_client}; wlcs::XdgOutputV1 xdg_output{xdg_output_manager, 0}; send_client.roundtrip(); auto const& output_state = xdg_output.state(); ASSERT_THAT(output_state.logical_size.operator bool(), Eq(true)); auto const output_size = output_state.logical_size.value(); int const move_to_x = 22; int const move_to_y = 33; EXPECT_CALL(listener, motion(_, wl_fixed_from_int(move_to_x), wl_fixed_from_int(move_to_y))); auto recieved_frame = false; EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; })); auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr); zwlr_virtual_pointer_v1_motion_absolute( handle, 0, move_to_x * 2, move_to_y * 2, output_size.first * 2, output_size.second * 2); zwlr_virtual_pointer_v1_frame(handle); send_client.roundtrip(); receive_client.dispatch_until([&] { return recieved_frame; }); } wlcs-1.7.0/tests/xdg_output_v1.cpp000066400000000000000000000025111453657357500171700ustar00rootroot00000000000000/* * Copyright © 2019 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: William Wold */ #include "in_process_server.h" #include "xdg_output_v1.h" #include using namespace testing; using namespace wlcs; using XdgOutputV1Test = wlcs::InProcessServer; TEST_F(XdgOutputV1Test, xdg_output_properties_set) { Client client{the_server()}; ASSERT_THAT(client.output_count(), Ge(1u)); XdgOutputManagerV1 xdg_output_manager{client}; XdgOutputV1 xdg_output{xdg_output_manager, 0}; client.roundtrip(); auto const& state = xdg_output.state(); ASSERT_THAT((bool)state.logical_position, true); ASSERT_THAT((bool)state.logical_size, true); ASSERT_THAT((bool)state.name, true); // Description is optional } wlcs-1.7.0/tests/xdg_popup.cpp000066400000000000000000001566321453657357500164030ustar00rootroot00000000000000/* * 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 "version_specifier.h" #include #include using namespace testing; int const window_width = 400, window_height = 500; int const popup_width = 60, popup_height = 40; namespace { uint32_t anchor_stable_to_v6(xdg_positioner_anchor anchor) { switch (anchor) { case XDG_POSITIONER_ANCHOR_NONE: return ZXDG_POSITIONER_V6_ANCHOR_NONE; case XDG_POSITIONER_ANCHOR_TOP: return ZXDG_POSITIONER_V6_ANCHOR_TOP; case XDG_POSITIONER_ANCHOR_BOTTOM: return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM; case XDG_POSITIONER_ANCHOR_LEFT: return ZXDG_POSITIONER_V6_ANCHOR_LEFT; case XDG_POSITIONER_ANCHOR_RIGHT: return ZXDG_POSITIONER_V6_ANCHOR_RIGHT; case XDG_POSITIONER_ANCHOR_TOP_LEFT: return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT; case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_LEFT; case XDG_POSITIONER_ANCHOR_TOP_RIGHT: return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT; case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT; default: return ZXDG_POSITIONER_V6_ANCHOR_NONE; } } uint32_t gravity_stable_to_v6(xdg_positioner_gravity gravity) { switch (gravity) { case XDG_POSITIONER_GRAVITY_NONE: return ZXDG_POSITIONER_V6_GRAVITY_NONE; case XDG_POSITIONER_GRAVITY_TOP: return ZXDG_POSITIONER_V6_GRAVITY_TOP; case XDG_POSITIONER_GRAVITY_BOTTOM: return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM; case XDG_POSITIONER_GRAVITY_LEFT: return ZXDG_POSITIONER_V6_GRAVITY_LEFT; case XDG_POSITIONER_GRAVITY_RIGHT: return ZXDG_POSITIONER_V6_GRAVITY_RIGHT; case XDG_POSITIONER_GRAVITY_TOP_LEFT: return ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_LEFT; case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_LEFT; case XDG_POSITIONER_GRAVITY_TOP_RIGHT: return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT; case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT; default: return ZXDG_POSITIONER_V6_GRAVITY_NONE; } } uint32_t constraint_adjustment_stable_to_v6(xdg_positioner_constraint_adjustment ca) { return ca; // the two enums have the same values } struct PositionerParams { PositionerParams() : popup_size{popup_width, popup_height}, anchor_rect{{0, 0}, {window_width, window_height}} { } auto with_size(int x, int y) -> PositionerParams& { popup_size = {x, y}; return *this; } auto with_anchor_rect(int x, int y, int w, int h) -> PositionerParams& { anchor_rect = {{x, y}, {w, h}}; return *this; } auto with_anchor(xdg_positioner_anchor value) -> PositionerParams& { anchor_stable = {value}; return *this; } auto with_gravity(xdg_positioner_gravity value) -> PositionerParams& { gravity_stable = {value}; return *this; } auto with_constraint_adjustment(uint32_t value) -> PositionerParams& { constraint_adjustment_stable = static_cast(value); return *this; } auto with_offset(int x, int y) -> PositionerParams& { offset = {{x, y}}; return *this; } auto with_reactive(bool enable = true) -> PositionerParams& { reactive = enable; return *this; } auto with_grab(bool enable = true) -> PositionerParams& { grab = enable; return *this; } std::pair popup_size; // will default to XdgPopupStableTestBase::popup_(width|height) if nullopt std::pair, std::pair> anchor_rect; // will default to the full window rect std::optional anchor_stable; std::optional gravity_stable; std::optional constraint_adjustment_stable; std::optional> offset; bool reactive{false}; 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}, expected_size{popup_width, popup_height}, positioner{positioner}, parent_position_func{std::nullopt} { } PositionerTestParams( std::string name, int expected_x, int expected_y, int expected_width, int expected_height, PositionerParams const& positioner, std::function(int, int)> parent_position_func) : name{name}, expected_positon{expected_x, expected_y}, expected_size{expected_width, expected_height}, positioner{positioner}, parent_position_func{std::move(parent_position_func)} { } std::string name; std::pair expected_positon; std::pair expected_size; PositionerParams positioner; /// parent_position_func is called with the size of the output std::optional(int output_width, int output_height)>> parent_position_func; }; 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, window_y; XdgPopupManagerBase(wlcs::Server& server, std::shared_ptr client) : the_server{server}, client{client}, surface{*client}, parent_position_{window_x, window_y} { 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, parent_position_.first, parent_position_.second); } 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 set_parent_position( std::function(int output_width, int output_height)> const& parent_position_func) { auto const output_size = client->output_state(0).mode_size.value(); parent_position_ = parent_position_func(output_size.first, output_size.second); the_server.move_surface_to(surface, parent_position_.first, parent_position_.second); client->roundtrip(); } auto parent_position() -> std::pair { return parent_position_; } void unmap_popup() { clear_popup(); popup_surface = std::nullopt; } virtual auto create_child_popup() -> std::unique_ptr = 0; virtual void dispatch_until_popup_configure() = 0; MOCK_METHOD0(popup_done, void()); wlcs::Server& the_server; std::shared_ptr const client; wlcs::Surface surface; std::optional popup_surface; std::pair parent_position_; std::optional state; protected: virtual void setup_popup(PositionerParams const& params) = 0; virtual void clear_popup() = 0; bool surface_rendered{true}; }; int const XdgPopupManagerBase::window_x = 500; int const XdgPopupManagerBase::window_y = 500; class XdgPopupStableManager : public XdgPopupManagerBase { public: XdgPopupStableManager(wlcs::Server& server) : XdgPopupManagerBase{server, std::make_shared(server)}, xdg_shell_surface{std::in_place, *client, surface}, toplevel{std::in_place, xdg_shell_surface.value()}, parent{&*xdg_shell_surface} { wait_for_frame_to_render(); } XdgPopupStableManager(wlcs::Server& server, std::shared_ptr client, wlcs::XdgSurfaceStable* parent) : XdgPopupManagerBase{server, client}, parent{parent} { } 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; }); } static void setup_positioner(wlcs::XdgPositionerStable& positioner, PositionerParams const& param) { // 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); if (param.reactive) { if (xdg_positioner_get_version(positioner) < XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION) { BOOST_THROW_EXCEPTION(std::logic_error("XDG shell version does not support reactive popups")); } xdg_positioner_set_reactive(positioner); } } void setup_popup(PositionerParams const& param) override { wlcs::XdgPositionerStable positioner{*client}; setup_positioner(positioner, param); popup_xdg_surface.emplace(*client, popup_surface.value()); popup.emplace(popup_xdg_surface.value(), parent, 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()); } ON_CALL(popup_xdg_surface.value(), configure).WillByDefault([&](auto serial) { xdg_surface_ack_configure(popup_xdg_surface.value(), serial); popup_surface_configure_count++; }); ON_CALL(popup.value(), configure).WillByDefault([&](auto... args) { state = State{args...}; }); ON_CALL(popup.value(), done()).WillByDefault([this](){ popup_done(); }); } void move_parent_using_pointer(std::pair to) { auto pointer = the_server.create_pointer(); pointer.move_to(parent_position_.first + 1, parent_position_.second + 1); pointer.left_button_down(); client->roundtrip(); if (client->window_under_cursor() != surface) { BOOST_THROW_EXCEPTION(std::runtime_error("surface not detected at expected position")); } xdg_toplevel_move(toplevel.value(), client->seat(), client->latest_serial().value()); client->roundtrip(); pointer.move_to(to.first + 1, to.second + 1); parent_position_ = to; pointer.left_button_up(); client->roundtrip(); } void clear_popup() override { popup = std::nullopt; popup_xdg_surface = std::nullopt; } auto create_child_popup() -> std::unique_ptr override { return std::make_unique(the_server, client, &popup_xdg_surface.value()); } std::optional xdg_shell_surface; std::optional toplevel; wlcs::XdgSurfaceStable* const parent; std::optional popup_xdg_surface; std::optional popup; int popup_surface_configure_count{0}; }; class XdgPopupV6Manager : public XdgPopupManagerBase { public: XdgPopupV6Manager(wlcs::Server& server) : XdgPopupManagerBase{server, std::make_shared(server)}, xdg_shell_surface{std::in_place, *client, surface}, toplevel{std::in_place, xdg_shell_surface.value()}, parent{&*xdg_shell_surface} { wait_for_frame_to_render(); } XdgPopupV6Manager(wlcs::Server& server, std::shared_ptr client, wlcs::XdgSurfaceV6* parent) : XdgPopupManagerBase{server, client}, parent{parent} { } 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(), *parent, 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()); } ON_CALL(popup.value(), done).WillByDefault([this](){ popup_done(); }); ON_CALL(popup_xdg_surface.value(), configure).WillByDefault([&](auto serial) { zxdg_surface_v6_ack_configure(popup_xdg_surface.value(), serial); popup_surface_configure_count++; }); ON_CALL(popup.value(), configure).WillByDefault([this](auto... args) { state = State{args...}; }); } void clear_popup() override { popup = std::nullopt; popup_xdg_surface = std::nullopt; } auto create_child_popup() -> std::unique_ptr override { return std::make_unique(the_server, client, &popup_xdg_surface.value()); } std::optional xdg_shell_surface; std::optional toplevel; wlcs::XdgSurfaceV6* const parent; std::optional popup_xdg_surface; std::optional popup; int popup_surface_configure_count{0}; }; class LayerV1PopupManager : public XdgPopupManagerBase { public: LayerV1PopupManager(wlcs::Server& server) : XdgPopupManagerBase{server, std::make_shared(server)}, layer_surface{*client, surface} { { wlcs::Client client{server}; auto const layer_shell = client.bind_if_supported( wlcs::AtLeastVersion{ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION}); client.roundtrip(); } zwlr_layer_surface_v1_set_size(layer_surface, window_width, window_height); zwlr_layer_surface_v1_set_keyboard_interactivity( layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND); 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::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()); } ON_CALL(popup_xdg_surface.value(), configure).WillByDefault([&](uint32_t serial) { xdg_surface_ack_configure(popup_xdg_surface.value(), serial); popup_surface_configure_count++; }); ON_CALL(popup.value(), configure).WillByDefault([this](auto... args) { state = State{args...}; }); ON_CALL(popup.value(), done()).WillByDefault([this](){ popup_done(); }); } void clear_popup() override { popup = std::nullopt; popup_xdg_surface = std::nullopt; } auto create_child_popup() -> std::unique_ptr override { return std::make_unique(the_server, client, &popup_xdg_surface.value()); } wlcs::LayerSurfaceV1 layer_surface; std::optional popup_xdg_surface; std::optional popup; int popup_surface_configure_count{0}; }; auto surface_actually_at_location( wlcs::Server& server, wlcs::Client& client, wl_surface* surface, std::pair location) -> bool { auto pointer = server.create_pointer(); int offset_x = 1, offset_y = 1; if (location.first < 0) { offset_x -= location.first; } if (location.second < 0) { offset_y -= location.second; } pointer.move_to(location.first + offset_x, location.second + offset_y); client.roundtrip(); return ( client.window_under_cursor() == surface && client.pointer_position() == std::make_pair(wl_fixed_from_int(offset_x), wl_fixed_from_int(offset_y))); } } class XdgPopupPositionerTest: public wlcs::StartedInProcessServer, public testing::WithParamInterface { }; TEST_P(XdgPopupPositionerTest, xdg_shell_stable_popup_placed_correctly) { auto manager = std::make_unique(the_server()); auto const& param = GetParam(); if (param.parent_position_func) { manager->set_parent_position(param.parent_position_func.value()); } manager->map_popup(param.positioner); ASSERT_THAT( manager->state, Ne(std::nullopt)) << "popup configure event not sent"; EXPECT_THAT( std::make_pair(manager->state.value().x, manager->state.value().y), Eq(param.expected_positon)) << "popup placed in incorrect position"; EXPECT_THAT( std::make_pair(manager->state.value().width, manager->state.value().height), Eq(param.expected_size)) << "popup has incorrect size"; EXPECT_THAT( surface_actually_at_location( the_server(), *manager->client, manager->popup_surface.value(), std::make_pair( manager->parent_position().first + param.expected_positon.first, manager->parent_position().second + param.expected_positon.second)), IsTrue()); } TEST_P(XdgPopupPositionerTest, xdg_shell_unstable_v6_popup_placed_correctly) { auto manager = std::make_unique(the_server()); auto const& param = GetParam(); if (param.parent_position_func) { manager->set_parent_position(param.parent_position_func.value()); } manager->map_popup(param.positioner); ASSERT_THAT( manager->state, Ne(std::nullopt)) << "popup configure event not sent"; EXPECT_THAT( std::make_pair(manager->state.value().x, manager->state.value().y), Eq(param.expected_positon)) << "popup placed in incorrect position"; EXPECT_THAT( std::make_pair(manager->state.value().width, manager->state.value().height), Eq(param.expected_size)) << "popup has incorrect size"; EXPECT_THAT( surface_actually_at_location( the_server(), *manager->client, manager->popup_surface.value(), std::make_pair( manager->parent_position().first + param.expected_positon.first, manager->parent_position().second + param.expected_positon.second)), IsTrue()); } TEST_P(XdgPopupPositionerTest, layer_shell_popup_placed_correctly) { auto manager = std::make_unique(the_server()); auto const& param = GetParam(); if (param.parent_position_func) { manager->set_parent_position(param.parent_position_func.value()); } manager->map_popup(param.positioner); ASSERT_THAT( manager->state, Ne(std::nullopt)) << "popup configure event not sent"; EXPECT_THAT( std::make_pair(manager->state.value().x, manager->state.value().y), Eq(param.expected_positon)) << "popup placed in incorrect position"; EXPECT_THAT( std::make_pair(manager->state.value().width, manager->state.value().height), Eq(param.expected_size)) << "popup has incorrect size"; EXPECT_THAT( surface_actually_at_location( the_server(), *manager->client, manager->popup_surface.value(), std::make_pair( manager->parent_position().first + param.expected_positon.first, manager->parent_position().second + param.expected_positon.second)), IsTrue()); } 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)} )); INSTANTIATE_TEST_SUITE_P( ConstraintAdjustmentNone, XdgPopupPositionerTest, testing::Values( PositionerTestParams{"middle of screen", -popup_width, -popup_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE), [](int width, int height){ return std::make_pair((width - window_width) / 2, (height - window_height) / 2); }}, PositionerTestParams{"off top left edge", -popup_width, -popup_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE), [](int /*width*/, int /*height*/){ return std::make_pair(5, 5); }}, PositionerTestParams{"off top right edge", window_width, -popup_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT) .with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE), [](int width, int /*height*/){ return std::make_pair(width - window_width - 5, 5); }}, PositionerTestParams{"off bottom left edge", -popup_width, window_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT) .with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE), [](int /*width*/, int height){ return std::make_pair(5, height - window_height - 5); }}, PositionerTestParams{"off bottom right edge", window_width, window_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) .with_constraint_adjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE), [](int width, int height){ return std::make_pair(width - window_width - 5, height - window_height - 5); }} )); INSTANTIATE_TEST_SUITE_P( ConstraintAdjustmentSlide, XdgPopupPositionerTest, testing::Values( PositionerTestParams{"middle of screen", -popup_width, -popup_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y), [](int width, int height){ return std::make_pair((width - window_width) / 2, (height - window_height) / 2); }}, PositionerTestParams{"off top left edge", -5, -5, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y), [](int /*width*/, int /*height*/){ return std::make_pair(5, 5); }}, PositionerTestParams{"off top right edge", window_width - popup_width + 5, -5, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y), [](int width, int /*height*/){ return std::make_pair(width - window_width - 5, 5); }}, PositionerTestParams{"off bottom left edge", -5, window_height - popup_height + 5, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y), [](int /*width*/, int height){ return std::make_pair(5, height - window_height - 5); }}, PositionerTestParams{"off bottom right edge", window_width - popup_width + 5, window_height - popup_height + 5, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y), [](int width, int height){ return std::make_pair(width - window_width - 5, height - window_height - 5); }} )); INSTANTIATE_TEST_SUITE_P( ConstraintAdjustmentFlip, XdgPopupPositionerTest, testing::Values( PositionerTestParams{"middle of screen", -popup_width, -popup_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y), [](int width, int height){ return std::make_pair((width - window_width) / 2, (height - window_height) / 2); }}, PositionerTestParams{"off top left edge", window_width, window_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y), [](int /*width*/, int /*height*/){ return std::make_pair(5, 5); }}, PositionerTestParams{"off top right edge", -popup_width, window_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y), [](int width, int /*height*/){ return std::make_pair(width - window_width - 5, 5); }}, PositionerTestParams{"off bottom left edge", window_width, -popup_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y), [](int /*width*/, int height){ return std::make_pair(5, height - window_height - 5); }}, PositionerTestParams{"off bottom right edge", -popup_width, -popup_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y), [](int width, int height){ return std::make_pair(width - window_width - 5, height - window_height - 5); }} )); INSTANTIATE_TEST_SUITE_P( ConstraintAdjustmentResize, XdgPopupPositionerTest, testing::Values( PositionerTestParams{"middle of screen", -popup_width, -popup_height, popup_width, popup_height, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y), [](int width, int height){ return std::make_pair((width - window_width) / 2, (height - window_height) / 2); }}, PositionerTestParams{"off top left edge", -5, -5, 5, 5, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y), [](int /*width*/, int /*height*/){ return std::make_pair(5, 5); }}, PositionerTestParams{"off top right edge", window_width, -5, 5, 5, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y), [](int width, int /*height*/){ return std::make_pair(width - window_width - 5, 5); }}, PositionerTestParams{"off bottom left edge", -5, window_height, 5, 5, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y), [](int /*width*/, int height){ return std::make_pair(5, height - window_height - 5); }}, PositionerTestParams{"off bottom right edge", window_width, window_height, 5, 5, PositionerParams() .with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y), [](int width, int height){ return std::make_pair(width - window_width - 5, height - window_height - 5); }} )); struct XdgPopupTestParam { std::function(wlcs::InProcessServer* const)> build; }; std::ostream& operator<<(std::ostream& out, XdgPopupTestParam const&) { return out; } class XdgPopupTest: public wlcs::StartedInProcessServer, public testing::WithParamInterface { }; TEST_P(XdgPopupTest, pointer_focus_goes_to_popup) { auto const& param = GetParam(); auto manager = param.build(this); auto pointer = the_server().create_pointer(); pointer.move_to(manager->window_x + 1, manager->window_y + 1); manager->client->roundtrip(); EXPECT_THAT(manager->client->window_under_cursor(), Eq((wl_surface*)manager->surface)); auto positioner = PositionerParams{} .with_size(30, 30) .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); manager->map_popup(positioner); manager->client->roundtrip(); pointer.move_to(manager->window_x + 2, manager->window_y + 1); manager->client->roundtrip(); EXPECT_THAT(manager->client->window_under_cursor(), Eq((wl_surface*)manager->popup_surface.value())); } TEST_P(XdgPopupTest, popup_gives_up_pointer_focus_when_gone) { auto const& param = GetParam(); auto manager = param.build(this); auto pointer = the_server().create_pointer(); auto positioner = PositionerParams{} .with_size(30, 30) .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); manager->map_popup(positioner); manager->client->roundtrip(); pointer.move_to(manager->window_x + 2, manager->window_y + 1); manager->client->roundtrip(); EXPECT_THAT(manager->client->window_under_cursor(), Eq((wl_surface*)manager->popup_surface.value())); manager->unmap_popup(); manager->client->roundtrip(); pointer.move_to(manager->window_x + 3, manager->window_y + 1); manager->client->roundtrip(); EXPECT_THAT(manager->client->window_under_cursor(), Eq((wl_surface*)manager->surface)); } TEST_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_popups_get_done_events_in_correct_order) { auto const& param = GetParam(); wlcs::Client background_client{the_server()}; wlcs::Surface background_surface{background_client.create_visible_surface(10, 10)}; the_server().move_surface_to( background_surface, XdgPopupManagerBase::window_x - 5, XdgPopupManagerBase::window_y - 5); auto top_popup_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(top_popup_manager->window_x + 2, top_popup_manager->window_y + 2); pointer.left_click(); top_popup_manager->client->roundtrip(); auto positioner = PositionerParams{} .with_anchor_rect(20, 20, 1, 1) .with_size(30, 30) .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) .with_grab(); top_popup_manager->map_popup(positioner); top_popup_manager->client->roundtrip(); auto sub_popup_manager = top_popup_manager->create_child_popup(); sub_popup_manager->map_popup(positioner); top_popup_manager->client->roundtrip(); InSequence seq; EXPECT_CALL(*sub_popup_manager, popup_done()); EXPECT_CALL(*top_popup_manager, popup_done()); // Click on background surface so it is focused and grabbed popups are dismissed pointer.move_to(top_popup_manager->window_x - 2, top_popup_manager->window_y - 2); pointer.left_click(); top_popup_manager->client->roundtrip(); } TEST_P(XdgPopupTest, grabbed_popup_gets_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(), Eq(static_cast(manager->popup_surface.value()))) << "grabbed popup not given keyboard focus"; } 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(manager->popup_surface.value()))) << "popup given keyboard focus"; EXPECT_THAT( manager->client->keyboard_focused_window(), Eq(static_cast(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(the_server()); auto positioner = PositionerParams{} .with_anchor_rect(window_width / 2, window_height / 2, 0, 0); manager->map_popup(positioner); manager->client->roundtrip(); ASSERT_THAT( std::make_pair(manager->state.value().x, manager->state.value().y), Eq(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::nullopt)); EXPECT_THAT(manager->state.value().width, Gt(0)); EXPECT_THAT(manager->state.value().height, Gt(0)); } TEST_F(XdgPopupTest, when_parent_surface_is_moved_a_reactive_popup_is_moved) { XdgPopupStableManager manager{the_server()}; manager.client->bind_if_supported(wlcs::AtLeastVersion{XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION}); auto positioner = PositionerParams{} .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y) .with_reactive(); manager.map_popup(positioner); // repositioned should not be called unless reposition is called EXPECT_CALL(manager.popup.value(), repositioned).Times(0); manager.client->roundtrip(); ASSERT_THAT( manager.state, Ne(std::nullopt)) << "popup configure event not sent"; ASSERT_THAT( std::make_pair(manager.state.value().x, manager.state.value().y), Eq(std::make_pair(-popup_width, -popup_height))) << "popup initially placed in incorrect position"; manager.move_parent_using_pointer({5, 5}); ASSERT_THAT( std::make_pair(manager.state.value().x, manager.state.value().y), Ne(std::make_pair(-popup_width, -popup_height))) << "reactive popup was not moved"; EXPECT_THAT( std::make_pair(manager.state.value().x, manager.state.value().y), Eq(std::make_pair(-5, -5))) << "reactive popup placed in incorrect position"; EXPECT_THAT( surface_actually_at_location( the_server(), *manager.client, manager.popup_surface.value(), std::make_pair(0, 0)), IsTrue()); } TEST_F(XdgPopupTest, when_parent_surface_is_moved_a_nonreactive_popup_is_not_moved) { XdgPopupStableManager manager{the_server()}; manager.client->bind_if_supported(wlcs::AtLeastVersion{XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION}); auto positioner = PositionerParams{} .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT) .with_constraint_adjustment( XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y); manager.map_popup(positioner); // repositioned should not be called unless reposition is called EXPECT_CALL(manager.popup.value(), repositioned).Times(0); manager.client->roundtrip(); ASSERT_THAT( manager.state, Ne(std::nullopt)) << "popup configure event not sent"; ASSERT_THAT( std::make_pair(manager.state.value().x, manager.state.value().y), Eq(std::make_pair(-popup_width, -popup_height))) << "popup initially placed in incorrect position"; manager.move_parent_using_pointer({5, 5}); EXPECT_THAT( std::make_pair(manager.state.value().x, manager.state.value().y), Eq(std::make_pair(-popup_width, -popup_height))) << "nonreactive popup was moved"; EXPECT_THAT( surface_actually_at_location( the_server(), *manager.client, manager.popup_surface.value(), std::make_pair(5 - popup_width, 5 - popup_height)), IsTrue()); } TEST_F(XdgPopupTest, popup_can_be_repositioned) { XdgPopupStableManager manager{the_server()}; manager.client->bind_if_supported(wlcs::AtLeastVersion{XDG_POPUP_REPOSITION_SINCE_VERSION}); auto positioner_a = PositionerParams{} .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT) .with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT); manager.map_popup(positioner_a); EXPECT_CALL(manager.popup.value(), repositioned).Times(0); manager.client->roundtrip(); auto positioner_b = PositionerParams{} .with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); wlcs::XdgPositionerStable xdg_positioner{*manager.client}; XdgPopupStableManager::setup_positioner(xdg_positioner, positioner_b); xdg_popup_reposition(manager.popup.value(), xdg_positioner, 1); EXPECT_CALL(manager.popup.value(), repositioned(1)); manager.client->roundtrip(); ASSERT_THAT( manager.state, Ne(std::nullopt)) << "popup configure event not sent"; EXPECT_THAT( std::make_pair(manager.state.value().x, manager.state.value().y), Eq(std::make_pair(window_width, window_height))); EXPECT_THAT( surface_actually_at_location( the_server(), *manager.client, manager.popup_surface.value(), std::make_pair( manager.parent_position().first + window_width, manager.parent_position().second + window_height)), IsTrue()); } INSTANTIATE_TEST_SUITE_P( XdgPopupStable, XdgPopupTest, testing::Values(XdgPopupTestParam{ [](wlcs::InProcessServer* const server) { return std::make_unique(server->the_server()); }})); INSTANTIATE_TEST_SUITE_P( XdgPopupUnstableV6, XdgPopupTest, testing::Values(XdgPopupTestParam{ [](wlcs::InProcessServer* const server) { return std::make_unique(server->the_server()); }})); INSTANTIATE_TEST_SUITE_P( LayerShellPopup, XdgPopupTest, testing::Values(XdgPopupTestParam{ [](wlcs::InProcessServer* const server) { return std::make_unique(server->the_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_offset // TODO: test that a zero size anchor rect fails on v6 wlcs-1.7.0/tests/xdg_surface_stable.cpp000066400000000000000000000136021453657357500202070ustar00rootroot00000000000000/* * Copyright © 2012 Intel Corporation * Copyright © 2013 Collabora, Ltd. * Copyright © 2018 Canonical Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "helpers.h" #include "in_process_server.h" #include "xdg_shell_stable.h" #include "wl_handle.h" #include "version_specifier.h" #include using namespace testing; using XdgSurfaceStableTest = wlcs::InProcessServer; using wlcs::AnyVersion; TEST_F(XdgSurfaceStableTest, supports_xdg_shell_stable_protocol) { wlcs::Client client{the_server()}; ASSERT_THAT(client.xdg_shell_stable(), NotNull()); wlcs::Surface surface{client}; wlcs::XdgSurfaceStable xdg_surface{client, surface}; } TEST_F(XdgSurfaceStableTest, gets_configure_event) { wlcs::Client client{the_server()}; wlcs::Surface surface{client}; wlcs::XdgSurfaceStable xdg_surface{client, surface}; bool configure_received{false}; EXPECT_CALL(xdg_surface, configure) .WillOnce([&](auto serial) { xdg_surface_ack_configure(xdg_surface, serial); configure_received = true; }); wlcs::XdgToplevelStable toplevel{xdg_surface}; // The first commit triggers an initial xdg_surface.configure event wl_surface_commit(surface); client.dispatch_until([&configure_received]() { return configure_received;} ); } TEST_F(XdgSurfaceStableTest, creating_xdg_surface_from_wl_surface_with_existing_role_is_an_error) { wlcs::Client client{the_server()}; auto const xdg_wm_base = client.bind_if_supported(AnyVersion); // We need a parent for the subsurface auto const parent = client.create_visible_surface(300, 300); auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor())); // We need some way of assigning a role to a wl_surface. wl_subcompositor is as good a way as any. auto const subsurface = wlcs::wrap_wl_object(wl_subcompositor_get_subsurface(client.subcompositor(), surface, parent)); client.roundtrip(); try { xdg_wm_base_get_xdg_surface(xdg_wm_base, surface); client.roundtrip(); } catch(wlcs::ProtocolError const& error) { EXPECT_THAT(error.interface(), Eq(&xdg_wm_base_interface)); EXPECT_THAT(error.error_code(), Eq(XDG_WM_BASE_ERROR_ROLE)); return; } FAIL() << "Expected protocol error not received"; } TEST_F(XdgSurfaceStableTest, creating_xdg_surface_from_wl_surface_with_attached_buffer_is_an_error) { wlcs::Client client{the_server()}; auto const xdg_wm_base = client.bind_if_supported(AnyVersion); auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor())); wlcs::ShmBuffer buffer{client, 300, 300}; wl_surface_attach(surface, buffer, 0, 0); client.roundtrip(); try { xdg_wm_base_get_xdg_surface(xdg_wm_base, surface); client.roundtrip(); } catch(wlcs::ProtocolError const& error) { EXPECT_THAT(error.interface(), Eq(&xdg_wm_base_interface)); EXPECT_THAT(error.error_code(), Eq(XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE)); return; } FAIL() << "Expected protocol error not received"; } TEST_F(XdgSurfaceStableTest, creating_xdg_surface_from_wl_surface_with_committed_buffer_is_an_error) { wlcs::Client client{the_server()}; auto const xdg_wm_base = client.bind_if_supported(AnyVersion); auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor())); wlcs::ShmBuffer buffer{client, 300, 300}; wl_surface_attach(surface, buffer, 0, 0); wl_surface_commit(surface); client.roundtrip(); try { xdg_wm_base_get_xdg_surface(xdg_wm_base, surface); client.roundtrip(); } catch(wlcs::ProtocolError const& error) { EXPECT_THAT(error.interface(), Eq(&xdg_wm_base_interface)); EXPECT_THAT(error.error_code(), Eq(XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE)); return; } FAIL() << "Expected protocol error not received"; } TEST_F(XdgSurfaceStableTest, attaching_buffer_to_unconfigured_xdg_surface_is_an_error) { wlcs::Client client{the_server()}; auto const xdg_wm_base = client.bind_if_supported(AnyVersion); auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor())); wlcs::ShmBuffer buffer{client, 300, 300}; client.roundtrip(); try { xdg_wm_base_get_xdg_surface(xdg_wm_base, surface); wl_surface_attach(surface, buffer, 0, 0); client.roundtrip(); } catch(wlcs::ProtocolError const& error) { EXPECT_THAT(error.interface(), Eq(&xdg_surface_interface)); EXPECT_THAT(error.error_code(), Eq(XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER)); return; } FAIL() << "Expected protocol error not received"; } wlcs-1.7.0/tests/xdg_surface_v6.cpp000066400000000000000000000040641453657357500172720ustar00rootroot00000000000000/* * Copyright © 2012 Intel Corporation * Copyright © 2013 Collabora, Ltd. * Copyright © 2018 Canonical Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "helpers.h" #include "in_process_server.h" #include "xdg_shell_v6.h" #include using namespace testing; using XdgSurfaceV6Test = wlcs::InProcessServer; TEST_F(XdgSurfaceV6Test, supports_xdg_shell_v6_protocol) { wlcs::Client client{the_server()}; ASSERT_THAT(client.xdg_shell_v6(), NotNull()); wlcs::Surface surface{client}; wlcs::XdgSurfaceV6 xdg_surface{client, surface}; } TEST_F(XdgSurfaceV6Test, gets_configure_event) { wlcs::Client client{the_server()}; wlcs::Surface surface{client}; wlcs::XdgSurfaceV6 xdg_surface{client, surface}; EXPECT_CALL(xdg_surface, configure).WillOnce([&](uint32_t serial) { zxdg_surface_v6_ack_configure(xdg_surface, serial); }); wlcs::XdgToplevelV6 toplevel{xdg_surface}; surface.attach_buffer(600, 400); client.roundtrip(); } wlcs-1.7.0/tests/xdg_toplevel_stable.cpp000066400000000000000000000545271453657357500204240ustar00rootroot00000000000000/* * 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 "version_specifier.h" #include "xdg_shell_stable.h" #include #include using namespace testing; namespace { class ConfigurationWindow { public: int const window_width = 200, window_height = 320; ConfigurationWindow(wlcs::Client& client) : client{client}, surface{client}, xdg_shell_surface{client, surface}, toplevel{xdg_shell_surface} { ON_CALL(xdg_shell_surface, configure).WillByDefault([&](auto serial) { xdg_surface_ack_configure(xdg_shell_surface, serial); surface_configure_count++; }); ON_CALL(toplevel, configure).WillByDefault([&](auto... args) { state = wlcs::XdgToplevelStable::State{args...}; }); wl_surface_commit(surface); /* From the xdg_surface protocol: * ... * After creating a role-specific object and setting it up, the client must perform an initial commit * without any buffer attached. The compositor will reply with initial wl_surface state such as * wl_surface.preferred_buffer_scale followed by an xdg_surface.configure event. The client must acknowledge * it and is then allowed to attach a buffer to map the surface. * ... * We've created the role-specific XdgToplevel above; we should now wait for * a configure event, ack it, and *then* attach a buffer. */ dispatch_until_configure(); surface.attach_buffer(window_width, window_height); wl_surface_commit(surface); /* Now that we've committed a buffer (and hence should be mapped) we expect * to be reconfigured with new state - particularly, we expect to be * in “activated” state after this. */ dispatch_until_configure(); } 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; TEST_F(XdgToplevelStableTest, wm_capabilities_are_sent) { wlcs::Client client{the_server()}; client.bind_if_supported(wlcs::AtLeastVersion{XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION}); wlcs::Surface surface{client}; wlcs::XdgSurfaceStable xdg_shell_surface{client, surface}; wlcs::XdgToplevelStable toplevel{xdg_shell_surface}; EXPECT_CALL(toplevel, wm_capabilities).Times(1); client.roundtrip(); } // there *could* be a bug in these tests, but also the window manager may not be behaving properly // lets take another look when we've updated the window manager TEST_F(XdgToplevelStableTest, pointer_respects_window_geom_offset) { const int offset_x = 35, offset_y = 12; const int window_pos_x = 200, window_pos_y = 280; const int pointer_x = window_pos_x + 20, pointer_y = window_pos_y + 30; wlcs::Client client{the_server()}; ConfigurationWindow window{client}; xdg_surface_set_window_geometry(window.xdg_shell_surface, offset_x, offset_y, window.window_width - offset_x, window.window_height - offset_y); wl_surface_commit(window.surface); the_server().move_surface_to(window.surface, window_pos_x, window_pos_y); auto pointer = the_server().create_pointer(); pointer.move_to(pointer_x, pointer_y); client.roundtrip(); ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)window.surface)); ASSERT_THAT(client.pointer_position(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - window_pos_x), wl_fixed_from_int(pointer_y - window_pos_y)))) << "set_window_geometry offset was ignored"; ASSERT_THAT(client.pointer_position(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - window_pos_x + offset_x), wl_fixed_from_int(pointer_y - window_pos_y + offset_y)))); } TEST_F(XdgToplevelStableTest, touch_respects_window_geom_offset) { const int offset_x = 35, offset_y = 12; const int window_pos_x = 200, window_pos_y = 280; const int pointer_x = window_pos_x + 20, pointer_y = window_pos_y + 30; wlcs::Client client{the_server()}; ConfigurationWindow window{client}; xdg_surface_set_window_geometry(window.xdg_shell_surface, offset_x, offset_y, window.window_width - offset_x, window.window_height - offset_y); wl_surface_commit(window.surface); the_server().move_surface_to(window.surface, window_pos_x, window_pos_y); auto touch = the_server().create_touch(); touch.down_at(pointer_x, pointer_y); client.roundtrip(); ASSERT_THAT(client.touched_window(), Eq((wl_surface*)window.surface)); ASSERT_THAT(client.touch_position(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - window_pos_x), wl_fixed_from_int(pointer_y - window_pos_y)))) << "set_window_geometry offset was ignored"; ASSERT_THAT(client.touch_position(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - window_pos_x + offset_x), wl_fixed_from_int(pointer_y - window_pos_y + offset_y)))); } // TODO: set_window_geometry window size (something will need to be added to wlcs) TEST_F(XdgToplevelStableTest, surface_can_be_moved_interactively) { int window_x = 100, window_y = 100; int window_width = 420, window_height = 390; int start_x = window_x + 5, start_y = window_y + 5; int dx = 60, dy = -40; int end_x = window_x + dx + 20, end_y = window_y + dy + 20; wlcs::Client client{the_server()}; wlcs::Surface surface{client}; wlcs::XdgSurfaceStable xdg_shell_surface{client, surface}; wlcs::XdgToplevelStable toplevel{xdg_shell_surface}; surface.attach_buffer(window_width, window_height); wl_surface_commit(surface); client.roundtrip(); the_server().move_surface_to(surface, window_x, window_y); auto pointer = the_server().create_pointer(); bool button_down{false}; uint32_t last_serial{0}; client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool { last_serial = serial; button_down = is_down; return true; }); pointer.move_to(start_x, start_y); pointer.left_button_down(); client.dispatch_until([&](){ return button_down; }); xdg_toplevel_move(toplevel, client.seat(), last_serial); client.roundtrip(); pointer.move_to(start_x + dx, start_x + dy); pointer.left_button_up(); client.roundtrip(); pointer.move_to(end_x, end_y); client.roundtrip(); EXPECT_THAT(client.window_under_cursor(), Eq(static_cast(surface))); EXPECT_THAT(client.pointer_position(), Eq(std::make_pair( wl_fixed_from_int(end_x - window_x - dx), wl_fixed_from_int(end_y - window_y - dy)))); client.roundtrip(); } // 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(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(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(); } TEST_F(XdgToplevelStableTest, when_parent_is_set_to_self_error_is_raised) { wlcs::Client client{the_server()}; ConfigurationWindow window{client}; xdg_toplevel_set_parent(window, window); wl_surface_commit(window); try { client.roundtrip(); } catch (wlcs::ProtocolError const& err) { return; } FAIL() << "Protocol error not raised"; } TEST_F(XdgToplevelStableTest, when_parent_is_set_to_child_descendant_error_is_raised) { wlcs::Client client{the_server()}; ConfigurationWindow parent{client}; ConfigurationWindow child{client}; xdg_toplevel_set_parent(child, parent); wl_surface_commit(child); client.roundtrip(); ConfigurationWindow grandchild{client}; xdg_toplevel_set_parent(grandchild, child); wl_surface_commit(grandchild); client.roundtrip(); xdg_toplevel_set_parent(parent, grandchild); wl_surface_commit(parent); try { client.roundtrip(); } catch (wlcs::ProtocolError const& err) { return; } FAIL() << "Protocol error not raised"; } 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)); } wlcs-1.7.0/tests/xdg_toplevel_v6.cpp000066400000000000000000000412351453657357500174750ustar00rootroot00000000000000/* * Copyright © 2012 Intel Corporation * Copyright © 2013 Collabora, Ltd. * Copyright © 2018 Canonical Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "helpers.h" #include "in_process_server.h" #include "xdg_shell_v6.h" #include #include using namespace testing; namespace { class ConfigurationWindow { public: int const window_width = 200, window_height = 320; ConfigurationWindow(wlcs::Client& client) : client{client}, surface{client}, xdg_surface{client, surface}, toplevel{xdg_surface} { ON_CALL(xdg_surface, configure).WillByDefault([&](auto serial) { zxdg_surface_v6_ack_configure(xdg_surface, serial); surface_configure_count++; }); ON_CALL(toplevel, configure).WillByDefault([this](auto... args) { state = wlcs::XdgToplevelV6::State{args...}; }); wl_surface_commit(surface); client.roundtrip(); surface.attach_buffer(window_width, window_height); wl_surface_commit(surface); dispatch_until_configure(); } ~ConfigurationWindow() { client.roundtrip(); } void dispatch_until_configure() { client.dispatch_until( [prev_count = surface_configure_count, ¤t_count = surface_configure_count]() { return current_count > prev_count; }); } operator wlcs::Surface&() {return surface;} operator wl_surface*() const {return surface;} operator zxdg_surface_v6*() const {return xdg_surface;} operator zxdg_toplevel_v6*() const {return toplevel;} wlcs::Client& client; wlcs::Surface surface; wlcs::XdgSurfaceV6 xdg_surface; wlcs::XdgToplevelV6 toplevel; int surface_configure_count{0}; wlcs::XdgToplevelV6::State state{0, 0, nullptr}; }; } using XdgToplevelV6Test = wlcs::InProcessServer; // there *could* be a bug in these tests, but also the window manager may not be behaving properly // lets take another look when we've updated the window manager TEST_F(XdgToplevelV6Test, pointer_respects_window_geom_offset) { const int offset_x = 35, offset_y = 12; const int window_pos_x = 200, window_pos_y = 280; const int pointer_x = window_pos_x + 20, pointer_y = window_pos_y + 30; wlcs::Client client{the_server()}; ConfigurationWindow window{client}; zxdg_surface_v6_set_window_geometry(window.xdg_surface, offset_x, offset_y, window.window_width - offset_x, window.window_height - offset_y); wl_surface_commit(window.surface); the_server().move_surface_to(window.surface, window_pos_x, window_pos_y); auto pointer = the_server().create_pointer(); pointer.move_to(pointer_x, pointer_y); client.roundtrip(); ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)window.surface)); ASSERT_THAT(client.pointer_position(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - window_pos_x), wl_fixed_from_int(pointer_y - window_pos_y)))) << "set_window_geometry offset was ignored"; ASSERT_THAT(client.pointer_position(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - window_pos_x + offset_x), wl_fixed_from_int(pointer_y - window_pos_y + offset_y)))); } TEST_F(XdgToplevelV6Test, touch_respects_window_geom_offset) { const int offset_x = 35, offset_y = 12; const int window_pos_x = 200, window_pos_y = 280; const int pointer_x = window_pos_x + 20, pointer_y = window_pos_y + 30; wlcs::Client client{the_server()}; ConfigurationWindow window{client}; zxdg_surface_v6_set_window_geometry(window.xdg_surface, offset_x, offset_y, window.window_width - offset_x, window.window_height - offset_y); wl_surface_commit(window.surface); the_server().move_surface_to(window.surface, window_pos_x, window_pos_y); auto touch = the_server().create_touch(); touch.down_at(pointer_x, pointer_y); client.roundtrip(); ASSERT_THAT(client.touched_window(), Eq((wl_surface*)window.surface)); ASSERT_THAT(client.touch_position(), Ne(std::make_pair( wl_fixed_from_int(pointer_x - window_pos_x), wl_fixed_from_int(pointer_y - window_pos_y)))) << "set_window_geometry offset was ignored"; ASSERT_THAT(client.touch_position(), Eq(std::make_pair( wl_fixed_from_int(pointer_x - window_pos_x + offset_x), wl_fixed_from_int(pointer_y - window_pos_y + offset_y)))); } // TODO: set_window_geometry window size (something will need to be added to wlcs) TEST_F(XdgToplevelV6Test, surface_can_be_moved_interactively) { int window_x = 100, window_y = 100; int window_width = 420, window_height = 390; int start_x = window_x + 5, start_y = window_y + 5; int dx = 60, dy = -40; int end_x = window_x + dx + 20, end_y = window_y + dy + 20; wlcs::Client client{the_server()}; wlcs::Surface surface{client}; wlcs::XdgSurfaceV6 xdg_surface{client, surface}; wlcs::XdgToplevelV6 toplevel{xdg_surface}; surface.attach_buffer(window_width, window_height); wl_surface_commit(surface); client.roundtrip(); the_server().move_surface_to(surface, window_x, window_y); auto pointer = the_server().create_pointer(); bool button_down{false}; uint32_t last_serial{0}; client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool { last_serial = serial; button_down = is_down; return true; }); pointer.move_to(start_x, start_y); pointer.left_button_down(); client.dispatch_until([&](){ return button_down; }); zxdg_toplevel_v6_move(toplevel, client.seat(), last_serial); client.roundtrip(); pointer.move_to(start_x + dx, start_x + dy); pointer.left_button_up(); client.roundtrip(); client.dispatch_until([&](){ return !button_down; }); pointer.move_to(end_x, end_y); client.roundtrip(); EXPECT_THAT(client.window_under_cursor(), Eq(static_cast(surface))); EXPECT_THAT(client.pointer_position(), Eq(std::make_pair( wl_fixed_from_int(end_x - window_x - dx), wl_fixed_from_int(end_y - window_y - dy)))); client.roundtrip(); } TEST_F(XdgToplevelV6Test, pointer_leaves_surface_during_interactive_move) { int window_x = 100, window_y = 100; int window_width = 420, window_height = 390; int start_x = window_x + 5, start_y = window_y + 5; wlcs::Client client{the_server()}; wlcs::Surface surface{client}; wlcs::XdgSurfaceV6 xdg_shell_surface{client, surface}; wlcs::XdgToplevelV6 toplevel{xdg_shell_surface}; surface.attach_buffer(window_width, window_height); wl_surface_commit(surface); client.roundtrip(); the_server().move_surface_to(surface, window_x, window_y); auto pointer = the_server().create_pointer(); bool button_down{false}; uint32_t last_serial{0}; client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool { last_serial = serial; button_down = is_down; return true; }); pointer.move_to(start_x, start_y); pointer.left_button_down(); client.dispatch_until([&](){ return button_down; }); zxdg_toplevel_v6_move(toplevel, client.seat(), last_serial); client.dispatch_until([&](){ return !client.window_under_cursor(); }); } TEST_F(XdgToplevelV6Test, surface_can_be_resized_interactively) { int window_x = 100, window_y = 100; int window_width = 420, window_height = 390; int start_x = window_x + 5, start_y = window_y + 5; int dx = 60, dy = -40; int end_x = window_x + dx + 20, end_y = window_y + dy + 20; wlcs::Client client{the_server()}; wlcs::Surface surface{client}; wlcs::XdgSurfaceV6 xdg_shell_surface{client, surface}; wlcs::XdgToplevelV6 toplevel{xdg_shell_surface}; surface.attach_buffer(window_width, window_height); wl_surface_commit(surface); client.roundtrip(); the_server().move_surface_to(surface, window_x, window_y); auto pointer = the_server().create_pointer(); bool button_down{false}; uint32_t last_serial{0}; client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool { last_serial = serial; button_down = is_down; return true; }); pointer.move_to(start_x, start_y); pointer.left_button_down(); client.dispatch_until([&](){ return button_down; }); zxdg_toplevel_v6_resize(toplevel, client.seat(), last_serial, ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT); client.roundtrip(); pointer.move_to(start_x + dx, start_x + dy); pointer.left_button_up(); client.roundtrip(); pointer.move_to(end_x, end_y); client.roundtrip(); EXPECT_THAT(client.window_under_cursor(), Eq(static_cast(surface))); EXPECT_THAT(client.pointer_position(), Eq(std::make_pair( wl_fixed_from_int(end_x - window_x - dx), wl_fixed_from_int(end_y - window_y - dy)))); client.roundtrip(); } TEST_F(XdgToplevelV6Test, pointer_leaves_surface_during_interactive_resize) { int window_x = 100, window_y = 100; int window_width = 420, window_height = 390; int start_x = window_x + 5, start_y = window_y + 5; wlcs::Client client{the_server()}; wlcs::Surface surface{client}; wlcs::XdgSurfaceV6 xdg_shell_surface{client, surface}; wlcs::XdgToplevelV6 toplevel{xdg_shell_surface}; surface.attach_buffer(window_width, window_height); wl_surface_commit(surface); client.roundtrip(); the_server().move_surface_to(surface, window_x, window_y); auto pointer = the_server().create_pointer(); bool button_down{false}; uint32_t last_serial{0}; client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool { last_serial = serial; button_down = is_down; return true; }); pointer.move_to(start_x, start_y); pointer.left_button_down(); client.dispatch_until([&](){ return button_down; }); zxdg_toplevel_v6_resize(toplevel, client.seat(), last_serial, ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT); client.dispatch_until([&](){ return !client.window_under_cursor(); }); } TEST_F(XdgToplevelV6Test, parent_can_be_set) { const int window_pos_x = 200, window_pos_y = 280; wlcs::Client client{the_server()}; ConfigurationWindow parent{client}; the_server().move_surface_to(parent.surface, window_pos_x, window_pos_y); ConfigurationWindow child{client}; the_server().move_surface_to(child.surface, window_pos_x, window_pos_y); zxdg_toplevel_v6_set_parent(child, parent); wl_surface_commit(child.surface); client.roundtrip(); } TEST_F(XdgToplevelV6Test, null_parent_can_be_set) { const int window_pos_x = 200, window_pos_y = 280; wlcs::Client client{the_server()}; ConfigurationWindow window{client}; the_server().move_surface_to(window.surface, window_pos_x, window_pos_y); zxdg_toplevel_v6_set_parent(window, nullptr); wl_surface_commit(window.surface); client.roundtrip(); } // TODO: interactive resize // This would probably make sense as a parameterized test, with resizing in all directions // Like move, resize is not implemented in the current WLCS window manager, and should not be tested until it is using XdgToplevelV6ConfigurationTest = wlcs::InProcessServer; TEST_F(XdgToplevelV6ConfigurationTest, defaults) { wlcs::Client client{the_server()}; ConfigurationWindow window{client}; auto& state = window.state; // default values EXPECT_THAT(state.width, Eq(0)); EXPECT_THAT(state.height, Eq(0)); EXPECT_THAT(state.maximized, Eq(false)); EXPECT_THAT(state.fullscreen, Eq(false)); EXPECT_THAT(state.resizing, Eq(false)); EXPECT_THAT(state.activated, Eq(true)); } TEST_F(XdgToplevelV6ConfigurationTest, window_can_maximize_itself) { wlcs::Client client{the_server()}; ConfigurationWindow window{client}; auto& state = window.state; zxdg_toplevel_v6_set_maximized(window); window.dispatch_until_configure(); EXPECT_THAT(state.width, Gt(0)); EXPECT_THAT(state.height, Gt(0)); EXPECT_THAT(state.maximized, Eq(true)); EXPECT_THAT(state.fullscreen, Eq(false)); EXPECT_THAT(state.resizing, Eq(false)); EXPECT_THAT(state.activated, Eq(true)); } TEST_F(XdgToplevelV6ConfigurationTest, window_can_unmaximize_itself) { wlcs::Client client{the_server()}; ConfigurationWindow window{client}; auto& state = window.state; zxdg_toplevel_v6_set_maximized(window); window.dispatch_until_configure(); ASSERT_THAT(state.maximized, Eq(true)) << "test could not run as precondition failed"; zxdg_toplevel_v6_unset_maximized(window); window.dispatch_until_configure(); EXPECT_THAT(state.maximized, Eq(false)); EXPECT_THAT(state.fullscreen, Eq(false)); EXPECT_THAT(state.resizing, Eq(false)); EXPECT_THAT(state.activated, Eq(true)); } TEST_F(XdgToplevelV6ConfigurationTest, window_can_fullscreen_itself) { wlcs::Client client{the_server()}; ConfigurationWindow window{client}; auto& state = window.state; zxdg_toplevel_v6_set_fullscreen(window, nullptr); window.dispatch_until_configure(); EXPECT_THAT(state.width, Gt(0)); EXPECT_THAT(state.height, Gt(0)); EXPECT_THAT(state.maximized, Eq(false)); // is this right? should it not be maximized, even when fullscreen? EXPECT_THAT(state.fullscreen, Eq(true)); EXPECT_THAT(state.resizing, Eq(false)); EXPECT_THAT(state.activated, Eq(true)); } TEST_F(XdgToplevelV6ConfigurationTest, window_can_unfullscreen_itself) { wlcs::Client client{the_server()}; ConfigurationWindow window{client}; auto& state = window.state; zxdg_toplevel_v6_set_fullscreen(window, nullptr); window.dispatch_until_configure(); EXPECT_THAT(state.fullscreen, Eq(true)) << "test could not run as precondition failed"; zxdg_toplevel_v6_unset_fullscreen(window); window.dispatch_until_configure(); EXPECT_THAT(state.maximized, Eq(false)); EXPECT_THAT(state.fullscreen, Eq(false)); EXPECT_THAT(state.resizing, Eq(false)); EXPECT_THAT(state.activated, Eq(true)); } TEST_F(XdgToplevelV6ConfigurationTest, activated_state_follows_pointer) { wlcs::Client client{the_server()}; ConfigurationWindow window_a{client}; auto& state_a = window_a.state; int const a_x = 12, a_y = 15; the_server().move_surface_to(window_a, a_x, a_y); ConfigurationWindow window_b{client}; auto& state_b = window_b.state; int const b_x = a_x + window_a.window_width + 27, b_y = 15; the_server().move_surface_to(window_b, b_x, b_y); auto pointer = the_server().create_pointer(); pointer.move_to(a_x + 10, a_y + 10); pointer.left_click(); client.roundtrip(); ASSERT_THAT(state_a.activated, Eq(true)); ASSERT_THAT(state_b.activated, Eq(false)); pointer.move_to(b_x + 10, b_y + 10); pointer.left_click(); client.roundtrip(); EXPECT_THAT(state_a.activated, Eq(false)); EXPECT_THAT(state_b.activated, Eq(true)); } wlcs-1.7.0/tools/000077500000000000000000000000001453657357500136535ustar00rootroot00000000000000wlcs-1.7.0/tools/make_release_tarball000077500000000000000000000025641453657357500177260ustar00rootroot00000000000000#!/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" wlcs-1.7.0/tools/ppa-upload.sh000077500000000000000000000110601453657357500162520ustar00rootroot00000000000000#!/bin/bash set -e LP_CREDS="$1" if [ -z "${LP_CREDS}" ]; then echo "ERROR: pass the Launchpad credentials file as argument" >&2 exit 2 fi 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)/)?(main|(release/|v)([0-9\.]+))$ ]]; then echo "ERROR: This script should only run on main or release tags" >&2 echo " or branches." >&2 exit 3 fi source <( python <&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/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 main GIT_COMMITS=$( git rev-list --count origin/main..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/rc to ppa:mir-team/release RELEASE_VERSION=${BASH_REMATCH[1]}-0ubuntu${UBUNTU_VERSION} echo "Copying wlcs_${RELEASE_VERSION} from ppa:mir-team/rc to ppa:mir-team/release…" python - ${RELEASE_VERSION} < ../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 wlcs-1.7.0/wlcs.pc.in000066400000000000000000000004721453657357500144170ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@PKGCONFIG_BINDIR@ libexecdir=@PKGCONFIG_LIBEXECDIR@ includedir=@PKGCONFIG_INCLUDEDIR@ test_runner=${libexecdir}/wlcs/wlcs Name: wlcs Description: Wayland Conformance Suite test harness Version: @PROJECT_VERSION@ Requires.private: wayland-client Cflags: -I${includedir}