├── test ├── test_fnlist.txt ├── test.sh ├── trace_perf.sh ├── trace_bcc.sh ├── common.h ├── fake_ssh.c ├── protocol_functions.txt ├── test-proto.xml ├── build_matrix.py ├── fuzz_hook_det.c ├── meson.build ├── diff_roundtrip.c ├── wire_parse.c ├── fuzz_hook_int.c ├── fuzz_hook_ext.c └── startup_failure.py ├── .gitignore ├── autoformat.sh ├── .gitlab-ci ├── debian-install.sh ├── debian32-install.sh └── debian32.yml ├── minimal_build.sh ├── COPYING ├── protocols ├── symgen_types.h ├── meson.build ├── function_list.txt ├── virtual-keyboard-unstable-v1.xml ├── wlr-gamma-control-unstable-v1.xml ├── sendgen.py ├── wayland-drm.xml ├── wlr-export-dmabuf-unstable-v1.xml └── gtk-primary-selection.xml ├── meson_options.txt ├── .gitlab-ci.yml ├── CONTRIBUTING.md ├── src ├── main.h ├── meson.build ├── kernel.h ├── interval.h ├── kernel_avx512f.c ├── kernel_neon.c ├── dmabuf.h ├── kernel_sse3.c ├── platform.c ├── kernel_avx2.c ├── parsing.h ├── interval.c └── kernel.c ├── .clang-format ├── meson.build └── README.md /test/test_fnlist.txt: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | waypipe 2 | build/ 3 | Doxyfile 4 | html 5 | latex 6 | doc 7 | test/matrix 8 | /build-minimal/ 9 | -------------------------------------------------------------------------------- /autoformat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | black -q test/*.py protocols/*.py 4 | clang-format -style=file --assume-filename=C -i src/*.h src/*.c test/*.c test/*.h 5 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | root=`pwd` 4 | 5 | waypipe=`which waypipe` 6 | 7 | program=`which ${1:-weston-terminal}` 8 | 9 | debug= 10 | debug=-d 11 | 12 | # Orange=client, purple=server 13 | 14 | rm -f /tmp/waypipe-server.sock /tmp/waypipe-client.sock 15 | ($waypipe -o $debug client 2>&1 | sed 's/.*/&/') & 16 | # ssh-to-self; should have a local keypair set up 17 | (ssh -R /tmp/waypipe-server.sock:/tmp/waypipe-client.sock localhost $waypipe -o $debug server -- $program) 2>&1 | sed 's/.*/&/' 18 | kill %1 19 | rm -f /tmp/waypipe-server.sock /tmp/waypipe-client.sock 20 | -------------------------------------------------------------------------------- /.gitlab-ci/debian-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -o xtrace 4 | 5 | apt-get update 6 | apt-get -y --no-install-recommends install \ 7 | build-essential git automake autoconf libtool pkg-config libexpat1-dev \ 8 | libffi-dev libxml2-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev \ 9 | libgles2-mesa-dev libwayland-dev libudev-dev libgbm-dev libxkbcommon-dev \ 10 | libvpx-dev libva-dev curl python3-pip python3-setuptools ninja-build weston \ 11 | liblz4-dev libzstd-dev wayland-protocols libavcodec-dev libavutil-dev \ 12 | libswscale-dev 13 | 14 | pip3 install --user git+https://github.com/mesonbuild/meson.git@0.47 15 | 16 | -------------------------------------------------------------------------------- /test/trace_perf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | # This probably requires root to set up the probes, and 6 | # a low sys/kernel/perf_event_paranoid to record them. 7 | 8 | # Also, perf record can create huge (>1 GB) files on busy machines, 9 | # so it's recommended to run this on a tmpfs 10 | 11 | prog=$(which waypipe) 12 | capture_time=${1:-120} 13 | 14 | setup="perf buildid-cache -a `which waypipe` ; perf probe -d sdt_waypipe:* ; perf probe sdt_waypipe:* ;" 15 | sudo -- sh -c "$setup" 16 | sudo perf record -e sdt_waypipe:*,sched:sched_switch -aR sleep $capture_time 17 | sudo chmod 644 perf.data 18 | 19 | perf script --ns | gzip -9 >scriptfile.gz 20 | -------------------------------------------------------------------------------- /.gitlab-ci/debian32-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -o xtrace 4 | 5 | dpkg --print-foreign-architectures 6 | dpkg --add-architecture i386 7 | 8 | apt-get update 9 | 10 | # Actual package dependencies 11 | apt-get -y --no-install-recommends install wayland-protocols:i386 pkg-config:i386 libwayland-dev:i386 libgbm-dev:i386 liblz4-dev:i386 libzstd-dev:i386 libavcodec-dev:i386 libavutil-dev:i386 libswscale-dev:i386 weston:i386 libdrm-dev:i386 12 | apt-get -y --no-install-recommends install gcc-8-multilib:i386 gcc-8:i386 make:i386 13 | 14 | # Build scripts, architecture doesn't matter 15 | apt-get -y --no-install-recommends install git python3-pip python3-wheel python3-setuptools ninja-build scdoc 16 | pip3 install --user git+https://github.com/mesonbuild/meson.git@0.47 17 | 18 | -------------------------------------------------------------------------------- /test/trace_bcc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # With bcc 'tplist -l `which waypipe`', can list all probes 5 | # With bcc 'trace', can print events, arguments, and timestamps 6 | 7 | sudo /usr/share/bcc/tools/trace -t \ 8 | 'u:/usr/bin/waypipe:construct_diff_exit "diffsize %d", arg1' \ 9 | 'u:/usr/bin/waypipe:construct_diff_enter "rects %d", arg1' \ 10 | 'u:/usr/bin/waypipe:apply_diff_enter "size %d diffsize %d", arg1, arg2' \ 11 | 'u:/usr/bin/waypipe:apply_diff_exit' \ 12 | 'u:/usr/bin/waypipe:channel_write_end' \ 13 | 'u:/usr/bin/waypipe:channel_write_start "size %d", arg1' \ 14 | 'u:/usr/bin/waypipe:worker_comp_enter "index %d", arg1' \ 15 | 'u:/usr/bin/waypipe:worker_comp_exit "index %d", arg1' \ 16 | 'u:/usr/bin/waypipe:worker_compdiff_enter "index %d", arg1' \ 17 | 'u:/usr/bin/waypipe:worker_compdiff_exit "index %d", arg1' 18 | -------------------------------------------------------------------------------- /minimal_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "This script is a backup build system in case meson/ninja are unavailable." 5 | echo "No optional features or optimizations are included. Waypipe will be slow." 6 | echo "Requirements: python3, gcc, libc+pthreads" 7 | echo "Enter to continue, interrupt to exit." 8 | read unused 9 | 10 | mkdir -p build-minimal 11 | cd build-minimal 12 | 13 | echo "Generating code..." 14 | python3 ../protocols/symgen.py data ../protocols/function_list.txt protocols.c \ 15 | ../protocols/*.xml 16 | python3 ../protocols/symgen.py header ../protocols/function_list.txt protocols.h \ 17 | ../protocols/*.xml 18 | echo '#define WAYPIPE_VERSION "minimal"' > config-waypipe.h 19 | 20 | echo "Compiling..." 21 | gcc -D_DEFAULT_SOURCE -I. -I../protocols/ -lpthread -o waypipe protocols.c \ 22 | ../src/bench.c ../src/client.c ../src/dmabuf.c ../src/handlers.c \ 23 | ../src/interval.c ../src/kernel.c ../src/mainloop.c ../src/parsing.c \ 24 | ../src/platform.c ../src/server.c ../src/shadow.c ../src/util.c \ 25 | ../src/video.c ../src/waypipe.c 26 | 27 | cd .. 28 | echo "Done. See ./build-minimal/waypipe" 29 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright © 2019 Manuel Stoeckl 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | 22 | --- 23 | 24 | The above is the version of the MIT "Expat" License used by X.org: 25 | 26 | http://cgit.freedesktop.org/xorg/xserver/tree/COPYING 27 | -------------------------------------------------------------------------------- /protocols/symgen_types.h: -------------------------------------------------------------------------------- 1 | #ifndef SYMGEN_TYPES_H 2 | #define SYMGEN_TYPES_H 3 | #include 4 | #include 5 | struct context; 6 | struct message_tracker; 7 | struct wp_object; 8 | typedef void (*wp_callfn_t)(struct context *ctx, const uint32_t *payload, const int *fds, struct message_tracker *mt); 9 | #define GAP_CODE_END 0x0 10 | #define GAP_CODE_OBJ 0x1 11 | #define GAP_CODE_ARR 0x2 12 | #define GAP_CODE_STR 0x3 13 | struct msg_data { 14 | /* Number of 4-byte blocks until next nontrivial input. 15 | * (Note: 16-bit length is sufficient since message lengths also 16-bit) 16 | * Lowest 2 bits indicate if what follows is end/obj/array/string */ 17 | const uint16_t* gaps; 18 | /* Pointer to new object types, can be null if none indicated */ 19 | const struct wp_interface **new_objs; 20 | /* Function pointer to parse + invoke do_ handler */ 21 | const wp_callfn_t call; 22 | /* Number of associated file descriptors */ 23 | const int16_t n_fds; 24 | /* Whether message destroys the object */ 25 | bool is_destructor; 26 | }; 27 | struct wp_interface { 28 | /* msgs[0..nreq-1] are reqs; msgs[nreq..nreq+nevt-1] are evts */ 29 | const struct msg_data *msgs; 30 | const int nreq, nevt; 31 | /* The name of the interface */ 32 | const char *name; 33 | /* The names of the messages, in order; stored tightly packed */ 34 | const char *msg_names; 35 | }; 36 | /* User should define this function. */ 37 | struct wp_object *get_object(struct message_tracker *mt, uint32_t id, const struct wp_interface *intf); 38 | #endif /* SYMGEN_TYPES_H */ 39 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') 2 | option('with_video', type : 'feature', value : 'auto', description : 'Link with ffmpeg libraries and provide a command line option to display all buffers using a video stream') 3 | option('with_dmabuf', type : 'feature', value : 'auto', description : 'Support DMABUFs, the file descriptors used to exchange data for e.g. OpenGL applications') 4 | option('with_lz4', type : 'feature', value : 'auto', description : 'Support LZ4 as a compression mechanism') 5 | option('with_zstd', type : 'feature', value : 'auto', description : 'Support ZStandard as a compression mechanism') 6 | option('with_vaapi', type : 'feature', value : 'auto', description : 'Link with libva and use VAAPI to perform hardware video output color space conversions on GPU') 7 | option('with_systemtap', type: 'boolean', value: true, description: 'Enable tracing using sdt and provide static tracepoints for profiling') 8 | 9 | # It is recommended to keep these on; Waypipe will automatically select the highest available instruction set at runtime 10 | option('with_avx512f', type: 'boolean', value: true, description: 'Compile with support for AVX512f SIMD instructions') 11 | option('with_avx2', type: 'boolean', value: true, description: 'Compile with support for AVX2 SIMD instructions') 12 | option('with_sse3', type: 'boolean', value: true, description: 'Compile with support for SSE3 SIMD instructions') 13 | option('with_neon_opts', type: 'boolean', value: true, description: 'Compile with support for ARM64 neon instructions') 14 | -------------------------------------------------------------------------------- /protocols/meson.build: -------------------------------------------------------------------------------- 1 | 2 | symgen_path = join_paths(meson.current_source_dir(), 'symgen.py') 3 | sendgen_path = join_paths(meson.current_source_dir(), 'sendgen.py') 4 | fn_list = join_paths(meson.current_source_dir(), 'function_list.txt') 5 | 6 | # Include a copy of these protocols in the repository, rather than looking 7 | # for packages containing them, to: 8 | # a) avoid versioning problems as new protocols/methods are introduced 9 | # b) keep the minimum build complexity for waypipe low 10 | # c) be able to relay through newer protocols than are default on a system 11 | protocols = [ 12 | 'wayland.xml', 13 | 'xdg-shell.xml', 14 | 'presentation-time.xml', 15 | 'linux-dmabuf-unstable-v1.xml', 16 | 'gtk-primary-selection.xml', 17 | 'input-method-unstable-v2.xml', 18 | 'primary-selection-unstable-v1.xml', 19 | 'virtual-keyboard-unstable-v1.xml', 20 | 'wlr-screencopy-unstable-v1.xml', 21 | 'wlr-export-dmabuf-unstable-v1.xml', 22 | 'wlr-data-control-unstable-v1.xml', 23 | 'wlr-gamma-control-unstable-v1.xml', 24 | 'wayland-drm.xml', 25 | ] 26 | 27 | protocols_src = [] 28 | protocols_src += custom_target( 29 | 'protocol code', 30 | output: 'protocols.c', 31 | input: protocols, 32 | depend_files: [fn_list, symgen_path], 33 | command: [python3, symgen_path, 'data', fn_list, '@OUTPUT@', '@INPUT@'], 34 | ) 35 | protocols_src += custom_target( 36 | 'protocol header', 37 | output: 'protocols.h', 38 | input: protocols, 39 | depend_files: [fn_list, symgen_path], 40 | command: [python3, symgen_path, 'header', fn_list, '@OUTPUT@', '@INPUT@'], 41 | ) 42 | 43 | # For use in test 44 | abs_protocols = [] 45 | foreach xml : protocols 46 | abs_protocols += join_paths(meson.current_source_dir(), xml) 47 | endforeach 48 | -------------------------------------------------------------------------------- /protocols/function_list.txt: -------------------------------------------------------------------------------- 1 | gtk_primary_selection_offer_req_receive 2 | gtk_primary_selection_source_evt_send 3 | wl_buffer_evt_release 4 | wl_data_offer_req_receive 5 | wl_data_source_evt_send 6 | wl_display_evt_delete_id 7 | wl_display_evt_error 8 | wl_display_req_get_registry 9 | wl_display_req_sync 10 | wl_drm_evt_device 11 | wl_drm_req_create_prime_buffer 12 | wl_keyboard_evt_keymap 13 | wl_registry_evt_global 14 | wl_registry_evt_global_remove 15 | wl_registry_req_bind 16 | wl_shm_req_create_pool 17 | wl_shm_pool_req_create_buffer 18 | wl_shm_pool_req_resize 19 | wl_surface_req_attach 20 | wl_surface_req_commit 21 | wl_surface_req_damage 22 | wl_surface_req_damage_buffer 23 | wl_surface_req_set_buffer_transform 24 | wl_surface_req_set_buffer_scale 25 | wp_presentation_evt_clock_id 26 | wp_presentation_feedback_evt_presented 27 | wp_presentation_req_feedback 28 | zwlr_data_control_offer_v1_req_receive 29 | zwlr_data_control_source_v1_evt_send 30 | zwlr_export_dmabuf_frame_v1_evt_frame 31 | zwlr_export_dmabuf_frame_v1_evt_object 32 | zwlr_export_dmabuf_frame_v1_evt_ready 33 | zwlr_gamma_control_v1_req_set_gamma 34 | zwlr_screencopy_frame_v1_evt_ready 35 | zwlr_screencopy_frame_v1_req_copy 36 | zwp_linux_dmabuf_feedback_v1_evt_done 37 | zwp_linux_dmabuf_feedback_v1_evt_format_table 38 | zwp_linux_dmabuf_feedback_v1_evt_main_device 39 | zwp_linux_dmabuf_feedback_v1_evt_tranche_done 40 | zwp_linux_dmabuf_feedback_v1_evt_tranche_target_device 41 | zwp_linux_dmabuf_feedback_v1_evt_tranche_formats 42 | zwp_linux_dmabuf_feedback_v1_evt_tranche_flags 43 | zwp_linux_buffer_params_v1_evt_created 44 | zwp_linux_buffer_params_v1_req_add 45 | zwp_linux_buffer_params_v1_req_create 46 | zwp_linux_buffer_params_v1_req_create_immed 47 | zwp_linux_dmabuf_v1_evt_modifier 48 | zwp_linux_dmabuf_v1_req_get_default_feedback 49 | zwp_linux_dmabuf_v1_req_get_surface_feedback 50 | zwp_primary_selection_offer_v1_req_receive 51 | zwp_primary_selection_source_v1_evt_send 52 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | 2 | variables: 3 | UPSTREAM_REPO: mstoeckl/waypipe 4 | 5 | DEBIAN_TAG: '2019-06-25.1' 6 | DEBIAN_VERSION: buster 7 | DEBIAN_EXEC: 'bash .gitlab-ci/debian-install.sh' 8 | DEBIAN_CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/debian/$DEBIAN_VERSION:$DEBIAN_TAG 9 | 10 | DEBIAN32_TAG: 'deb32-2019-07-04.2' 11 | DEBIAN32_VERSION: buster 12 | DEBIAN32_EXEC: 'bash .gitlab-ci/debian32-install.sh' 13 | DEBIAN32_CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/debian/$DEBIAN32_VERSION:$DEBIAN32_TAG 14 | 15 | include: 16 | - project: 'wayland/ci-templates' 17 | ref: 96912c7331cbc6da41fbf22c4217aa541176f063 18 | file: '/templates/debian.yml' 19 | - project: 'mstoeckl/waypipe' 20 | file: '/.gitlab-ci/debian32.yml' 21 | ref: master 22 | 23 | stages: 24 | - container_prep 25 | - build 26 | 27 | debian_container_prep: 28 | extends: .debian@container-ifnot-exists 29 | stage: container_prep 30 | 31 | debian32_container_prep: 32 | extends: .debian32@container-ifnot-exists 33 | stage: container_prep 34 | 35 | .build-debian-all: 36 | stage: build 37 | script: 38 | - export PATH=~/.local/bin:$PATH 39 | - cd "$BUILDDIR" 40 | - meson --prefix="$PREFIX" -Dwerror=true .. 41 | - ninja -k0 42 | - ninja install 43 | - ninja test 44 | - ninja clean 45 | artifacts: 46 | name: waypipe-$CI_COMMIT_SHA-$CI_JOB_ID 47 | when: always 48 | paths: 49 | - b-*/meson-logs 50 | - b-*/run 51 | - p-* 52 | 53 | debian-build: 54 | before_script: 55 | - export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)" 56 | - export BUILD_ID="$CI_COMMIT_SHA-$CI_JOB_ID" 57 | - export PREFIX="$(pwd)/p-$BUILD_ID" 58 | - export BUILDDIR="$(pwd)/b-$BUILD_ID" 59 | - mkdir "$BUILDDIR" "$PREFIX" 60 | - export CC=gcc 61 | extends: .build-debian-all 62 | image: $DEBIAN_CONTAINER_IMAGE 63 | 64 | debian32-build: 65 | before_script: 66 | - export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)" 67 | - export BUILD_ID="$CI_COMMIT_SHA-$CI_JOB_ID" 68 | - export PREFIX="$(pwd)/p-$BUILD_ID" 69 | - export BUILDDIR="$(pwd)/b-$BUILD_ID" 70 | - mkdir "$BUILDDIR" "$PREFIX" 71 | - export CC=/usr/bin/gcc-8 72 | - export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig/ 73 | extends: .build-debian-all 74 | image: $DEBIAN32_CONTAINER_IMAGE 75 | 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing guidelines 2 | =============================================================================== 3 | 4 | ## Formatting 5 | 6 | To avoid needless time spent formatting things, this project has autoformatting 7 | set up. Yes, it's often ugly, but after using it long enough you'll forget that 8 | code can look nice. Python scripts are formatted with black[0], and C code with 9 | clang-format[1]. The script `autoformat.sh` at the root of the directory should 10 | format all source code files in the project. 11 | 12 | [0] https://github.com/python/black 13 | [1] https://clang.llvm.org/docs/ClangFormat.html 14 | 15 | ## Types 16 | 17 | * Typedefs should be used only for function signatures, and never applied to 18 | structs. 19 | * `short`, `long`, and `long long` should not be used, in favor of `int16_t` 20 | and `int64_t`. 21 | * All wire-format structures should use fixed size types. It's safe to assume 22 | that buffers will never be larger than about 1 GB, so buffer sizes and 23 | indices do not require 64 bit types when used in protocol message headers. 24 | * `printf` should be called with the correct format codes. For example, `%zd` 25 | for `ssize_t`, and the `PRIu32` macro for `uint32_t`. 26 | * Avoid unnecessary casts. 27 | 28 | ## Comments 29 | 30 | Explain precisely that which is not obvious. `/* ... */` is preferred to 31 | `// ...` for longer comments; the leading `/*` and trailing `*/ do not need 32 | lines of their own. Use Doxygen style (`/**`) for functions and structs that 33 | need commenting, but not to the point where it hinders source code readability. 34 | Waypipe is not a library. 35 | 36 | ## Memory and errors 37 | 38 | All error conditions should be handled, including the errors produced by 39 | allocation failures. (It is relatively easy to test for allocation failure 40 | by `LD_PRELOAD`ing a library that redefines malloc et al.; see for instance 41 | "mallocfail" and "failmalloc". `ulimit -v` may be less effective.) 42 | 43 | Some errors are unrecoverable, and for those cases Waypipe should shut down 44 | cleanly. For instance, if Waypipe cannot replicate a file descriptor, then an 45 | application connected through it will almost certainly crash, and it's better 46 | to have Waypipe exit instead. Other errors can safely ignored -- if fine 47 | grained damage tracking fails, a sane fallback would be to assume that an 48 | entire surface is damaged. 49 | -------------------------------------------------------------------------------- /test/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | #ifndef WAYPIPE_TESTCOMMON_H 26 | #define WAYPIPE_TESTCOMMON_H 27 | 28 | #include "main.h" 29 | #include "parsing.h" 30 | #include "util.h" 31 | 32 | /** a simple log handler to STDOUT for use by test programs */ 33 | void test_log_handler(const char *file, int line, enum log_level level, 34 | const char *fmt, ...); 35 | void test_atomic_log_handler(const char *file, int line, enum log_level level, 36 | const char *fmt, ...); 37 | 38 | extern uint64_t time_value; 39 | extern uint64_t local_time_offset; 40 | 41 | void *read_file_into_mem(const char *path, size_t *len); 42 | struct msg { 43 | uint32_t *data; 44 | int len; 45 | int *fds; 46 | int nfds; 47 | }; 48 | struct test_state { 49 | struct main_config config; 50 | struct globals glob; 51 | bool display_side; 52 | bool failed; 53 | /* messages received from the other side */ 54 | int nrcvd; 55 | struct msg *rcvd; 56 | uint64_t local_time_offset; 57 | }; 58 | 59 | void send_wayland_msg(struct test_state *src, const struct msg msg, 60 | struct transfer_queue *queue); 61 | void receive_wire(struct test_state *src, struct transfer_queue *queue); 62 | 63 | void send_protocol_msg(struct test_state *src, struct test_state *dst, 64 | const struct msg msg); 65 | int setup_state(struct test_state *s, bool display_side, bool has_gpu); 66 | void cleanup_state(struct test_state *s); 67 | 68 | #endif /* WAYPIPE_TESTCOMMON_H */ 69 | -------------------------------------------------------------------------------- /test/fake_ssh.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2021 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | static int usage(void) 33 | { 34 | fprintf(stderr, "usage: fake_ssh [-R A:B] [-t] destination command...\n"); 35 | return EXIT_FAILURE; 36 | } 37 | 38 | int main(int argc, char **argv) 39 | { 40 | if (argc < 2) { 41 | return usage(); 42 | } 43 | argv++; 44 | argc--; 45 | 46 | bool pseudoterminal = false; 47 | char *link = NULL; 48 | char *destination = NULL; 49 | while (argc > 0) { 50 | if (strcmp(argv[0], "-t") == 0) { 51 | pseudoterminal = true; 52 | argv++; 53 | argc--; 54 | } else if (strcmp(argv[0], "-R") == 0) { 55 | link = argv[1]; 56 | argv += 2; 57 | argc -= 2; 58 | } else { 59 | destination = argv[0]; 60 | argv++; 61 | argc--; 62 | break; 63 | } 64 | } 65 | 66 | if (link) { 67 | char *p1 = link, *p2 = NULL; 68 | for (char *c = link; *c; c++) { 69 | if (*c == ':') { 70 | *c = '\0'; 71 | p2 = c + 1; 72 | break; 73 | } 74 | } 75 | if (!p2) { 76 | fprintf(stderr, "Failed to split forwarding descriptor '%s'\n", 77 | p1); 78 | return EXIT_FAILURE; 79 | } 80 | unlink(p1); 81 | if (symlink(p2, p1) == -1) { 82 | fprintf(stderr, "Symlinking '%s' to '%s' failed\n", p2, 83 | p1); 84 | return EXIT_FAILURE; 85 | } 86 | } 87 | (void)destination; 88 | (void)pseudoterminal; 89 | 90 | if (execvp(argv[0], argv) == -1) { 91 | fprintf(stderr, "Failed to run program '%s'\n", argv[0]); 92 | return EXIT_FAILURE; 93 | } 94 | 95 | return EXIT_SUCCESS; 96 | } 97 | -------------------------------------------------------------------------------- /test/protocol_functions.txt: -------------------------------------------------------------------------------- 1 | gtk_primary_selection_device_evt_data_offer 2 | gtk_primary_selection_device_evt_selection 3 | gtk_primary_selection_device_manager_req_create_source 4 | gtk_primary_selection_device_manager_req_get_device 5 | gtk_primary_selection_device_req_set_selection 6 | gtk_primary_selection_offer_evt_offer 7 | gtk_primary_selection_offer_req_receive 8 | gtk_primary_selection_source_evt_send 9 | gtk_primary_selection_source_req_offer 10 | wl_compositor_req_create_surface 11 | wl_data_device_evt_data_offer 12 | wl_data_device_evt_selection 13 | wl_data_device_manager_req_create_data_source 14 | wl_data_device_manager_req_get_data_device 15 | wl_data_device_req_set_selection 16 | wl_data_offer_evt_offer 17 | wl_data_offer_req_receive 18 | wl_data_source_evt_send 19 | wl_data_source_req_offer 20 | wl_display_req_get_registry 21 | wl_drm_evt_device 22 | wl_drm_evt_format 23 | wl_drm_evt_capabilities 24 | wl_drm_req_create_prime_buffer 25 | wl_keyboard_evt_keymap 26 | wl_registry_evt_global 27 | wl_registry_req_bind 28 | wl_seat_evt_capabilities 29 | wl_seat_req_get_keyboard 30 | wl_shm_pool_req_create_buffer 31 | wl_shm_req_create_pool 32 | wl_surface_req_attach 33 | wl_surface_req_commit 34 | wl_surface_req_damage 35 | wp_presentation_evt_clock_id 36 | wp_presentation_req_feedback 37 | wp_presentation_feedback_evt_presented 38 | zwlr_data_control_device_v1_evt_data_offer 39 | zwlr_data_control_device_v1_evt_selection 40 | zwlr_data_control_device_v1_req_set_selection 41 | zwlr_data_control_manager_v1_req_create_data_source 42 | zwlr_data_control_manager_v1_req_get_data_device 43 | zwlr_data_control_offer_v1_evt_offer 44 | zwlr_data_control_offer_v1_req_receive 45 | zwlr_data_control_source_v1_evt_send 46 | zwlr_data_control_source_v1_req_offer 47 | zwlr_export_dmabuf_manager_v1_req_capture_output 48 | zwlr_export_dmabuf_frame_v1_evt_frame 49 | zwlr_export_dmabuf_frame_v1_evt_object 50 | zwlr_export_dmabuf_frame_v1_evt_ready 51 | zwlr_gamma_control_manager_v1_req_get_gamma_control 52 | zwlr_gamma_control_v1_evt_gamma_size 53 | zwlr_gamma_control_v1_req_set_gamma 54 | zwlr_screencopy_frame_v1_evt_buffer 55 | zwlr_screencopy_frame_v1_evt_flags 56 | zwlr_screencopy_frame_v1_evt_ready 57 | zwlr_screencopy_frame_v1_req_copy 58 | zwlr_screencopy_manager_v1_req_capture_output 59 | zwp_linux_buffer_params_v1_evt_created 60 | zwp_linux_buffer_params_v1_req_add 61 | zwp_linux_buffer_params_v1_req_create 62 | zwp_linux_buffer_params_v1_req_create_immed 63 | zwp_linux_buffer_params_v1_req_destroy 64 | zwp_linux_dmabuf_v1_evt_modifier 65 | zwp_linux_dmabuf_v1_req_create_params 66 | zwp_primary_selection_device_manager_v1_req_create_source 67 | zwp_primary_selection_device_manager_v1_req_get_device 68 | zwp_primary_selection_device_v1_evt_data_offer 69 | zwp_primary_selection_device_v1_evt_selection 70 | zwp_primary_selection_device_v1_req_set_selection 71 | zwp_primary_selection_offer_v1_evt_offer 72 | zwp_primary_selection_offer_v1_req_receive 73 | zwp_primary_selection_source_v1_evt_send 74 | zwp_primary_selection_source_v1_req_offer 75 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | #ifndef WAYPIPE_MAIN_H 26 | #define WAYPIPE_MAIN_H 27 | 28 | #include "parsing.h" 29 | #include "shadow.h" 30 | #include "util.h" 31 | 32 | struct main_config { 33 | const char *drm_node; 34 | int n_worker_threads; 35 | enum compression_mode compression; 36 | int compression_level; 37 | bool no_gpu; 38 | bool only_linear_dmabuf; 39 | bool video_if_possible; 40 | int video_bpf; 41 | enum video_coding_fmt video_fmt; 42 | bool prefer_hwvideo; 43 | bool old_video_mode; 44 | }; 45 | struct globals { 46 | const struct main_config *config; 47 | struct fd_translation_map map; 48 | struct render_data render; 49 | struct message_tracker tracker; 50 | struct thread_pool threads; 51 | }; 52 | 53 | /** Main processing loop 54 | * 55 | * chanfd: connected socket to channel 56 | * progfd: connected socket to Wayland program 57 | * linkfd: optional socket providing new chanfds. (-1 means not provided) 58 | * 59 | * Returns either EXIT_SUCCESS or EXIT_FAILURE (if exit caused by an error.) 60 | */ 61 | int main_interface_loop(int chanfd, int progfd, int linkfd, 62 | const struct main_config *config, bool display_side); 63 | 64 | /** Act as a Wayland server */ 65 | int run_server(int cwd_fd, struct socket_path socket_path, 66 | const char *display_suffix, const char *control_path, 67 | const struct main_config *config, bool oneshot, 68 | bool unlink_at_end, char *const app_argv[], 69 | bool login_shell_if_backup); 70 | /** Act as a Wayland client */ 71 | int run_client(int cwd_fd, const char *sock_folder_name, int sock_folder_fd, 72 | const char *sock_filename, const struct main_config *config, 73 | bool oneshot, const char *wayland_socket, pid_t eol_pid, 74 | int channelsock); 75 | /** Run benchmarking tool; n_worker_threads defined as with \ref main_config */ 76 | int run_bench(float bandwidth_mBps, uint32_t test_size, int n_worker_threads); 77 | 78 | #endif // WAYPIPE_MAIN_H 79 | -------------------------------------------------------------------------------- /test/test-proto.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copyright © 2019 Manuel Stoeckl 6 | 7 | Permission to use, copy, modify, distribute, and sell this 8 | software and its documentation for any purpose is hereby granted 9 | without fee, provided that\n the above copyright notice appear in 10 | all copies and that both that copyright notice and this permission 11 | notice appear in supporting documentation, and that the name of 12 | the copyright holders not be used in advertising or publicity 13 | pertaining to distribution of the software without specific, 14 | written prior permission. The copyright holders make no 15 | representations about the suitability of this software for any 16 | purpose. It is provided "as is" without express or implied 17 | warranty. 18 | 19 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 20 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 21 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 24 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 25 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 26 | THIS SOFTWARE. 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | 2 | waypipe_source_files = ['dmabuf.c', 'handlers.c', 'kernel.c', 'mainloop.c', 'parsing.c', 'platform.c', 'shadow.c', 'interval.c', 'util.c', 'video.c'] 3 | waypipe_deps = [ 4 | pthreads, # To run expensive computations in parallel 5 | rt, # For shared memory 6 | ] 7 | if config_data.has('HAS_DMABUF') 8 | # General GPU buffer creation, aligned with dmabuf proto 9 | waypipe_deps += [libgbm] 10 | endif 11 | if config_data.has('HAS_LZ4') 12 | waypipe_deps += [liblz4] # Fast compression option 13 | endif 14 | if config_data.has('HAS_ZSTD') 15 | waypipe_deps += [libzstd] # Slow compression option 16 | endif 17 | if config_data.has('HAS_VIDEO') 18 | waypipe_deps += [libavcodec,libavutil,libswscale] 19 | endif 20 | if config_data.has('HAS_VAAPI') 21 | waypipe_deps += [libva] # For NV12->RGB conversions 22 | endif 23 | 24 | # Conditionally compile SIMD-optimized code. 25 | # (The meson simd module is a bit too limited for this) 26 | kernel_libs = [] 27 | if cc.has_argument('-mavx512f') and cc.has_argument('-mlzcnt') and cc.has_argument('-mbmi') and get_option('with_avx512f') 28 | kernel_libs += static_library('kernel_avx512f', 'kernel_avx512f.c', c_args:['-mavx512f', '-mlzcnt', '-mbmi']) 29 | config_data.set('HAVE_AVX512F', 1, description: 'Compiler supports AVX-512F') 30 | endif 31 | if cc.has_argument('-mavx2') and cc.has_argument('-mlzcnt') and cc.has_argument('-mbmi') and get_option('with_avx2') 32 | kernel_libs += static_library('kernel_avx2', 'kernel_avx2.c', c_args:['-mavx2', '-mlzcnt', '-mbmi']) 33 | config_data.set('HAVE_AVX2', 1, description: 'Compiler supports AVX2') 34 | endif 35 | if cc.has_argument('-msse3') and get_option('with_sse3') 36 | kernel_libs += static_library('kernel_sse3', 'kernel_sse3.c', c_args:['-msse3']) 37 | config_data.set('HAVE_SSE3', 1, description: 'Compiler supports SSE 3') 38 | endif 39 | if ( host_machine.cpu_family() == 'aarch64' or cc.has_argument('-mfpu=neon') ) and get_option('with_neon_opts') 40 | neon_args = host_machine.cpu_family() == 'aarch64' ? [] : ['-mfpu=neon'] 41 | 42 | # Clang additionally enforces that NEON code only be compiled 43 | # to target a CPU that actually supports NEON instructions, 44 | # so bump the host CPU version for the optionally executed code only. 45 | if host_machine.cpu_family() == 'arm' and cc.get_id() == 'clang' 46 | host_cpu = host_machine.cpu() 47 | if host_cpu.contains('4') or host_cpu.contains('5') or host_cpu.contains('6') 48 | neon_args += ['-march=armv7-a'] 49 | endif 50 | endif 51 | 52 | kernel_libs += static_library('kernel_neon', 'kernel_neon.c', c_args:neon_args) 53 | config_data.set('HAVE_NEON', 1, description: 'Compiler supports NEON') 54 | endif 55 | 56 | configure_file( 57 | output: 'config-waypipe.h', 58 | configuration: config_data, 59 | ) 60 | 61 | lib_waypipe_src = static_library( 62 | 'waypipe_src', 63 | waypipe_source_files + protocols_src, 64 | include_directories: waypipe_includes, 65 | link_with: kernel_libs, 66 | dependencies: waypipe_deps, 67 | ) 68 | 69 | waypipe_prog = executable( 70 | 'waypipe', 71 | ['waypipe.c', 'bench.c', 'client.c', 'server.c'], 72 | link_with: lib_waypipe_src, 73 | install: true 74 | ) 75 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Only including options for C only 2 | Language: Cpp 3 | AlignAfterOpenBracket: DontAlign 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlines: Right 7 | AlignOperands: true 8 | AlignTrailingComments: true 9 | AllowAllParametersOfDeclarationOnNextLine: true 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterDefinitionReturnType: None 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | BinPackArguments: true 19 | BinPackParameters: true 20 | BraceWrapping: 21 | AfterControlStatement: false 22 | AfterEnum: false 23 | AfterFunction: false 24 | AfterStruct: false 25 | AfterUnion: false 26 | AfterExternBlock: false 27 | BeforeCatch: false 28 | BeforeElse: false 29 | IndentBraces: false 30 | SplitEmptyFunction: true 31 | SplitEmptyRecord: true 32 | SplitEmptyNamespace: true 33 | BreakBeforeBinaryOperators: None 34 | BreakBeforeBraces: Linux 35 | BreakBeforeInheritanceComma: false 36 | BreakBeforeTernaryOperators: true 37 | BreakStringLiterals: false 38 | ColumnLimit: 80 39 | CommentPragmas: '^ IWYU pragma:' 40 | CompactNamespaces: false 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 16 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | FixNamespaceComments: true 49 | ForEachMacros: 50 | - foreach 51 | - Q_FOREACH 52 | - BOOST_FOREACH 53 | IncludeBlocks: Preserve 54 | IncludeCategories: 55 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 56 | Priority: 2 57 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 58 | Priority: 3 59 | - Regex: '.*' 60 | Priority: 1 61 | IncludeIsMainRegex: '(Test)?$' 62 | IndentCaseLabels: false 63 | IndentPPDirectives: None 64 | IndentWidth: 8 65 | IndentWrappedFunctionNames: false 66 | KeepEmptyLinesAtTheStartOfBlocks: true 67 | MacroBlockBegin: '' 68 | MacroBlockEnd: '' 69 | MaxEmptyLinesToKeep: 1 70 | NamespaceIndentation: None 71 | PenaltyBreakAssignment: 2 72 | PenaltyBreakBeforeFirstCallParameter: 19 73 | PenaltyBreakComment: 300 74 | PenaltyBreakFirstLessLess: 120 75 | PenaltyBreakString: 1000 76 | PenaltyBreakTemplateDeclaration: 10 77 | PenaltyExcessCharacter: 1000000 78 | PenaltyReturnTypeOnItsOwnLine: 60 79 | PointerAlignment: Right 80 | ReflowComments: true 81 | SortIncludes: true 82 | SortUsingDeclarations: true 83 | SpaceAfterCStyleCast: false 84 | SpaceBeforeParens: ControlStatements 85 | SpaceBeforeRangeBasedForLoopColon: true 86 | SpaceInEmptyParentheses: false 87 | SpacesBeforeTrailingComments: 1 88 | SpacesInAngles: false 89 | SpacesInContainerLiterals: true 90 | SpacesInCStyleCastParentheses: false 91 | SpacesInParentheses: false 92 | SpacesInSquareBrackets: false 93 | Standard: Cpp03 94 | TabWidth: 8 95 | UseTab: ForContinuationAndIndentation 96 | 97 | -------------------------------------------------------------------------------- /src/kernel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | #ifndef WAYPIPE_KERNEL_H 26 | #define WAYPIPE_KERNEL_H 27 | 28 | #include 29 | #include 30 | 31 | struct interval; 32 | typedef size_t (*interval_diff_fn_t)(const int diff_window_size, 33 | const void *__restrict__ imod, void *__restrict__ ibase, 34 | uint32_t *__restrict__ diff, size_t i, const size_t i_end); 35 | 36 | enum diff_type { 37 | DIFF_FASTEST, 38 | DIFF_AVX512F, 39 | DIFF_AVX2, 40 | DIFF_SSE3, 41 | DIFF_NEON, 42 | DIFF_C, 43 | }; 44 | 45 | /** Returns a function pointer to a diff construction kernel, and indicates 46 | * the alignment of the data which is to be passed in */ 47 | interval_diff_fn_t get_diff_function(enum diff_type type, int *alignment_bits); 48 | /** Given intervals aligned to 1<= 4: 85 | setups = [(s, c, e) for s, c, e in setups if s == sys.argv[3]] 86 | 87 | base_env = os.environ.copy() 88 | for key, options, env in setups: 89 | print(key, end=" ") 90 | sys.stdout.flush() 91 | nenv = base_env.copy() 92 | for e in env: 93 | nenv[e] = env[e] 94 | 95 | bdir = os.path.join(build_root, key) 96 | try: 97 | shutil.rmtree(bdir) 98 | except FileNotFoundError: 99 | pass 100 | r1 = subprocess.run( 101 | ["meson", waypipe_root, bdir] + options, capture_output=True, env=nenv 102 | ) 103 | if r1.returncode: 104 | print("failed") 105 | print(r1.stdout, r1.stderr, r1.returncode) 106 | continue 107 | r2 = subprocess.run(["ninja", "test"], cwd=bdir, capture_output=True, env=nenv) 108 | if r2.returncode: 109 | print("failed") 110 | print(r2.stdout, r2.stderr, r2.returncode) 111 | continue 112 | print("passed") 113 | -------------------------------------------------------------------------------- /src/interval.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | #ifndef WAYPIPE_INTERVAL_H 26 | #define WAYPIPE_INTERVAL_H 27 | 28 | #include 29 | 30 | /** A slight modification of the standard 'damage' rectangle 31 | * formulation, written to be agnostic of whatever buffers 32 | * underlie the system. 33 | * 34 | * [start,start+width),[start+stride,start+stride+width), 35 | * ... [start+(rep-1)*stride,start+(rep-1)*stride+width) */ 36 | struct ext_interval { 37 | int32_t start; 38 | /** Subinterval width */ 39 | int32_t width; 40 | /** Number of distinct subinterval start positions. For a single 41 | * interval, this is one. */ 42 | int32_t rep; 43 | /** Spacing between start positions, should be > width, unless 44 | * the is only one subinterval, in which case the value shouldn't 45 | * matter and is conventionally set to 0. */ 46 | int32_t stride; 47 | }; 48 | /** [start, end). (This is better than {start,width}, since width computations 49 | * are rare and trivial, while merging code branches frequently off of 50 | * endpoints) */ 51 | struct interval { 52 | int32_t start; 53 | int32_t end; 54 | }; 55 | 56 | #define DAMAGE_EVERYTHING ((struct interval *)-1) 57 | 58 | /** Interval-based damage tracking. If damage is NULL, there is 59 | * no recorded damage. If damage is DAMAGE_EVERYTHING, the entire 60 | * region should be updated. If ndamage_intvs > 0, then 61 | * damage points to an array of struct interval objects. */ 62 | struct damage { 63 | struct interval *damage; 64 | int ndamage_intvs; 65 | 66 | int64_t acc_damage_stat; 67 | int acc_count; 68 | }; 69 | 70 | /** Given an array of extended intervals, update the base damage structure 71 | * so that it contains a reasonably small disjoint set of extended intervals 72 | * which contains the old base set and the new set. Before merging, all 73 | * interval boundaries will be rounded to the next multiple of 74 | * `1 << alignment_bits`. */ 75 | void merge_damage_records(struct damage *base, int nintervals, 76 | const struct ext_interval *const new_list, int alignment_bits); 77 | /** Set damage to empty */ 78 | void reset_damage(struct damage *base); 79 | /** Expand damage to cover everything */ 80 | void damage_everything(struct damage *base); 81 | 82 | /* internal merge driver, made visible for testing */ 83 | void merge_mergesort(const int old_count, struct interval *old_list, 84 | const int new_count, const struct ext_interval *const new_list, 85 | int *dst_count, struct interval **dst_list, int merge_margin, 86 | int alignment_bits); 87 | 88 | #endif // WAYPIPE_INTERVAL_H 89 | -------------------------------------------------------------------------------- /src/kernel_avx512f.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | size_t run_interval_diff_avx512f(const int diff_window_size, 33 | const void *__restrict__ imod, void *__restrict__ ibase, 34 | uint32_t *__restrict__ diff, size_t i, const size_t i_end) 35 | { 36 | const __m512i *mod = imod; 37 | __m512i *base = ibase; 38 | 39 | size_t dc = 0; 40 | while (1) { 41 | /* Loop: no changes */ 42 | uint32_t *ctrl_blocks = (uint32_t *)&diff[dc]; 43 | dc += 2; 44 | 45 | int trailing_unchanged = 0; 46 | for (; i < i_end; i++) { 47 | __m512i m = _mm512_load_si512(&mod[i]); 48 | __m512i b = _mm512_load_si512(&base[i]); 49 | uint32_t mask = (uint32_t)_mm512_cmpeq_epi32_mask(m, b); 50 | if (mask != 0xffff) { 51 | _mm512_store_si512(&base[i], m); 52 | 53 | size_t ncom = (size_t)_tzcnt_u32( 54 | ~(unsigned int)mask); 55 | __mmask16 storemask = 56 | (__mmask16)(0xffffu << ncom); 57 | #if 0 58 | __m512i v = _mm512_maskz_compress_epi32( 59 | storemask, m); 60 | _mm512_storeu_si512(&diff[dc], v); 61 | #else 62 | _mm512_mask_storeu_epi32( 63 | &diff[dc - ncom], storemask, m); 64 | #endif 65 | dc += 16 - ncom; 66 | 67 | trailing_unchanged = (int)_lzcnt_u32(~mask & 68 | 0xffff) - 69 | 16; 70 | ctrl_blocks[0] = (uint32_t)(16 * i + ncom); 71 | 72 | i++; 73 | if (i >= i_end) { 74 | /* Last block, hence will not enter copy 75 | * loop */ 76 | ctrl_blocks[1] = (uint32_t)(16 * i); 77 | dc += 2; 78 | } 79 | 80 | break; 81 | } 82 | } 83 | if (i >= i_end) { 84 | dc -= 2; 85 | break; 86 | } 87 | 88 | /* Loop: until an entire window is clear */ 89 | for (; i < i_end; i++) { 90 | __m512i m = _mm512_load_si512(&mod[i]); 91 | __m512i b = _mm512_load_si512(&base[i]); 92 | uint32_t mask = (uint32_t)_mm512_cmpeq_epi32_mask(m, b); 93 | 94 | /* Reset trailing counter if anything changed */ 95 | uint32_t amask = ~(mask << 16); 96 | int clear = (mask == 0xffff) ? 1 : 0; 97 | trailing_unchanged = clear * trailing_unchanged + 98 | (int)_lzcnt_u32(amask); 99 | 100 | _mm512_storeu_si512(&diff[dc], m); 101 | dc += 16; 102 | if (trailing_unchanged > diff_window_size) { 103 | i++; 104 | break; 105 | } 106 | _mm512_store_si512(&base[i], m); 107 | } 108 | /* Write coda */ 109 | dc -= (size_t)trailing_unchanged; 110 | ctrl_blocks[1] = 111 | (uint32_t)(16 * i - (size_t)trailing_unchanged); 112 | 113 | if (i >= i_end) { 114 | break; 115 | } 116 | } 117 | 118 | return dc; 119 | } 120 | -------------------------------------------------------------------------------- /src/kernel_neon.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | size_t run_interval_diff_neon(const int diff_window_size, 34 | const void *__restrict__ imod, void *__restrict__ ibase, 35 | uint32_t *__restrict__ diff, size_t i, const size_t i_end) 36 | { 37 | const uint64_t *__restrict__ mod = imod; 38 | uint64_t *__restrict__ base = ibase; 39 | 40 | size_t dc = 0; 41 | while (1) { 42 | uint32_t *ctrl_blocks = &diff[dc]; 43 | dc += 2; 44 | 45 | /* Loop: no changes */ 46 | size_t trailing_unchanged = 0; 47 | for (; i < i_end; i++) { 48 | /* Q: does it make sense to unroll by 2, cutting branch 49 | * count in half? */ 50 | uint64x2_t b = vld1q_u64(&base[2 * i]); 51 | uint64x2_t m = vld1q_u64(&mod[2 * i]); 52 | uint64x2_t x = veorq_u64(m, b); 53 | uint32x2_t o = vqmovn_u64(x); 54 | uint64_t n = vget_lane_u64(vreinterpret_u64_u32(o), 0); 55 | if (n) { 56 | vst1q_u64(&base[2 * i], m); 57 | 58 | bool lead_empty = vget_lane_u32(o, 0) == 0; 59 | /* vtbl only works on u64 chunks, so we branch 60 | * instead */ 61 | if (lead_empty) { 62 | vst1_u64((uint64_t *)&diff[dc], 63 | vget_high_u64(m)); 64 | trailing_unchanged = 0; 65 | ctrl_blocks[0] = (uint32_t)(4 * i + 2); 66 | dc += 2; 67 | } else { 68 | vst1q_u64((uint64_t *)&diff[dc], m); 69 | trailing_unchanged = 70 | 2 * 71 | (vget_lane_u32(o, 1) == 72 | 0); 73 | ctrl_blocks[0] = (uint32_t)(4 * i); 74 | dc += 4; 75 | } 76 | trailing_unchanged = 0; 77 | 78 | i++; 79 | if (i >= i_end) { 80 | /* Last block, hence will not enter copy 81 | * loop */ 82 | ctrl_blocks[1] = (uint32_t)(4 * i); 83 | dc += 2; 84 | } 85 | 86 | break; 87 | } 88 | } 89 | if (i >= i_end) { 90 | dc -= 2; 91 | break; 92 | } 93 | 94 | /* Main copy loop */ 95 | for (; i < i_end; i++) { 96 | uint64x2_t m = vld1q_u64(&mod[2 * i]); 97 | uint64x2_t b = vld1q_u64(&base[2 * i]); 98 | uint64x2_t x = veorq_u64(m, b); 99 | 100 | uint32x2_t o = vqmovn_u64(x); 101 | uint64_t n = vget_lane_u64(vreinterpret_u64_u32(o), 0); 102 | 103 | /* Reset trailing counter if anything changed */ 104 | trailing_unchanged = trailing_unchanged * (n == 0); 105 | size_t nt = (size_t)((vget_lane_u32(o, 1) == 0) * 106 | (1 + (vget_lane_u32(o, 0) == 0))); 107 | trailing_unchanged += 2 * nt; 108 | 109 | vst1q_u64((uint64_t *)&diff[dc], m); 110 | dc += 4; 111 | if (trailing_unchanged > (size_t)diff_window_size) { 112 | i++; 113 | break; 114 | } 115 | vst1q_u64(&base[2 * i], m); 116 | } 117 | /* Write coda */ 118 | dc -= trailing_unchanged; 119 | ctrl_blocks[1] = (uint32_t)(4 * i - trailing_unchanged); 120 | if (i >= i_end) { 121 | break; 122 | } 123 | } 124 | 125 | return dc; 126 | } 127 | -------------------------------------------------------------------------------- /src/dmabuf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | #ifndef WAYPIPE_DMABUF_H 26 | #define WAYPIPE_DMABUF_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | typedef void *VADisplay; 34 | typedef unsigned int VAGenericID; 35 | typedef VAGenericID VAConfigID; 36 | struct render_data { 37 | bool disabled; 38 | int drm_fd; 39 | const char *drm_node_path; 40 | struct gbm_device *dev; 41 | bool supports_modifiers; 42 | /* video hardware context */ 43 | bool av_disabled; 44 | int av_bpf; 45 | int av_video_fmt; 46 | struct AVBufferRef *av_hwdevice_ref; 47 | struct AVBufferRef *av_drmdevice_ref; 48 | VADisplay av_vadisplay; 49 | VAConfigID av_copy_config; 50 | }; 51 | 52 | /** Additional information to help serialize a dmabuf */ 53 | struct dmabuf_slice_data { 54 | /* This information partially duplicates that of a gbm_bo. However, for 55 | * instance with weston, it is possible for the compositor to handle 56 | * multibuffer multiplanar images, even though a driver may only support 57 | * multiplanar images derived from a single underlying dmabuf. */ 58 | uint32_t width; 59 | uint32_t height; 60 | uint32_t format; 61 | int32_t num_planes; 62 | uint32_t offsets[4]; 63 | uint32_t strides[4]; 64 | uint64_t modifier; 65 | // to which planes is the matching dmabuf assigned? 66 | uint8_t using_planes[4]; 67 | char pad[4]; 68 | }; 69 | static_assert(sizeof(struct dmabuf_slice_data) == 64, "size check"); 70 | 71 | int init_render_data(struct render_data *); 72 | void cleanup_render_data(struct render_data *); 73 | struct gbm_bo *make_dmabuf( 74 | struct render_data *rd, const struct dmabuf_slice_data *info); 75 | int export_dmabuf(struct gbm_bo *bo); 76 | /** Import DMABUF to a GBM buffer object. */ 77 | struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size, 78 | const struct dmabuf_slice_data *info); 79 | void destroy_dmabuf(struct gbm_bo *bo); 80 | /** Map a DMABUF for reading or for writing */ 81 | void *map_dmabuf(struct gbm_bo *bo, bool write, void **map_handle, 82 | uint32_t *exp_stride); 83 | int unmap_dmabuf(struct gbm_bo *bo, void *map_handle); 84 | /** The handle values are unique among the set of currently active buffer 85 | * objects. To compare a set of buffer objects, produce handles in a batch, and 86 | * then free the temporary buffer objects in a batch */ 87 | int get_unique_dmabuf_handle( 88 | struct render_data *rd, int fd, struct gbm_bo **temporary_bo); 89 | uint32_t dmabuf_get_simple_format_for_plane(uint32_t format, int plane); 90 | uint32_t dmabuf_get_stride(struct gbm_bo *bo); 91 | 92 | /** Returns the number of bytes per pixel for WL or DRM format 'format', if the 93 | * format is an RGBA-type single plane format. For YUV-type or planar formats, 94 | * returns -1. */ 95 | int get_shm_bytes_per_pixel(uint32_t format); 96 | 97 | #ifndef DRM_FORMAT_MOD_INVALID 98 | #define DRM_FORMAT_MOD_INVALID 0x00ffffffffffffffULL 99 | #endif 100 | 101 | #endif // WAYPIPE_DMABUF_H 102 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'waypipe', 3 | 'c', 4 | license: 'MIT/Expat', 5 | meson_version: '>=0.47.0', 6 | default_options: [ 7 | 'c_std=c11', 8 | 'warning_level=3', 9 | 'werror=true', 10 | ], 11 | version: '0.8.4', 12 | ) 13 | 14 | # DEFAULT_SOURCE implies POSIX_C_SOURCE 200809L + extras like CMSG_LEN 15 | # requires glibc >= 4.19 (2014), freebsd libc (since 2016?), musl >= 1.15 (2014) 16 | add_project_arguments('-D_DEFAULT_SOURCE', language: 'c') 17 | 18 | # Sometimes ignoring the result of read()/write() is the right thing to do 19 | add_project_arguments('-Wno-unused-result', language: 'c') 20 | 21 | cc = meson.get_compiler('c') 22 | config_data = configuration_data() 23 | 24 | # mention version 25 | version = '"@0@"'.format(meson.project_version()) 26 | git = find_program('git', native: true, required: false) 27 | if git.found() 28 | dir_arg = '--git-dir=@0@/.git'.format(meson.source_root()) 29 | commit = run_command([git, dir_arg, 'rev-parse', '--verify', '-q', 'HEAD']) 30 | if commit.returncode() == 0 31 | version = '"@0@ (commit @1@)"'.format(meson.project_version(), commit.stdout().strip()) 32 | endif 33 | endif 34 | config_data.set('WAYPIPE_VERSION', version) 35 | 36 | # Make build reproducible if possible 37 | python3 = import('python').find_installation() 38 | prefix_finder = 'import os.path; print(os.path.join(os.path.relpath(\'@0@\', \'@1@\'),\'\'))' 39 | r = run_command(python3, '-c', prefix_finder.format(meson.source_root(), meson.build_root())) 40 | relative_dir = r.stdout().strip() 41 | if cc.has_argument('-fmacro-prefix-map=/prefix/to/hide=') 42 | add_project_arguments( 43 | '-fmacro-prefix-map=@0@='.format(relative_dir), 44 | language: 'c', 45 | ) 46 | else 47 | add_project_arguments( 48 | '-DWAYPIPE_REL_SRC_DIR="@0@"'.format(relative_dir), 49 | language: 'c', 50 | ) 51 | endif 52 | 53 | libgbm = dependency('gbm', required: get_option('with_dmabuf')) 54 | libdrm = dependency('libdrm', required: get_option('with_dmabuf')) 55 | if libgbm.found() and libdrm.found() 56 | config_data.set('HAS_DMABUF', 1, description: 'Support DMABUF replication') 57 | has_dmabuf = true 58 | else 59 | has_dmabuf = false 60 | endif 61 | pthreads = dependency('threads') 62 | rt = cc.find_library('rt') 63 | # XXX dtrace -G (Solaris, FreeBSD, NetBSD) isn't supported yet 64 | is_linux = host_machine.system() == 'linux' 65 | is_darwin = host_machine.system() == 'darwin' 66 | if (is_linux or is_darwin) and get_option('with_systemtap') and cc.has_header('sys/sdt.h') 67 | config_data.set('HAS_USDT', 1, description: 'Enable static trace probes') 68 | endif 69 | liblz4 = dependency('liblz4', version: '>=1.7.0', required: get_option('with_lz4')) 70 | if liblz4.found() 71 | config_data.set('HAS_LZ4', 1, description: 'Enable LZ4 compression') 72 | endif 73 | libzstd = dependency('libzstd', version: '>=0.4.6', required: get_option('with_zstd')) 74 | if libzstd.found() 75 | config_data.set('HAS_ZSTD', 1, description: 'Enable Zstd compression') 76 | endif 77 | libavcodec = dependency('libavcodec', required: get_option('with_video')) 78 | libavutil = dependency('libavutil', required: get_option('with_video')) 79 | libswscale = dependency('libswscale', required: get_option('with_video')) 80 | libva = dependency('libva', required: get_option('with_vaapi')) 81 | if libavcodec.found() and libavutil.found() and libswscale.found() 82 | config_data.set('HAS_VIDEO', 1, description: 'Enable video (de)compression') 83 | if libva.found() 84 | config_data.set('HAS_VAAPI', 1, description: 'Enable hardware video (de)compression with VAAPI') 85 | endif 86 | endif 87 | 88 | waypipe_includes = [include_directories('protocols'), include_directories('src')] 89 | if libdrm.found() 90 | waypipe_includes += include_directories(libdrm.get_pkgconfig_variable('includedir')) 91 | endif 92 | 93 | subdir('protocols') 94 | subdir('src') 95 | subdir('test') 96 | 97 | scdoc = dependency('scdoc', version: '>=1.9.4', native: true, required: get_option('man-pages')) 98 | if scdoc.found() 99 | scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) 100 | sh = find_program('sh', native: true) 101 | mandir = get_option('mandir') 102 | custom_target( 103 | 'waypipe.1', 104 | input: 'waypipe.scd', 105 | output: 'waypipe.1', 106 | command: [ 107 | sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), 'waypipe.1') 108 | ], 109 | install: true, 110 | install_dir: '@0@/man1'.format(mandir) 111 | ) 112 | endif 113 | -------------------------------------------------------------------------------- /src/kernel_sse3.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include // sse 31 | #include // sse2 32 | #include // sse3 33 | 34 | size_t run_interval_diff_sse3(const int diff_window_size, 35 | const void *__restrict__ imod, void *__restrict__ ibase, 36 | uint32_t *__restrict__ diff, size_t i, const size_t i_end) 37 | { 38 | const __m128i *__restrict__ mod = imod; 39 | __m128i *__restrict__ base = ibase; 40 | 41 | size_t dc = 0; 42 | while (1) { 43 | /* Loop: no changes */ 44 | uint32_t *ctrl_blocks = (uint32_t *)&diff[dc]; 45 | dc += 2; 46 | 47 | int trailing_unchanged = 0; 48 | for (; i < i_end; i++) { 49 | __m128i b0 = _mm_load_si128(&base[2 * i]); 50 | __m128i b1 = _mm_load_si128(&base[2 * i + 1]); 51 | __m128i m0 = _mm_load_si128(&mod[2 * i]); 52 | __m128i m1 = _mm_load_si128(&mod[2 * i + 1]); 53 | 54 | /* pxor + ptest + branch could be faster, depending on 55 | * compiler choices */ 56 | __m128i eq0 = _mm_cmpeq_epi32(m0, b0); 57 | __m128i eq1 = _mm_cmpeq_epi32(m1, b1); 58 | uint32_t mask = (uint32_t)_mm_movemask_epi8(eq0); 59 | mask |= ((uint32_t)_mm_movemask_epi8(eq1)) << 16; 60 | if (mask != 0xffffffff) { 61 | _mm_storeu_si128(&base[2 * i], m0); 62 | _mm_storeu_si128(&base[2 * i + 1], m1); 63 | 64 | /* Write the changed bytes, starting at the 65 | * first modified term, and set the unchanged 66 | * counter. */ 67 | size_t ncom = (size_t)__builtin_ctz(~mask) >> 2; 68 | union { 69 | __m128i s[2]; 70 | uint32_t v[8]; 71 | } tmp; 72 | tmp.s[0] = m0; 73 | tmp.s[1] = m1; 74 | for (size_t z = ncom; z < 8; z++) { 75 | diff[dc++] = tmp.v[z]; 76 | } 77 | trailing_unchanged = __builtin_clz(~mask) >> 2; 78 | ctrl_blocks[0] = (uint32_t)(8 * i + ncom); 79 | 80 | i++; 81 | if (i >= i_end) { 82 | /* Last block, hence will not enter copy 83 | * loop */ 84 | ctrl_blocks[1] = (uint32_t)(8 * i); 85 | dc += 2; 86 | } 87 | 88 | break; 89 | } 90 | } 91 | if (i >= i_end) { 92 | dc -= 2; 93 | break; 94 | } 95 | 96 | /* Loop: until no changes for DIFF_WINDOW +/- 4 spaces */ 97 | for (; i < i_end; i++) { 98 | __m128i b0 = _mm_load_si128(&base[2 * i]); 99 | __m128i b1 = _mm_load_si128(&base[2 * i + 1]); 100 | __m128i m0 = _mm_load_si128(&mod[2 * i]); 101 | __m128i m1 = _mm_load_si128(&mod[2 * i + 1]); 102 | 103 | __m128i eq0 = _mm_cmpeq_epi32(m0, b0); 104 | __m128i eq1 = _mm_cmpeq_epi32(m1, b1); 105 | uint32_t mask = (uint32_t)_mm_movemask_epi8(eq0); 106 | mask |= ((uint32_t)_mm_movemask_epi8(eq1)) << 16; 107 | 108 | bool clear = mask == 0xffffffff; 109 | /* Because clz is undefined when mask=0, extend */ 110 | uint64_t ext_mask = ((uint64_t)mask) << 32; 111 | int nleading = __builtin_clzll(~ext_mask); 112 | 113 | trailing_unchanged = clear * (trailing_unchanged + 8) + 114 | (!clear) * (nleading >> 2); 115 | 116 | _mm_storeu_si128((__m128i *)&diff[dc], m0); 117 | _mm_storeu_si128((__m128i *)&diff[dc + 4], m1); 118 | dc += 8; 119 | if (trailing_unchanged > diff_window_size) { 120 | i++; 121 | break; 122 | } 123 | _mm_storeu_si128(&base[2 * i], m0); 124 | _mm_storeu_si128(&base[2 * i + 1], m1); 125 | } 126 | /* Write coda */ 127 | dc -= (size_t)trailing_unchanged; 128 | ctrl_blocks[1] = (uint32_t)(8 * i - (size_t)trailing_unchanged); 129 | 130 | if (i >= i_end) { 131 | break; 132 | } 133 | } 134 | return dc; 135 | } 136 | -------------------------------------------------------------------------------- /test/fuzz_hook_det.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include "common.h" 27 | #include "main.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | log_handler_func_t log_funcs[2] = {NULL, NULL}; 44 | int main(int argc, char **argv) 45 | { 46 | if (argc == 1 || !strcmp(argv[1], "--help")) { 47 | printf("Usage: ./fuzz_hook_det [--server] [--log] {input_file}\n"); 48 | printf("A program to run and control Wayland and channel inputs for core Waypipe operations\n"); 49 | return EXIT_FAILURE; 50 | } 51 | bool display_side = true; 52 | if (argc > 1 && !strcmp(argv[1], "--server")) { 53 | display_side = false; 54 | argc--; 55 | argv++; 56 | } 57 | if (argc > 1 && !strcmp(argv[1], "--log")) { 58 | log_funcs[0] = test_atomic_log_handler; 59 | log_funcs[1] = test_atomic_log_handler; 60 | argc--; 61 | argv++; 62 | } 63 | 64 | size_t len; 65 | char *buf = read_file_into_mem(argv[1], &len); 66 | if (!buf) { 67 | return EXIT_FAILURE; 68 | } 69 | printf("Loaded %zu bytes\n", len); 70 | 71 | struct test_state ts; 72 | if (setup_state(&ts, display_side, true) == -1) { 73 | return -1; 74 | } 75 | 76 | char *ignore_buf = malloc(65536); 77 | 78 | /* Main loop: RW from socketpairs with sendmsg, with short wait */ 79 | int64_t file_nwords = (int64_t)len / 4; 80 | int64_t cursor = 0; 81 | uint32_t *data = (uint32_t *)buf; 82 | while (cursor < file_nwords) { 83 | uint32_t header = data[cursor++]; 84 | bool wayland_side = header & 0x1; 85 | bool add_file = header & 0x2; 86 | int new_fileno = -1; 87 | 88 | if (add_file && wayland_side && cursor < file_nwords) { 89 | uint32_t fsize = data[cursor++]; 90 | if (fsize == 0) { 91 | /* 'copy' sink */ 92 | new_fileno = open("/dev/null", 93 | O_WRONLY | O_NOCTTY); 94 | if (new_fileno == -1) { 95 | wp_error("Failed to open /dev/null"); 96 | } 97 | } else { 98 | /* avoid buffer overflow */ 99 | fsize = fsize > 1000000 ? 1000000 : fsize; 100 | new_fileno = create_anon_file(); 101 | if (ftruncate(new_fileno, (off_t)fsize) == -1) { 102 | wp_error("Failed to resize tempfile"); 103 | checked_close(new_fileno); 104 | break; 105 | } 106 | } 107 | } 108 | 109 | uint32_t packet_size = header >> 2; 110 | int64_t words_left = file_nwords - cursor; 111 | if (packet_size > 2048) { 112 | packet_size = 2048; 113 | } 114 | if (packet_size > (uint32_t)words_left) { 115 | packet_size = (uint32_t)words_left; 116 | } 117 | 118 | struct transfer_queue transfers; 119 | memset(&transfers, 0, sizeof(transfers)); 120 | pthread_mutex_init(&transfers.async_recv_queue.lock, NULL); 121 | 122 | if (wayland_side) { 123 | /* Send a message (incl fds) */ 124 | struct msg m; 125 | m.data = &data[cursor]; 126 | m.len = (int)packet_size; 127 | if (new_fileno != -1) { 128 | m.fds = &new_fileno; 129 | m.nfds = 1; 130 | } else { 131 | m.fds = NULL; 132 | m.nfds = 0; 133 | } 134 | send_wayland_msg(&ts, m, &transfers); 135 | /* ignore any created transfers, since this is only 136 | * a test of one side */ 137 | } else { 138 | /* Send a transfer */ 139 | void *msg_copy = calloc(packet_size, 4); 140 | memcpy(msg_copy, &data[cursor], packet_size * 4); 141 | transfer_add(&transfers, packet_size * 4, msg_copy); 142 | receive_wire(&ts, &transfers); 143 | } 144 | cleanup_transfer_queue(&transfers); 145 | 146 | cursor += packet_size; 147 | } 148 | 149 | cleanup_state(&ts); 150 | 151 | free(buf); 152 | free(ignore_buf); 153 | return EXIT_SUCCESS; 154 | } 155 | -------------------------------------------------------------------------------- /protocols/virtual-keyboard-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2008-2011 Kristian Høgsberg 5 | Copyright © 2010-2013 Intel Corporation 6 | Copyright © 2012-2013 Collabora, Ltd. 7 | Copyright © 2018 Purism SPC 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a 10 | copy of this software and associated documentation files (the "Software"), 11 | to deal in the Software without restriction, including without limitation 12 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | and/or sell copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice (including the next 17 | paragraph) shall be included in all copies or substantial portions of the 18 | Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 23 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | 28 | 29 | 30 | 31 | The virtual keyboard provides an application with requests which emulate 32 | the behaviour of a physical keyboard. 33 | 34 | This interface can be used by clients on its own to provide raw input 35 | events, or it can accompany the input method protocol. 36 | 37 | 38 | 39 | 40 | Provide a file descriptor to the compositor which can be 41 | memory-mapped to provide a keyboard mapping description. 42 | 43 | Format carries a value from the keymap_format enumeration. 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | A key was pressed or released. 57 | The time argument is a timestamp with millisecond granularity, with an 58 | undefined base. All requests regarding a single object must share the 59 | same clock. 60 | 61 | Keymap must be set before issuing this request. 62 | 63 | State carries a value from the key_state enumeration. 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Notifies the compositor that the modifier and/or group state has 73 | changed, and it should update state. 74 | 75 | The client should use wl_keyboard.modifiers event to synchronize its 76 | internal state with seat state. 77 | 78 | Keymap must be set before issuing this request. 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | A virtual keyboard manager allows an application to provide keyboard 94 | input events as if they came from a physical keyboard. 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Creates a new virtual keyboard associated to a seat. 104 | 105 | If the compositor enables a keyboard to perform arbitrary actions, it 106 | should present an error when an untrusted client requests a new 107 | keyboard. 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/platform.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #ifndef _GNU_SOURCE 27 | #define _GNU_SOURCE 28 | #endif 29 | 30 | #include "config-waypipe.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #if defined(__linux__) && defined(__arm__) 44 | #include 45 | #include 46 | #elif defined(__FreeBSD__) && defined(__arm__) 47 | #include 48 | #endif 49 | 50 | #if defined(__linux__) 51 | /* memfd_create was introduced in glibc 2.27 */ 52 | #if !defined(__GLIBC__) || (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 27) 53 | #define HAS_MEMFD 1 54 | #endif 55 | #endif 56 | 57 | #if defined(__linux__) 58 | #define HAS_O_PATH 1 59 | #endif 60 | 61 | int create_anon_file(void) 62 | { 63 | int new_fileno; 64 | #ifdef HAS_MEMFD 65 | new_fileno = memfd_create("waypipe", 0); 66 | #elif defined(SHM_ANON) 67 | new_fileno = shm_open(SHM_ANON, O_RDWR, 0600); 68 | #else 69 | // Fallback code. Should not be used from multiple threads 70 | static int counter = 0; 71 | int pid = getpid(); 72 | counter++; 73 | char tmp_name[64]; 74 | sprintf(tmp_name, "/waypipe%d-data_%d", pid, counter); 75 | new_fileno = shm_open(tmp_name, O_EXCL | O_RDWR | O_CREAT, 0644); 76 | if (new_fileno == -1) { 77 | return -1; 78 | } 79 | (void)shm_unlink(tmp_name); 80 | #endif 81 | return new_fileno; 82 | } 83 | 84 | int get_hardware_thread_count(void) 85 | { 86 | return (int)sysconf(_SC_NPROCESSORS_ONLN); 87 | } 88 | 89 | int get_iov_max(void) { return (int)sysconf(_SC_IOV_MAX); } 90 | 91 | #ifdef HAVE_NEON 92 | bool neon_available(void) 93 | { 94 | /* The actual methods are platform-dependent */ 95 | #if defined(__linux__) && defined(__arm__) 96 | return (getauxval(AT_HWCAP) & HWCAP_NEON) != 0; 97 | #elif defined(__FreeBSD__) && defined(__arm__) 98 | unsigned long hwcap = 0; 99 | elf_aux_info(AT_HWCAP, &hwcap, sizeof(hwcap)); 100 | return (hwcap & HWCAP_NEON) != 0; 101 | #endif 102 | return true; 103 | } 104 | #endif 105 | 106 | static void *align_ptr(void *ptr, size_t alignment) 107 | { 108 | return (uint8_t *)ptr + ((alignment - (uintptr_t)ptr) % alignment); 109 | } 110 | void *zeroed_aligned_alloc(size_t bytes, size_t alignment, void **handle) 111 | { 112 | if (*handle) { 113 | /* require a clean handle */ 114 | return NULL; 115 | } 116 | *handle = calloc(bytes + alignment - 1, 1); 117 | return align_ptr(*handle, alignment); 118 | } 119 | void *zeroed_aligned_realloc(size_t old_size_bytes, size_t new_size_bytes, 120 | size_t alignment, void *data, void **handle) 121 | { 122 | /* warning: this might copy a lot of data */ 123 | if (new_size_bytes <= 2 * old_size_bytes) { 124 | void *old_handle = *handle; 125 | ptrdiff_t old_offset = (uint8_t *)data - (uint8_t *)old_handle; 126 | 127 | void *new_handle = realloc( 128 | old_handle, new_size_bytes + alignment - 1); 129 | if (!new_handle) { 130 | return NULL; 131 | } 132 | void *new_data = align_ptr(new_handle, alignment); 133 | ptrdiff_t new_offset = 134 | (uint8_t *)new_data - (uint8_t *)new_handle; 135 | if (old_offset != new_offset) { 136 | /* realloc broke alignment offset */ 137 | memmove((uint8_t *)new_data + new_offset, 138 | (uint8_t *)new_data + old_offset, 139 | new_size_bytes > old_size_bytes 140 | ? old_size_bytes 141 | : new_size_bytes); 142 | } 143 | if (new_size_bytes > old_size_bytes) { 144 | memset((uint8_t *)new_data + old_size_bytes, 0, 145 | new_size_bytes - old_size_bytes); 146 | } 147 | *handle = new_handle; 148 | return new_data; 149 | } else { 150 | void *new_handle = calloc(new_size_bytes + alignment - 1, 1); 151 | if (!new_handle) { 152 | return NULL; 153 | } 154 | void *new_data = align_ptr(new_handle, alignment); 155 | memcpy(new_data, data, 156 | new_size_bytes > old_size_bytes 157 | ? old_size_bytes 158 | : new_size_bytes); 159 | free(*handle); 160 | *handle = new_handle; 161 | return new_data; 162 | } 163 | } 164 | void zeroed_aligned_free(void *data, void **handle) 165 | { 166 | (void)data; 167 | free(*handle); 168 | *handle = NULL; 169 | } 170 | 171 | int open_folder(const char *name) 172 | { 173 | const char *path = name[0] ? name : "."; 174 | #ifdef HAS_O_PATH 175 | return open(path, O_PATH); 176 | #else 177 | return open(path, O_RDONLY | O_DIRECTORY); 178 | #endif 179 | } 180 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | 2 | common_src = static_library( 3 | 'common', 4 | 'common.c', 5 | include_directories: waypipe_includes 6 | ) 7 | # Testing 8 | test_diff = executable( 9 | 'diff_roundtrip', 10 | ['diff_roundtrip.c'], 11 | include_directories: waypipe_includes, 12 | link_with: [lib_waypipe_src, common_src] 13 | ) 14 | test('Whether diff operations successfully roundtrip', test_diff, timeout: 60) 15 | test_damage = executable( 16 | 'damage_merge', 17 | ['damage_merge.c'], 18 | include_directories: waypipe_includes, 19 | link_with: [lib_waypipe_src, common_src] 20 | ) 21 | test('If damage rectangles merge efficiently', test_damage, timeout: 5) 22 | test_mirror = executable( 23 | 'fd_mirror', 24 | ['fd_mirror.c'], 25 | include_directories: waypipe_includes, 26 | link_with: [lib_waypipe_src, common_src], 27 | dependencies: [libgbm] 28 | ) 29 | # disable leak checking, because library code is often responsible 30 | test('How well buffers are replicated', test_mirror, env: ['ASAN_OPTIONS=detect_leaks=0'], timeout: 40) 31 | test_proto_functions = files('protocol_functions.txt') 32 | proto_send_src = custom_target( 33 | 'protocol_control message serialization', 34 | output: 'protocol_functions.h', 35 | depend_files: [test_proto_functions, sendgen_path] + abs_protocols, 36 | command: [python3, sendgen_path, test_proto_functions, '@OUTPUT@'] + abs_protocols, 37 | ) 38 | test_protocol = executable( 39 | 'protocol_control', 40 | ['protocol_control.c', proto_send_src], 41 | include_directories: waypipe_includes, 42 | link_with: [lib_waypipe_src, common_src] 43 | ) 44 | test('That common Wayland message patterns work', test_protocol, env: ['ASAN_OPTIONS=detect_leaks=0'], timeout: 20) 45 | test_pipe = executable( 46 | 'pipe_mirror', 47 | ['pipe_mirror.c'], 48 | include_directories: waypipe_includes, 49 | link_with: [lib_waypipe_src, common_src] 50 | ) 51 | test('How well pipes are replicated', test_pipe, timeout: 20) 52 | test_fnlist = files('test_fnlist.txt') 53 | testproto_src = custom_target( 54 | 'test-proto code', 55 | output: 'protocol-@BASENAME@.c', 56 | input: 'test-proto.xml', 57 | depend_files: [test_fnlist, symgen_path], 58 | command: [python3, symgen_path, 'data', test_fnlist, '@OUTPUT@', '@INPUT@'], 59 | ) 60 | testproto_header = custom_target( 61 | 'test-proto client-header', 62 | output: 'protocol-@BASENAME@.h', 63 | input: 'test-proto.xml', 64 | depend_files: [test_fnlist, symgen_path], 65 | command: [python3, symgen_path, 'header', test_fnlist, '@OUTPUT@', '@INPUT@'], 66 | ) 67 | test_parse = executable( 68 | 'wire_parse', 69 | ['wire_parse.c', testproto_src, testproto_header], 70 | include_directories: waypipe_includes, 71 | link_with: [lib_waypipe_src, common_src], 72 | ) 73 | test('That protocol parsing fails cleanly', test_parse, timeout: 5) 74 | 75 | fake_ssh = executable( 76 | 'ssh', 77 | ['fake_ssh.c'] 78 | ) 79 | 80 | weston_dep = dependency('weston', required: false) 81 | testprog_paths = [] 82 | if weston_dep.found() 83 | # Sometimes weston's test clients are installed here instead 84 | testprog_paths += weston_dep.get_pkgconfig_variable('libexecdir') 85 | endif 86 | weston_prog = find_program('weston', required: false) 87 | base_envlist = [ 88 | 'TEST_WAYPIPE_PATH=@0@'.format(waypipe_prog.full_path()), 89 | ] 90 | 91 | headless_envlist = base_envlist 92 | if weston_prog.found() 93 | headless_envlist += 'TEST_WESTON_PATH=@0@'.format(weston_prog.path()) 94 | endif 95 | test_programs = [ 96 | ['TEST_WESTON_SHM_PATH', 'weston-simple-shm'], 97 | # ['TEST_WESTON_EGL_PATH', 'weston-simple-egl'], 98 | ['TEST_WESTON_TERM_PATH', 'weston-terminal'], 99 | ['TEST_WESTON_PRES_PATH', 'weston-presentation-shm'], 100 | ['TEST_WESTON_SUBSURF_PATH', 'weston-subsurfaces'], 101 | ] 102 | if has_dmabuf 103 | test_programs += [['TEST_WESTON_DMA_PATH', 'weston-simple-dmabuf-egl']] 104 | endif 105 | have_test_progs = false 106 | foreach t : test_programs 107 | test_prog = find_program(t[1], required: false) 108 | foreach p : testprog_paths 109 | if not test_prog.found() 110 | test_prog = find_program(join_paths(p, t[1]), required: false) 111 | endif 112 | endforeach 113 | if test_prog.found() 114 | have_test_progs = true 115 | headless_envlist += '@0@=@1@'.format(t[0], test_prog.path()) 116 | endif 117 | endforeach 118 | 119 | if weston_prog.found() and have_test_progs 120 | test_headless = join_paths(meson.current_source_dir(), 'headless.py') 121 | test('If clients crash when run with weston via waypipe', python3, args: test_headless, env: headless_envlist, timeout: 30) 122 | endif 123 | 124 | sleep_prog = find_program('sleep') 125 | startup_envlist = base_envlist 126 | startup_envlist += ['TEST_SLEEP_PATH=' + sleep_prog.path()] 127 | startup_envlist += ['TEST_FAKE_SSH_PATH=' + fake_ssh.full_path()] 128 | test_startup = join_paths(meson.current_source_dir(), 'startup_failure.py') 129 | test('That waypipe exits cleanly given a bad setup', 130 | python3, args: test_startup, env: startup_envlist, timeout: 30 131 | ) 132 | 133 | fuzz_hook_ext = executable( 134 | 'fuzz_hook_ext', 135 | ['fuzz_hook_ext.c'], 136 | include_directories: waypipe_includes, 137 | link_with: [lib_waypipe_src, common_src], 138 | dependencies: [pthreads] 139 | ) 140 | fuzz_hook_int = executable( 141 | 'fuzz_hook_int', 142 | ['fuzz_hook_int.c'], 143 | include_directories: waypipe_includes, 144 | link_with: [lib_waypipe_src, common_src], 145 | dependencies: [pthreads] 146 | ) 147 | fuzz_hook_det = executable( 148 | 'fuzz_hook_det', 149 | ['fuzz_hook_det.c'], 150 | include_directories: waypipe_includes, 151 | link_with: [lib_waypipe_src, common_src] 152 | ) 153 | test('That `waypipe bench` doesn\'t crash', 154 | waypipe_prog, timeout: 20, 155 | args: ['--threads', '2', '--test-size', '16384', 'bench', '100.0'] 156 | ) 157 | -------------------------------------------------------------------------------- /src/kernel_avx2.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #ifdef __x86_64__ 33 | static inline int tzcnt(uint64_t v) { return (int)_tzcnt_u64(v); } 34 | #else 35 | static inline int tzcnt(uint64_t v) { return v ? __builtin_ctzll(v) : 64; } 36 | #endif 37 | #ifdef __x86_64__ 38 | static inline int lzcnt(uint64_t v) { return (int)_lzcnt_u64(v); } 39 | #else 40 | static inline int lzcnt(uint64_t v) { return v ? __builtin_clzll(v) : 64; } 41 | #endif 42 | 43 | size_t run_interval_diff_avx2(const int diff_window_size, 44 | const void *__restrict__ imod, void *__restrict__ ibase, 45 | uint32_t *__restrict__ diff, size_t i, const size_t i_end) 46 | { 47 | const __m256i *__restrict__ mod = imod; 48 | __m256i *__restrict__ base = ibase; 49 | 50 | size_t dc = 0; 51 | while (1) { 52 | /* Loop: no changes */ 53 | uint32_t *ctrl_blocks = &diff[dc]; 54 | dc += 2; 55 | 56 | int trailing_unchanged = 0; 57 | for (; i < i_end; i++) { 58 | __m256i m0 = _mm256_load_si256(&mod[2 * i]); 59 | __m256i m1 = _mm256_load_si256(&mod[2 * i + 1]); 60 | __m256i b0 = _mm256_load_si256(&base[2 * i]); 61 | __m256i b1 = _mm256_load_si256(&base[2 * i + 1]); 62 | __m256i eq0 = _mm256_cmpeq_epi32(m0, b0); 63 | __m256i eq1 = _mm256_cmpeq_epi32(m1, b1); 64 | 65 | /* It's very hard to tell which loop exit method is 66 | * better, since the routine is typically bandwidth 67 | * limited */ 68 | #if 1 69 | uint32_t mask0 = (uint32_t)_mm256_movemask_epi8(eq0); 70 | uint32_t mask1 = (uint32_t)_mm256_movemask_epi8(eq1); 71 | uint64_t mask = mask0 + mask1 * 0x100000000uLL; 72 | if (~mask) { 73 | #else 74 | __m256i andv = _mm256_and_si256(eq0, eq1); 75 | if (_mm256_testz_si256(andv, _mm256_set1_epi8(-1))) { 76 | uint32_t mask0 = (uint32_t)_mm256_movemask_epi8( 77 | eq0); 78 | uint32_t mask1 = (uint32_t)_mm256_movemask_epi8( 79 | eq1); 80 | uint64_t mask = mask0 + mask1 * 0x100000000uLL; 81 | #endif 82 | _mm256_store_si256(&base[2 * i], m0); 83 | _mm256_store_si256(&base[2 * i + 1], m1); 84 | 85 | /* Write the changed bytes, starting at the 86 | * first modified term, 87 | * and set the n_unchanged counter */ 88 | size_t ncom = (size_t)tzcnt(~mask) >> 2; 89 | 90 | size_t block_shift = (ncom & 7); 91 | uint64_t esmask = 0xffffffffuLL 92 | << (block_shift * 4); 93 | __m128i halfsize = _mm_set_epi64x( 94 | 0uLL, (long long)esmask); 95 | __m256i estoremask = 96 | _mm256_cvtepi8_epi64(halfsize); 97 | _mm256_maskstore_epi32( 98 | (int *)&diff[dc - block_shift], 99 | estoremask, ncom < 8 ? m0 : m1); 100 | if (ncom < 8) { 101 | _mm256_storeu_si256( 102 | (__m256i *)&diff[dc + 103 | 8 - 104 | block_shift], 105 | m1); 106 | } 107 | dc += 16 - ncom; 108 | 109 | trailing_unchanged = lzcnt(~mask) >> 2; 110 | ctrl_blocks[0] = (uint32_t)(16 * i + ncom); 111 | 112 | i++; 113 | if (i >= i_end) { 114 | /* Last block, hence will not enter copy 115 | * loop */ 116 | ctrl_blocks[1] = (uint32_t)(16 * i); 117 | dc += 2; 118 | } 119 | 120 | break; 121 | } 122 | } 123 | if (i >= i_end) { 124 | dc -= 2; 125 | break; 126 | } 127 | 128 | /* Loop: until no changes for DIFF_WINDOW +/- 4 spaces */ 129 | for (; i < i_end; i++) { 130 | __m256i m0 = _mm256_load_si256(&mod[2 * i]); 131 | __m256i m1 = _mm256_load_si256(&mod[2 * i + 1]); 132 | __m256i b0 = _mm256_load_si256(&base[2 * i]); 133 | __m256i b1 = _mm256_load_si256(&base[2 * i + 1]); 134 | __m256i eq0 = _mm256_cmpeq_epi32(m0, b0); 135 | __m256i eq1 = _mm256_cmpeq_epi32(m1, b1); 136 | uint32_t mask0 = (uint32_t)_mm256_movemask_epi8(eq0); 137 | uint32_t mask1 = (uint32_t)_mm256_movemask_epi8(eq1); 138 | uint64_t mask = mask0 + mask1 * 0x100000000uLL; 139 | 140 | /* Reset trailing counter if anything changed */ 141 | bool clear = ~mask == 0; 142 | trailing_unchanged = clear * trailing_unchanged + 143 | (lzcnt(~mask) >> 2); 144 | 145 | _mm256_storeu_si256((__m256i *)&diff[dc], m0); 146 | _mm256_storeu_si256((__m256i *)&diff[dc + 8], m1); 147 | dc += 16; 148 | if (trailing_unchanged > diff_window_size) { 149 | i++; 150 | break; 151 | } 152 | _mm256_store_si256(&base[2 * i], m0); 153 | _mm256_store_si256(&base[2 * i + 1], m1); 154 | } 155 | /* Write coda */ 156 | dc -= (size_t)trailing_unchanged; 157 | ctrl_blocks[1] = 158 | (uint32_t)(16 * i - (size_t)trailing_unchanged); 159 | 160 | if (i >= i_end) { 161 | break; 162 | } 163 | } 164 | 165 | return dc; 166 | } 167 | -------------------------------------------------------------------------------- /protocols/wlr-gamma-control-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2015 Giulio camuffo 5 | Copyright © 2018 Simon Ser 6 | 7 | Permission to use, copy, modify, distribute, and sell this 8 | software and its documentation for any purpose is hereby granted 9 | without fee, provided that the above copyright notice appear in 10 | all copies and that both that copyright notice and this permission 11 | notice appear in supporting documentation, and that the name of 12 | the copyright holders not be used in advertising or publicity 13 | pertaining to distribution of the software without specific, 14 | written prior permission. The copyright holders make no 15 | representations about the suitability of this software for any 16 | purpose. It is provided "as is" without express or implied 17 | warranty. 18 | 19 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 20 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 21 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 24 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 25 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 26 | THIS SOFTWARE. 27 | 28 | 29 | 30 | This protocol allows a privileged client to set the gamma tables for 31 | outputs. 32 | 33 | Warning! The protocol described in this file is experimental and 34 | backward incompatible changes may be made. Backward compatible changes 35 | may be added together with the corresponding interface version bump. 36 | Backward incompatible changes are done by bumping the version number in 37 | the protocol and interface names and resetting the interface version. 38 | Once the protocol is to be declared stable, the 'z' prefix and the 39 | version number in the protocol and interface names are removed and the 40 | interface version number is reset. 41 | 42 | 43 | 44 | 45 | This interface is a manager that allows creating per-output gamma 46 | controls. 47 | 48 | 49 | 50 | 51 | Create a gamma control that can be used to adjust gamma tables for the 52 | provided output. 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | All objects created by the manager will still remain valid, until their 61 | appropriate destroy request has been called. 62 | 63 | 64 | 65 | 66 | 67 | 68 | This interface allows a client to adjust gamma tables for a particular 69 | output. 70 | 71 | The client will receive the gamma size, and will then be able to set gamma 72 | tables. At any time the compositor can send a failed event indicating that 73 | this object is no longer valid. 74 | 75 | There can only be at most one gamma control object per output, which 76 | has exclusive access to this particular output. When the gamma control 77 | object is destroyed, the gamma table is restored to its original value. 78 | 79 | 80 | 81 | 82 | Advertise the size of each gamma ramp. 83 | 84 | This event is sent immediately when the gamma control object is created. 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | Set the gamma table. The file descriptor can be memory-mapped to provide 96 | the raw gamma table, which contains successive gamma ramps for the red, 97 | green and blue channels. Each gamma ramp is an array of 16-byte unsigned 98 | integers which has the same length as the gamma size. 99 | 100 | The file descriptor data must have the same length as three times the 101 | gamma size. 102 | 103 | 104 | 105 | 106 | 107 | 108 | This event indicates that the gamma control is no longer valid. This 109 | can happen for a number of reasons, including: 110 | - The output doesn't support gamma tables 111 | - Setting the gamma tables failed 112 | - Another client already has exclusive gamma control for this output 113 | - The compositor has transferred gamma control to another client 114 | 115 | Upon receiving this event, the client should destroy this object. 116 | 117 | 118 | 119 | 120 | 121 | Destroys the gamma control object. If the object is still valid, this 122 | restores the original gamma tables. 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /.gitlab-ci/debian32.yml: -------------------------------------------------------------------------------- 1 | # vim: set expandtab shiftwidth=2 tabstop=8 textwidth=0: 2 | 3 | # This template will create a debian image based on the following variables: 4 | # 5 | # - DEBIAN_VERSION: the debian version (stretch, sid, etc...) 6 | # - DEBIAN_DEBS: if set, list of packages that needs to be installed 7 | # - DEBIAN_EXEC: if set, this command will be run once the packages have 8 | # been installed 9 | # - UPSTREAM_REPO: the upstream project on this gitlab instance where we might 10 | # find the given tag (for example: `wayland/weston`) 11 | # - DEBIAN_TAG: tag to copy the image from the upstream registry. If the 12 | # tag does not exist, create a new build and tag it 13 | # 14 | # The resulting image will be pushed in the local registry, under: 15 | # $CI_REGISTRY_IMAGE/debian/$DEBIAN_VERSION:$DEBIAN_TAG 16 | # 17 | # Two flavors of templates are available: 18 | # - `.debian32@container-build`: this will force rebuild a new container 19 | # and tag it with $DEBIAN_TAG without checks 20 | # - `.debian32@container-ifnot-exists`: this will rebuild a new container 21 | # only if $DEBIAN_TAG is not available in the local registry or 22 | # in the $UPSTREAM_REPO registry 23 | 24 | # we can not reuse exported variables in after_script, 25 | # so have a common definition 26 | .debian32_vars: &distro_vars | 27 | # exporting templates variables 28 | # https://gitlab.com/gitlab-com/support-forum/issues/4349 29 | export BUILDAH_FORMAT=docker 30 | # The '32' version should run multilib 31 | export DISTRO=debian 32 | export DISTRO_TAG=$DEBIAN32_TAG 33 | export DISTRO_VERSION=$DEBIAN32_VERSION 34 | export DISTRO_EXEC=$DEBIAN32_EXEC 35 | 36 | 37 | # Do not use this template directly, you can not reuse the produced image 38 | # as it is tagged with $CI_JOB_ID 39 | ..debian32@container-template: 40 | image: $CI_REGISTRY/wayland/ci-templates/buildah:latest 41 | stage: build 42 | before_script: 43 | # log in to the registry 44 | - podman login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 45 | 46 | - *distro_vars 47 | 48 | script: 49 | - *distro_vars 50 | - echo Building $DISTRO/$DISTRO_VERSION:$DISTRO_TAG from $DISTRO:$DISTRO_VERSION 51 | # initial set up: take the base image, update it and install the packages 52 | - buildcntr=$(buildah from $DISTRO:$DISTRO_VERSION) 53 | - buildmnt=$(buildah mount $buildcntr) 54 | - buildah run $buildcntr cat /etc/apt/sources.list 55 | - echo 'path-exclude=/usr/share/doc/*' > $buildmnt/etc/dpkg/dpkg.cfg.d/99-exclude-cruft 56 | - echo 'path-exclude=/usr/share/locale/*' >> $buildmnt/etc/dpkg/dpkg.cfg.d/99-exclude-cruft 57 | - echo 'path-exclude=/usr/share/man/*' >> $buildmnt/etc/dpkg/dpkg.cfg.d/99-exclude-cruft 58 | - echo 'APT::Install-Recommends "false";' > $buildmnt/etc/apt/apt.conf 59 | - echo '#!/bin/sh' > $buildmnt/usr/sbin/policy-rc.d 60 | - echo 'exit 101' >> $buildmnt/usr/sbin/policy-rc.d 61 | - chmod +x $buildmnt/usr/sbin/policy-rc.d 62 | 63 | - buildah run $buildcntr env DEBIAN_FRONTEND=noninteractive apt-get update 64 | - buildah run $buildcntr env DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade 65 | - if [[ x"$DEBIAN_DEBS" != x"" ]] ; 66 | then 67 | buildah run $buildcntr env DEBIAN_FRONTEND=noninteractive apt-get -y install $DEBIAN_DEBS ; 68 | fi 69 | 70 | # check if there is an optional post install script and run it 71 | - if [[ x"$DISTRO_EXEC" != x"" ]] ; 72 | then 73 | echo Running $DISTRO_EXEC ; 74 | set -x ; 75 | mkdir $buildmnt/tmp/clone ; 76 | pushd $buildmnt/tmp/clone ; 77 | git init ; 78 | git remote add origin $CI_REPOSITORY_URL ; 79 | git fetch --depth 1 origin $CI_COMMIT_SHA ; 80 | git checkout FETCH_HEAD > /dev/null; 81 | buildah config --workingdir /tmp/clone $buildcntr ; 82 | buildah run $buildcntr bash -c "set -x ; $DISTRO_EXEC" ; 83 | popd ; 84 | rm -rf $buildmnt/tmp/clone ; 85 | set +x ; 86 | fi 87 | 88 | # do not store the packages database, it's pointless 89 | - buildah run $buildcntr env DEBIAN_FRONTEND=noninteractive apt-get clean 90 | - rm -f $buildmnt/var/lib/apt/lists/*.lz4 91 | 92 | # set up the working directory 93 | - buildah config --workingdir /app $buildcntr 94 | # umount the container, not required, but, heh 95 | - buildah unmount $buildcntr 96 | # tag the current container 97 | - buildah commit $buildcntr $CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$CI_JOB_ID 98 | # clean up the working container 99 | - buildah rm $buildcntr 100 | 101 | # push the container image to the registry 102 | # There is a bug when pushing 2 tags in the same repo with the same base: 103 | # this may fail. Just retry it after. 104 | - podman push $CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$CI_JOB_ID || true 105 | - sleep 2 106 | - podman push $CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$CI_JOB_ID 107 | 108 | # mark the current stage as successed, to get the result in after_script 109 | - touch .success 110 | 111 | 112 | .debian32@container-build: 113 | extends: ..debian32@container-template 114 | after_script: 115 | # if we did not build, or if there was a failure, exit 116 | # (the exit status does not matter here) 117 | - if [[ ! -e .success ]] ; 118 | then 119 | exit 0; 120 | fi 121 | 122 | - *distro_vars 123 | 124 | - skopeo copy docker://$CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$CI_JOB_ID 125 | docker://$CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$DISTRO_TAG 126 | 127 | 128 | .debian32@container-ifnot-exists: 129 | extends: .debian32@container-build 130 | before_script: 131 | # log in to the registry 132 | - podman login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 133 | 134 | - *distro_vars 135 | 136 | # check if our image is already in the current registry 137 | - skopeo inspect docker://$CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$DISTRO_TAG > /dev/null && exit 0 || true 138 | 139 | # copy the original image into the current project registry namespace 140 | - skopeo copy docker://$CI_REGISTRY/$UPSTREAM_REPO/$DISTRO/$DISTRO_VERSION:$DISTRO_TAG 141 | docker://$CI_REGISTRY_IMAGE/$DISTRO/$DISTRO_VERSION:$DISTRO_TAG && exit 0 || true 142 | -------------------------------------------------------------------------------- /src/parsing.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | #ifndef WAYPIPE_PARSING_H 26 | #define WAYPIPE_PARSING_H 27 | 28 | #include 29 | #include 30 | 31 | struct char_window; 32 | struct int_window; 33 | struct fd_translation_map; 34 | struct main_config; 35 | 36 | struct wp_interface; 37 | /** An object used by the wayland protocol. Specific types may extend 38 | * this struct, using the following data as a header */ 39 | struct wp_object { 40 | struct wp_object *t_left, *t_right; // inline tree implementation 41 | const struct wp_interface *type; // Use to lookup the message handler 42 | uint32_t obj_id; 43 | bool is_zombie; // object deleted but not yet acknowledged remotely 44 | }; 45 | struct message_tracker { 46 | /* Tree containing all objects that are currently alive or zombie */ 47 | struct wp_object *objtree_root; 48 | /* sequence number to discriminate between wl_buffer objects; object ids 49 | * and pointers are not guaranteed to be unique */ 50 | uint64_t buffer_seqno; 51 | }; 52 | /** Context object, to be passed to the protocol handler functions */ 53 | struct context { 54 | struct globals *const g; 55 | struct message_tracker *const tracker; 56 | struct wp_object *obj; 57 | bool drop_this_msg; 58 | /* If true, running as waypipe client, and interfacing with compositor's 59 | * buffers */ 60 | const bool on_display_side; 61 | /* The transferred message can be rewritten in place, and resized, as 62 | * long as there is space available. Setting 'fds_changed' will 63 | * prevent the fd zone start from autoincrementing after running 64 | * the function, which may be useful when injecting messages with fds */ 65 | const int message_available_space; 66 | uint32_t *const message; 67 | int message_length; 68 | bool fds_changed; 69 | struct int_window *const fds; 70 | }; 71 | 72 | /** Add a protocol object to the list, replacing any preceding object with 73 | * the same id. */ 74 | void tracker_insert(struct message_tracker *mt, struct wp_object *obj); 75 | void tracker_remove(struct message_tracker *mt, struct wp_object *obj); 76 | /** Replace an object that is already in the protocol list with a new object 77 | * that has the same id; will silently fail if id not present */ 78 | void tracker_replace_existing( 79 | struct message_tracker *mt, struct wp_object *obj); 80 | struct wp_object *tracker_get(struct message_tracker *mt, uint32_t id); 81 | 82 | int init_message_tracker(struct message_tracker *mt); 83 | void cleanup_message_tracker(struct message_tracker *mt); 84 | 85 | /** Read message size from header; the 8 bytes beyond data must exist */ 86 | int peek_message_size(const void *data); 87 | /** Generate the second uint32_t field of a message header; this assumes no 88 | * fds or equivalently no fd count subfield */ 89 | static inline uint32_t message_header_2(uint32_t size_bytes, uint32_t msgno) 90 | { 91 | return (size_bytes << 16) | msgno; 92 | } 93 | const char *get_nth_packed_string(const char *pack, int n); 94 | enum parse_state { PARSE_KNOWN, PARSE_UNKNOWN, PARSE_ERROR }; 95 | /** 96 | * The return value is false iff the given message should be dropped. 97 | * The flag `unidentified_changes` is set to true if the message does 98 | * not correspond to a known protocol. 99 | * 100 | * The message data payload may be modified and increased in size. 101 | * 102 | * The window `chars` should start at the message start, end 103 | * at its end, and indicate remaining space. 104 | * The window `fds` should start at the next fd in the queue, ends 105 | * with the last. 106 | * 107 | * The start and end of `chars` will be moved to the new end of the message. 108 | * The end of `fds` may be moved if any fds are inserted or discarded. 109 | * The start of fds will be moved, depending on how many fds were consumed. 110 | */ 111 | enum parse_state handle_message(struct globals *g, bool on_display_side, 112 | bool from_client, struct char_window *chars, 113 | struct int_window *fds); 114 | /** 115 | * Given a set of messages and fds, parse the messages, and if indicated 116 | * by parsing logic, compact the message buffer by removing selected 117 | * messages, or edit message contents. 118 | * 119 | * The `source_bytes` window indicates the range of unread data; it's 120 | * zone start point will be advanced. The 'dest_bytes' window indicates 121 | * the range of written data; it's zone end point will be advanced. 122 | * 123 | * The file descriptor queue `fds` will have its start advanced, leaving only 124 | * file descriptors that have not yet been read. Further edits may be made 125 | * to inject new file descriptors. 126 | */ 127 | void parse_and_prune_messages(struct globals *g, bool on_display_side, 128 | bool from_client, struct char_window *source_bytes, 129 | struct char_window *dest_bytes, struct int_window *fds); 130 | 131 | // handlers.c 132 | /** Create a new Wayland protocol object of the given type; some types 133 | * produce structs extending from wp_object */ 134 | struct wp_object *create_wp_object( 135 | uint32_t it, const struct wp_interface *type); 136 | /** Type-specific destruction routines, also dereferencing linked shadow_fds */ 137 | void destroy_wp_object(struct wp_object *object); 138 | 139 | extern const struct wp_interface *the_display_interface; 140 | 141 | #endif // WAYPIPE_PARSING_H 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Waypipe 2 | ================================================================================ 3 | 4 | `waypipe` is a proxy for Wayland[0] clients. It forwards Wayland messages and 5 | serializes changes to shared memory buffers over a single socket. This makes 6 | application forwarding similar to `ssh -X` [1] feasible. 7 | 8 | [0] [https://wayland.freedesktop.org/](https://wayland.freedesktop.org/) 9 | [1] [https://wiki.archlinux.org/title/OpenSSH#X11_forwarding](https://wiki.archlinux.org/title/OpenSSH#X11_forwarding) 10 | 11 | ## Usage 12 | 13 | `waypipe` should be installed on both the local and remote computers. There is 14 | a user-friendly command line pattern which prefixes a call to `ssh` and 15 | automatically sets up a reverse tunnel for protocol data. For example, 16 | 17 | waypipe ssh user@theserver weston-terminal 18 | 19 | will run `ssh`, connect to `theserver`, and remotely run `weston-terminal`, 20 | using local and remote `waypipe` processes to synchronize the shared memory 21 | buffers used by Wayland clients between both computers. Command line arguments 22 | before `ssh` apply only to `waypipe`; those after `ssh` belong to `ssh`. 23 | 24 | Alternatively, one can launch the local and remote processes by hand, with the 25 | following set of shell commands: 26 | 27 | /usr/bin/waypipe -s /tmp/socket-local client & 28 | ssh -R /tmp/socket-remote:/tmp/socket-local -t user@theserver \ 29 | /usr/bin/waypipe -s /tmp/socket-remote server -- \ 30 | /usr/bin/weston-terminal 31 | kill %1 32 | 33 | It's possible to set up the local and remote processes so that, when the 34 | connection between the the sockets used by each end breaks, one can create 35 | a new forwarded socket on the remote side and reconnect the two processes. 36 | For a more detailed example, see the man page. 37 | 38 | ## Installing 39 | 40 | Build with meson[0]. A typical incantation is 41 | 42 | cd /path/to/waypipe/ && cd .. 43 | mkdir build-waypipe 44 | meson --buildtype debugoptimized waypipe build-waypipe 45 | ninja -C build-waypipe install 46 | 47 | Core build requirements: 48 | 49 | * meson (build, >= 0.47. with dependencies `ninja`, `pkg-config`, `python3`) 50 | * C compiler 51 | 52 | Optional dependencies: 53 | 54 | * liblz4 (for fast compression, >=1.7.0) 55 | * libzstd (for slower compression, >= 0.4.6) 56 | * libgbm (to support programs using OpenGL via DMABUFs) 57 | * libdrm (same as for libgbm) 58 | * ffmpeg (>=3.1, needs avcodec/avutil/swscale for lossy video encoding) 59 | * libva (for hardware video encoding and decoding) 60 | * scdoc (to generate a man page) 61 | * sys/sdt.h (to provide static tracepoints for profiling) 62 | * ssh (runtime, OpenSSH >= 6.7, for Unix domain socket forwarding) 63 | * libx264 (ffmpeg runtime, for software video decoding and encoding) 64 | 65 | [0] [https://mesonbuild.com/](https://mesonbuild.com/) 66 | [1] [https://git.sr.ht/~sircmpwn/scdoc](https://git.sr.ht/~sircmpwn/scdoc) 67 | 68 | ## Reporting issues 69 | 70 | Waypipe is developed at [0]; file bug reports or submit patches here. 71 | 72 | In general, if a program does not work properly under Waypipe, it is a bug 73 | worth reporting. If possible, before doing so ensure both computers are using 74 | the most recently released version of Waypipe (or are built from git master). 75 | 76 | A workaround that may help for some programs using OpenGL or Vulkan is to 77 | run Waypipe with the `--no-gpu` flag, which may force them to use software 78 | rendering and shared memory buffers. (Please still file a bug.) 79 | 80 | Some programs may require specific environment variable settings or command 81 | line flags to run remotely; a few examples are given in the man page[1]. 82 | 83 | Useful information for bug reports includes: 84 | 85 | * If a Waypipe process has crashed on either end of the connection, 86 | a full stack trace, with debug symbols. (In gdb, `bt full`). 87 | * If the program uses OpenGL or Vulkan, the graphics cards and drivers on 88 | both computers. 89 | * The output of `waypipe --version` on both ends of the connection 90 | * Logs when Waypipe is run with the `--debug` flag, or when the program 91 | is run with the environment variable setting `WAYLAND_DEBUG=1`. 92 | * Screenshots of any visual glitches. 93 | 94 | [0] [https://gitlab.freedesktop.org/mstoeckl/waypipe/](https://gitlab.freedesktop.org/mstoeckl/waypipe/) 95 | [1] [https://gitlab.freedesktop.org/mstoeckl/waypipe/-/blob/master/waypipe.scd](https://gitlab.freedesktop.org/mstoeckl/waypipe/-/blob/master/waypipe.scd) 96 | 97 | ## Technical Limitations 98 | 99 | Waypipe does not have a full view of the Wayland protocol. It includes a 100 | compiled form of the base protocol and several extension protocols, but is not 101 | able to parse all messages that the programs it connects send. Fortunately, the 102 | Wayland wire protocol is partially self-describing, so Waypipe can parse the 103 | messages it needs (those related to resources shared with file descriptors) 104 | while ignoring the rest. This makes Waypipe partially forward-compatible: if a 105 | future protocol comes out about details (for example, about window positioning) 106 | which do not require that file descriptors be sent, then applications will be 107 | able to use that protocol even with older versions of Waypipe. The 108 | tradeoff to allowing messages that Waypipe can not parse is that Waypipe can 109 | only make minor modifications to the wire protocol. In particular, adding or 110 | removing any Wayland protocol objects would require changing all messages that 111 | refer to them, including those messages that Waypipe does not parse. This 112 | precludes, for example, global object deduplication tricks that could reduce 113 | startup time for complicated applications. 114 | 115 | Shared memory buffer updates, including those for the contents of windows, are 116 | tracked by keeping a "mirror" copy of the buffer the represents the view which 117 | the opposing instance of Waypipe has. This way, Waypipe can send only the 118 | regions of the buffer that have changed relative to the remote copy. This is 119 | more efficient than resending the entire buffer on every update, which is good 120 | for applications with reasonably static user interfaces (like a text editor or 121 | email client). However, with programs with animations where the interaction 122 | latency matters (like games or certain audio tools), major window updates will 123 | unavoidably produce a lag spike. The additional memory cost of keeping mirrors 124 | is moderate. 125 | 126 | The video encoding option for DMABUFs currently maintains a video stream for 127 | each buffer that is used by a window surface. Since surfaces typically rotate 128 | between a small number of buffers, a video encoded window will appear to 129 | flicker as it switches rapidly between the underlying buffers, each of whose 130 | video streams has different encoding artifacts. 131 | 132 | The `zwp_linux_explicit_synchronization_v1` Wayland protocol is currently not 133 | supported. 134 | 135 | Waypipe does not work between computers that use different byte orders. 136 | -------------------------------------------------------------------------------- /test/diff_roundtrip.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include "common.h" 27 | #include "shadow.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | static int64_t rand_gap_fill(char *data, size_t size, int max_run) 35 | { 36 | if (max_run == -1) { 37 | memset(data, rand(), size); 38 | return 1; 39 | } else if (max_run == -2) { 40 | memset(data, 0, size); 41 | return 0; 42 | } 43 | 44 | max_run = max(2, max_run); 45 | size_t pos = 0; 46 | int64_t nruns = 0; 47 | while (pos < size) { 48 | int gap1 = (rand() % max_run); 49 | gap1 = min((int)(size - pos), gap1); 50 | pos += (size_t)gap1; 51 | int gap2 = (rand() % max_run); 52 | gap2 = min((int)(size - pos), gap2); 53 | int val = rand(); 54 | memset(&data[pos], val, (size_t)gap2); 55 | pos += (size_t)gap2; 56 | nruns++; 57 | } 58 | return nruns; 59 | } 60 | 61 | struct subtest { 62 | size_t size; 63 | int max_gap; 64 | uint32_t seed; 65 | int shards; 66 | }; 67 | 68 | static const struct subtest subtests[] = { 69 | {256, 128, 0x11, 3}, 70 | {333333, 128, 0x11, 3}, 71 | {39, 2, 0x13, 17}, 72 | {10000000, 262144, 0x21, 1}, 73 | {4, 4, 0x41, 1}, 74 | {65537, 177, 0x51, 1}, 75 | {17777, 2, 0x61, 1}, 76 | {60005, 60005, 0x71, 1}, 77 | {1 << 16, -1, 0x71, 4}, 78 | {1 << 16, -2, 0x71, 4}, 79 | {1 << 24, -1, 0x71, 4}, 80 | {1 << 24, -2, 0x71, 4}, 81 | }; 82 | 83 | static const enum diff_type diff_types[5] = { 84 | DIFF_AVX512F, 85 | DIFF_AVX2, 86 | DIFF_SSE3, 87 | DIFF_NEON, 88 | DIFF_C, 89 | }; 90 | static const char *diff_names[5] = { 91 | "avx512", 92 | "avx2 ", 93 | "sse3 ", 94 | "neon ", 95 | "plainC", 96 | }; 97 | 98 | static bool run_subtest(int i, const struct subtest test, char *diff, 99 | char *source, char *mirror, char *target1, char *target2, 100 | interval_diff_fn_t diff_fn, int alignment_bits, 101 | const char *diff_name) 102 | { 103 | uint64_t ns01 = 0, ns12 = 0; 104 | int64_t nruns = 0; 105 | size_t net_diffsize = 0; 106 | srand((uint32_t)test.seed); 107 | memset(mirror, 0, test.size); 108 | memset(target1, 0, test.size); 109 | memset(target2, 0, test.size); 110 | 111 | int roughtime = (int)test.size + test.shards * 500; 112 | int repetitions = min(100, max(1000000000 / roughtime, 1)); 113 | 114 | bool all_success = true; 115 | for (int x = 0; x < repetitions; x++) { 116 | nruns += rand_gap_fill(source, test.size, test.max_gap); 117 | 118 | net_diffsize = 0; 119 | for (int s = 0; s < test.shards; s++) { 120 | 121 | struct interval damage; 122 | damage.start = split_interval( 123 | 0, (int)test.size, test.shards, s); 124 | damage.end = split_interval( 125 | 0, (int)test.size, test.shards, s + 1); 126 | int alignment = 1 << alignment_bits; 127 | damage.start = alignment * (damage.start / alignment); 128 | damage.end = alignment * (damage.end / alignment); 129 | 130 | struct timespec t0, t1, t2; 131 | clock_gettime(CLOCK_MONOTONIC, &t0); 132 | size_t diffsize = 0; 133 | if (damage.start < damage.end) { 134 | diffsize = construct_diff_core(diff_fn, 135 | alignment_bits, &damage, 1, 136 | mirror, source, diff); 137 | } 138 | size_t ntrailing = 0; 139 | if (s == test.shards - 1) { 140 | ntrailing = construct_diff_trailing(test.size, 141 | alignment_bits, mirror, source, 142 | diff + diffsize); 143 | } 144 | clock_gettime(CLOCK_MONOTONIC, &t1); 145 | apply_diff(test.size, target1, target2, diffsize, 146 | ntrailing, diff); 147 | clock_gettime(CLOCK_MONOTONIC, &t2); 148 | ns01 += (uint64_t)((t1.tv_sec - t0.tv_sec) * 149 | 1000000000LL + 150 | (t1.tv_nsec - t0.tv_nsec)); 151 | ns12 += (uint64_t)((t2.tv_sec - t1.tv_sec) * 152 | 1000000000LL + 153 | (t2.tv_nsec - t1.tv_nsec)); 154 | net_diffsize += diffsize + ntrailing; 155 | } 156 | 157 | if (memcmp(target1, source, test.size)) { 158 | printf("Failed to synchronize\n"); 159 | int ndiff = 0; 160 | for (size_t k = 0; k < test.size; k++) { 161 | if (target1[k] != source[k] || 162 | mirror[k] != source[k]) { 163 | if (ndiff > 300) { 164 | printf("and still more differences\n"); 165 | break; 166 | } 167 | printf("i %d: target1 %02x mirror %02x source %02x\n", 168 | (int)k, 169 | (uint8_t)target1[k], 170 | (uint8_t)mirror[k], 171 | (uint8_t)source[k]); 172 | ndiff++; 173 | } 174 | } 175 | all_success = false; 176 | break; 177 | } 178 | } 179 | 180 | double scale = 1.0 / ((double)repetitions * (double)test.size); 181 | printf("%s #%2d, : %6.3f,%6.3f,%6.3f ns/byte create,apply,net (%d/%d@%d), %.1f bytes/run\n", 182 | diff_name, i, (double)ns01 * scale, 183 | (double)ns12 * scale, (double)(ns01 + ns12) * scale, 184 | (int)net_diffsize, (int)test.size, test.shards, 185 | (double)repetitions * (double)test.size / 186 | (double)nruns); 187 | return all_success; 188 | } 189 | 190 | log_handler_func_t log_funcs[2] = {test_log_handler, test_log_handler}; 191 | int main(int argc, char **argv) 192 | { 193 | (void)argc; 194 | (void)argv; 195 | 196 | bool all_success = true; 197 | 198 | const int nsubtests = (sizeof(subtests) / sizeof(subtests[0])); 199 | for (int i = 0; i < nsubtests; i++) { 200 | struct subtest test = subtests[i]; 201 | 202 | /* Use maximum alignment */ 203 | const size_t bufsize = alignz(test.size + 8 + 64, 64); 204 | char *diff = aligned_alloc(64, bufsize); 205 | char *source = aligned_alloc(64, bufsize); 206 | char *mirror = aligned_alloc(64, bufsize); 207 | char *target1 = aligned_alloc(64, bufsize); 208 | char *target2 = aligned_alloc(64, bufsize); 209 | const int ntypes = sizeof(diff_types) / sizeof(diff_types[0]); 210 | for (int a = 0; a < ntypes; a++) { 211 | int alignment_bits; 212 | interval_diff_fn_t diff_fn = get_diff_function( 213 | diff_types[a], &alignment_bits); 214 | if (!diff_fn) { 215 | continue; 216 | } 217 | all_success &= run_subtest(i, test, diff, source, 218 | mirror, target1, target2, diff_fn, 219 | alignment_bits, diff_names[a]); 220 | } 221 | free(diff); 222 | free(source); 223 | free(mirror); 224 | free(target1); 225 | free(target2); 226 | } 227 | 228 | return all_success ? EXIT_SUCCESS : EXIT_FAILURE; 229 | } 230 | -------------------------------------------------------------------------------- /protocols/sendgen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os, sys, fnmatch 4 | import xml.etree.ElementTree as ET 5 | 6 | """ 7 | A static protocol code generator for the task of creating the wire representation 8 | of a list of events/requests 9 | """ 10 | 11 | wltype_to_ctypes = { 12 | "uint": "uint32_t ", 13 | "fixed": "uint32_t ", 14 | "int": "int32_t ", 15 | "object": "struct wp_objid ", 16 | "new_id": "struct wp_objid ", 17 | "string": "const char *", 18 | "fd": "int ", 19 | } 20 | 21 | 22 | def write_enum(ostream, iface_name, enum): 23 | enum_name = enum.attrib["name"] 24 | is_bitfield = "bitfield" in enum.attrib and enum.attrib["bitfield"] == "true" 25 | 26 | for entry in enum: 27 | if entry.tag != "entry": 28 | continue 29 | entry_name = entry.attrib["name"] 30 | entry_value = entry.attrib["value"] 31 | 32 | full_name = (iface_name + "_" + enum_name + "_" + entry_name).upper() 33 | print("#define {} {}".format(full_name, entry_value), file=ostream) 34 | 35 | 36 | def is_exportable(func_name, export_list): 37 | for e in export_list: 38 | if fnmatch.fnmatchcase(func_name, e): 39 | return True 40 | return False 41 | 42 | 43 | def write_func(ostream, iface_name, func, is_request, func_no, export_list): 44 | func_name = ( 45 | iface_name + "_" + ("req" if is_request else "evt") + "_" + func.attrib["name"] 46 | ) 47 | 48 | for_export = is_exportable(func_name, export_list) 49 | if not for_export: 50 | return 51 | 52 | c_sig = ["struct transfer_states *ts", "struct wp_objid " + iface_name + "_id"] 53 | w_args = [] 54 | 55 | num_fd_args = 0 56 | num_reg_args = 0 57 | num_obj_args = 0 58 | num_new_args = 0 59 | num_stretch_args = 0 60 | for arg in func: 61 | if arg.tag != "arg": 62 | continue 63 | 64 | arg_name = arg.attrib["name"] 65 | arg_type = arg.attrib["type"] 66 | arg_interface = arg.attrib["interface"] if "interface" in arg.attrib else None 67 | if arg_type == "new_id" and arg_interface is None: 68 | # Special case, for wl_registry_bind 69 | c_sig.append("const char *interface") 70 | c_sig.append("uint32_t version") 71 | c_sig.append("struct wp_objid id") 72 | w_args.append(("interface", "string", None)) 73 | w_args.append(("version", "uint", None)) 74 | w_args.append((arg_name, "new_id", None)) 75 | num_obj_args += 1 76 | num_new_args += 1 77 | num_reg_args += 3 78 | num_stretch_args += 1 79 | continue 80 | 81 | if arg_type == "array": 82 | c_sig.append("int " + arg_name + "_count") 83 | c_sig.append("const uint8_t *" + arg_name + "_val") 84 | else: 85 | c_sig.append(wltype_to_ctypes[arg_type] + arg_name) 86 | w_args.append((arg_name, arg_type, arg_interface)) 87 | if arg_type == "fd": 88 | num_fd_args += 1 89 | else: 90 | num_reg_args += 1 91 | if arg_type == "object" or arg_type == "new_id": 92 | num_obj_args += 1 93 | if arg_type == "new_id": 94 | num_new_args += 1 95 | if arg_type in ("array", "string"): 96 | num_stretch_args += 1 97 | 98 | send_signature = "static void send_{}({}) ".format(func_name, ", ".join(c_sig)) 99 | 100 | W = lambda *x: print(*x, file=ostream) 101 | 102 | # Write function definition 103 | W(send_signature + " {") 104 | W("\tts->fd_size = 0;") 105 | W("\tts->msg_space[0] = {}.id;".format(iface_name + "_id")) 106 | W("\tts->msg_size = 2;") 107 | 108 | tmp_names = ["ctx"] 109 | for i, (arg_name, arg_type, arg_interface) in enumerate(w_args): 110 | if arg_type == "array": 111 | raise NotImplementedError() 112 | continue 113 | elif arg_type == "fd": 114 | W("\tts->fd_space[ts->fd_size++] = {};".format(arg_name)) 115 | continue 116 | elif arg_type == "string": 117 | W("\tserialize_string(ts, {});".format(arg_name)) 118 | continue 119 | elif arg_type == "object" or arg_type == "new_id": 120 | W("\tts->msg_space[ts->msg_size++] = {}.id;".format(arg_name)) 121 | elif arg_type == "int": 122 | W("\tts->msg_space[ts->msg_size++] = (uint32_t){};".format(arg_name)) 123 | elif arg_type == "uint" or arg_type == "fixed": 124 | W("\tts->msg_space[ts->msg_size++] = {};".format(arg_name)) 125 | else: 126 | raise KeyError(arg_type) 127 | 128 | W("\tts->msg_space[1] = ((uint32_t)ts->msg_size << 18) | {};".format(func_no)) 129 | if is_request: 130 | W("\tts->send(ts, ts->app, ts->comp);") 131 | else: 132 | W("\tts->send(ts, ts->comp, ts->app);") 133 | 134 | W("}") 135 | 136 | 137 | if __name__ == "__main__": 138 | req_file, dest = sys.argv[1:3] 139 | sources = sys.argv[3:] 140 | assert dest.endswith(".h") 141 | dest_shortname = dest[:-2] 142 | header_flag = dest_shortname.upper().replace("/", "_") + "_H" 143 | 144 | export_list = open(req_file).read().split("\n") 145 | 146 | with open(dest, "w") as ostream: 147 | W = lambda *x: print(*x, file=ostream) 148 | 149 | W("#ifndef {}".format(header_flag)) 150 | W("#include ") 151 | W("#include ") 152 | W("#include ") 153 | W("struct test_state;") 154 | W("struct wp_objid { uint32_t id; };") 155 | W("struct transfer_states {") 156 | W("\tuint32_t msg_space[256];") 157 | W("\tint fd_space[16];") 158 | W("\tunsigned int msg_size;") 159 | W("\tunsigned int fd_size;") 160 | W("\tstruct test_state *app;") 161 | W("\tstruct test_state *comp;") 162 | W( 163 | "\tvoid (*send)(struct transfer_states *, struct test_state *src, struct test_state *dst);" 164 | ) 165 | W("};") 166 | # note: this script assumes that serialize_string will be used 167 | W("static void serialize_string(struct transfer_states *ts, const char *str) {") 168 | W("\tif (str) {") 169 | W("\t\tsize_t slen = strlen(str) + 1;") 170 | W("\t\tts->msg_space[ts->msg_size] = (uint32_t)slen;") 171 | W("\t\tmemcpy(&ts->msg_space[ts->msg_size + 1], str, slen);") 172 | W("\t\tts->msg_size += ((uint32_t)slen + 0x7) >> 2;") 173 | W("\t} else {") 174 | W("\t\tts->msg_space[ts->msg_size++] = 0;") 175 | W("\t}") 176 | W("}") 177 | 178 | for source in sorted(sources): 179 | tree = ET.parse(source) 180 | root = tree.getroot() 181 | for interface in root: 182 | if interface.tag != "interface": 183 | continue 184 | iface_name = interface.attrib["name"] 185 | 186 | func_data = [] 187 | nreq, nevt = 0, 0 188 | for item in interface: 189 | if item.tag == "enum": 190 | write_enum(ostream, iface_name, item) 191 | elif item.tag == "request": 192 | write_func(ostream, iface_name, item, True, nreq, export_list) 193 | nreq += 1 194 | elif item.tag == "event": 195 | write_func(ostream, iface_name, item, False, nevt, export_list) 196 | nevt += 1 197 | elif item.tag == "description": 198 | pass 199 | else: 200 | raise Exception(item.tag) 201 | 202 | W("#endif /* {} */".format(header_flag)) 203 | -------------------------------------------------------------------------------- /test/wire_parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include "common.h" 27 | #include "parsing.h" 28 | #include "shadow.h" 29 | #include "util.h" 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "protocol-test-proto.h" 37 | 38 | /* from parsing.c */ 39 | bool size_check(const struct msg_data *data, const uint32_t *payload, 40 | unsigned int true_length, int fd_length); 41 | 42 | void do_xtype_req_blue(struct context *ctx, const char *interface, 43 | uint32_t version, struct wp_object *id, int b, int32_t c, 44 | uint32_t d, struct wp_object *e, const char *f, uint32_t g) 45 | { 46 | char buf[256]; 47 | sprintf(buf, "%s %u %u %d %d %u %u %s %u", interface, version, 48 | id ? id->obj_id : 0, b, c, d, e ? e->obj_id : 0, f, g); 49 | printf("%s\n", buf); 50 | ctx->drop_this_msg = 51 | strcmp(buf, "babacba 4441 992 7771 3331 4442 991 (null) 4443") != 52 | 0; 53 | } 54 | void do_xtype_evt_yellow(struct context *ctx, uint32_t c) 55 | { 56 | char buf[256]; 57 | sprintf(buf, "%u", c); 58 | printf("%s\n", buf); 59 | ctx->drop_this_msg = strcmp(buf, "4441") != 0; 60 | } 61 | void do_ytype_req_green(struct context *ctx, uint32_t a, const char *b, 62 | const char *c, int d, const char *e, struct wp_object *f, 63 | uint32_t g_count, const uint8_t *g_val) 64 | { 65 | char buf[256]; 66 | sprintf(buf, "%u %s %s %d %s %u %u %x|%x|%x|%x|%x|%x|%x|%x", a, b, c, d, 67 | e, f ? f->obj_id : 0, g_count, g_val[0], g_val[1], 68 | g_val[2], g_val[3], g_val[4], g_val[5], g_val[6], 69 | g_val[7]); 70 | printf("%s\n", buf); 71 | ctx->drop_this_msg = 72 | strcmp(buf, "4441 bea (null) 7771 cbbc 991 8 81|80|81|80|90|99|99|99") != 73 | 0; 74 | } 75 | void do_ytype_evt_red(struct context *ctx, struct wp_object *a, int32_t b, 76 | int c, struct wp_object *d, int32_t e, int32_t f, 77 | struct wp_object *g, int32_t h, uint32_t i, const char *j, 78 | int k, uint32_t l_count, const uint8_t *l_val, uint32_t n, 79 | const char *m, struct wp_object *o, int p, struct wp_object *q) 80 | { 81 | char buf[256]; 82 | sprintf(buf, "%u %d %d %u %d %d %u %d %u %s %d %u %x|%x|%x %u %s %u %d %u", 83 | a ? a->obj_id : 0, b, c, d ? d->obj_id : 0, e, f, 84 | g ? g->obj_id : 0, h, i, j, k, l_count, l_val[0], 85 | l_val[1], l_val[2], n, m, o ? o->obj_id : 0, p, 86 | q ? q->obj_id : 0); 87 | printf("%s\n", buf); 88 | ctx->drop_this_msg = 89 | strcmp(buf, "0 33330 8881 0 33331 33332 0 33333 44440 bcaba 8882 3 80|80|80 99990 (null) 992 8883 991") != 90 | 0; 91 | } 92 | 93 | struct wire_test { 94 | const struct wp_interface *intf; 95 | int msg_offset; 96 | int fds[4]; 97 | uint32_t words[50]; 98 | int nfds; 99 | int nwords; 100 | }; 101 | 102 | static inline uint32_t pack_u32(uint8_t a0, uint8_t a1, uint8_t a2, uint8_t a3) 103 | { 104 | union { 105 | uint8_t s[4]; 106 | uint32_t v; 107 | } u; 108 | u.s[0] = a0; 109 | u.s[1] = a1; 110 | u.s[2] = a2; 111 | u.s[3] = a3; 112 | return u.v; 113 | } 114 | 115 | log_handler_func_t log_funcs[2] = {test_log_handler, test_log_handler}; 116 | int main(int argc, char **argv) 117 | { 118 | (void)argc; 119 | (void)argv; 120 | 121 | struct message_tracker mt; 122 | init_message_tracker(&mt); 123 | struct wp_object *old_display = tracker_get(&mt, 1); 124 | tracker_remove(&mt, old_display); 125 | destroy_wp_object(old_display); 126 | 127 | struct wp_object xobj; 128 | xobj.type = &intf_xtype; 129 | xobj.is_zombie = false; 130 | xobj.obj_id = 991; 131 | tracker_insert(&mt, &xobj); 132 | 133 | struct wp_object yobj; 134 | yobj.type = &intf_ytype; 135 | yobj.is_zombie = false; 136 | yobj.obj_id = 992; 137 | tracker_insert(&mt, &yobj); 138 | 139 | struct context ctx = {.obj = &xobj, .g = NULL}; 140 | 141 | struct wire_test tests[] = { 142 | {&intf_xtype, 0, {7771}, 143 | {8, pack_u32(0x62, 0x61, 0x62, 0x61), 144 | pack_u32(0x63, 0x62, 145 | 0x61, 146 | 0), 147 | 4441, yobj.obj_id, 3331, 148 | 4442, xobj.obj_id, 0, 149 | 4443}, 150 | 1, 10}, 151 | {&intf_xtype, 1, {0}, {4441}, 0, 1}, 152 | {&intf_ytype, 0, {7771}, 153 | {4441, 4, pack_u32(0x62, 0x65, 0x61, 0), 154 | 0, 5, 155 | pack_u32(0x63, 0x62, 156 | 0x62, 157 | 0x63), 158 | pack_u32(0, 0x99, 0x99, 159 | 0x99), 160 | xobj.obj_id, 8, 161 | pack_u32(0x81, 0x80, 162 | 0x81, 163 | 0x80), 164 | pack_u32(0x90, 0x99, 165 | 0x99, 166 | 0x99)}, 167 | 1, 11}, 168 | {&intf_ytype, 1, {8881, 8882, 8883}, 169 | {7770, 33330, 7771, 33331, 33332, 7773, 170 | 33333, 44440, 6, 171 | pack_u32(0x62, 0x63, 172 | 0x61, 173 | 0x62), 174 | pack_u32(0x61, 0, 0x99, 175 | 0x99), 176 | 3, 177 | pack_u32(0x80, 0x80, 178 | 0x80, 179 | 0x11), 180 | 99990, 0, yobj.obj_id, 181 | xobj.obj_id}, 182 | 3, 17}}; 183 | 184 | bool all_success = true; 185 | for (size_t t = 0; t < sizeof(tests) / sizeof(tests[0]); t++) { 186 | struct wire_test *wt = &tests[t]; 187 | 188 | ctx.drop_this_msg = false; 189 | wp_callfn_t func = wt->intf->msgs[wt->msg_offset].call; 190 | 191 | (*func)(&ctx, wt->words, wt->fds, &mt); 192 | if (ctx.drop_this_msg) { 193 | all_success = false; 194 | } 195 | printf("Function call %s.%s, %s\n", wt->intf->name, 196 | get_nth_packed_string(wt->intf->msg_names, 197 | wt->msg_offset), 198 | ctx.drop_this_msg ? "FAIL" : "pass"); 199 | 200 | for (int fdlen = wt->nfds; fdlen >= 0; fdlen--) { 201 | for (int length = wt->nwords; length >= 0; length--) { 202 | if (fdlen != wt->nfds && length < wt->nwords) { 203 | /* the fd check is really trivial */ 204 | continue; 205 | } 206 | 207 | bool expect_success = (wt->nwords == length) && 208 | (fdlen == wt->nfds); 209 | printf("Trying: %d/%d words, %d/%d fds\n", 210 | length, wt->nwords, fdlen, 211 | wt->nfds); 212 | 213 | bool sp = size_check( 214 | &wt->intf->msgs[wt->msg_offset], 215 | wt->words, (unsigned int)length, 216 | fdlen); 217 | if (sp != expect_success) { 218 | wp_error("size check FAIL (%c, expected %c) at %d/%d chars, %d/%d fds", 219 | sp ? 'Y' : 'n', 220 | expect_success ? 'Y' 221 | : 'n', 222 | length, wt->nwords, 223 | fdlen, wt->nfds); 224 | } 225 | all_success &= (sp == expect_success); 226 | } 227 | } 228 | } 229 | 230 | tracker_remove(&mt, &xobj); 231 | tracker_remove(&mt, &yobj); 232 | cleanup_message_tracker(&mt); 233 | 234 | printf("Net result: %s\n", all_success ? "pass" : "FAIL"); 235 | return all_success ? EXIT_SUCCESS : EXIT_FAILURE; 236 | } 237 | -------------------------------------------------------------------------------- /protocols/wayland-drm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copyright © 2008-2011 Kristian Høgsberg 6 | Copyright © 2010-2011 Intel Corporation 7 | 8 | Permission to use, copy, modify, distribute, and sell this 9 | software and its documentation for any purpose is hereby granted 10 | without fee, provided that\n the above copyright notice appear in 11 | all copies and that both that copyright notice and this permission 12 | notice appear in supporting documentation, and that the name of 13 | the copyright holders not be used in advertising or publicity 14 | pertaining to distribution of the software without specific, 15 | written prior permission. The copyright holders make no 16 | representations about the suitability of this software for any 17 | purpose. It is provided "as is" without express or implied 18 | warranty. 19 | 20 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 21 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 22 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 24 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 25 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 26 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 27 | THIS SOFTWARE. 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | Bitmask of capabilities. 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /test/fuzz_hook_int.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include "common.h" 27 | #include "main.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | 45 | struct copy_setup { 46 | int conn; 47 | int wayl; 48 | bool is_display_side; 49 | struct main_config *mc; 50 | }; 51 | 52 | static void *start_looper(void *data) 53 | { 54 | struct copy_setup *setup = (struct copy_setup *)data; 55 | main_interface_loop(setup->conn, setup->wayl, -1, setup->mc, 56 | setup->is_display_side); 57 | return NULL; 58 | } 59 | 60 | log_handler_func_t log_funcs[2] = {NULL, NULL}; 61 | int main(int argc, char **argv) 62 | { 63 | if (argc == 1 || !strcmp(argv[1], "--help")) { 64 | printf("Usage: ./fuzz_hook_int [--server] [--log] {input_file}\n"); 65 | printf("A program to run and control Wayland and channel inputs for a waypipe main loop\n"); 66 | return EXIT_FAILURE; 67 | } 68 | bool display_side = true; 69 | if (argc > 1 && !strcmp(argv[1], "--server")) { 70 | display_side = false; 71 | argc--; 72 | argv++; 73 | } 74 | if (argc > 1 && !strcmp(argv[1], "--log")) { 75 | log_funcs[0] = test_atomic_log_handler; 76 | log_funcs[1] = test_atomic_log_handler; 77 | argc--; 78 | argv++; 79 | } 80 | setup_video_logging(); 81 | 82 | size_t len; 83 | char *buf = read_file_into_mem(argv[1], &len); 84 | if (!buf) { 85 | return EXIT_FAILURE; 86 | } 87 | printf("Loaded %zu bytes\n", len); 88 | 89 | int way_fds[2], conn_fds[2]; 90 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, way_fds) == -1 || 91 | socketpair(AF_UNIX, SOCK_STREAM, 0, conn_fds) == -1) { 92 | printf("Socketpair failed\n"); 93 | return EXIT_FAILURE; 94 | } 95 | 96 | struct main_config config = { 97 | .drm_node = NULL, 98 | .n_worker_threads = 1, 99 | .compression = COMP_NONE, 100 | .compression_level = 0, 101 | .no_gpu = true, /* until we can construct dmabufs here 102 | */ 103 | .only_linear_dmabuf = false, 104 | .video_if_possible = true, 105 | .prefer_hwvideo = false, 106 | }; 107 | 108 | pthread_t thread; 109 | struct copy_setup conf = {.conn = conn_fds[1], 110 | .wayl = way_fds[1], 111 | .is_display_side = display_side, 112 | .mc = &config}; 113 | if (pthread_create(&thread, NULL, start_looper, &conf) == -1) { 114 | printf("Thread failed\n"); 115 | return EXIT_FAILURE; 116 | } 117 | 118 | char *ignore_buf = malloc(65536); 119 | 120 | /* Main loop: RW from socketpairs with sendmsg, with short wait */ 121 | int64_t file_nwords = (int64_t)len / 4; 122 | int64_t cursor = 0; 123 | uint32_t *data = (uint32_t *)buf; 124 | while (cursor < file_nwords) { 125 | uint32_t header = data[cursor++]; 126 | bool wayland_side = header & 0x1; 127 | bool add_file = header & 0x2; 128 | int new_fileno = -1; 129 | 130 | if (add_file && wayland_side && cursor < file_nwords) { 131 | uint32_t fsize = data[cursor++]; 132 | if (fsize == 0) { 133 | /* 'copy' sink */ 134 | new_fileno = open("/dev/null", 135 | O_WRONLY | O_NOCTTY); 136 | if (new_fileno == -1) { 137 | wp_error("Failed to open /dev/null"); 138 | } 139 | } else { 140 | /* avoid buffer overflow */ 141 | fsize = fsize > 1000000 ? 1000000 : fsize; 142 | new_fileno = create_anon_file(); 143 | if (ftruncate(new_fileno, (off_t)fsize) == -1) { 144 | wp_error("Failed to resize tempfile"); 145 | checked_close(new_fileno); 146 | break; 147 | } 148 | } 149 | } 150 | 151 | uint32_t packet_size = header >> 2; 152 | int64_t words_left = file_nwords - cursor; 153 | if (packet_size > 2048) { 154 | packet_size = 2048; 155 | } 156 | if (packet_size > (uint32_t)words_left) { 157 | packet_size = (uint32_t)words_left; 158 | } 159 | /* 2 msec max delay for 8KB of data, assuming no system 160 | * interference, should be easily attainable */ 161 | int max_write_delay_ms = 1; 162 | int max_read_delay_ms = 2; 163 | 164 | int send_fd = wayland_side ? way_fds[0] : conn_fds[0]; 165 | /* Write packet to stream */ 166 | struct pollfd write_pfd; 167 | write_pfd.fd = send_fd; 168 | write_pfd.events = POLLOUT; 169 | int nw; 170 | retry_poll: 171 | nw = poll(&write_pfd, 1, max_write_delay_ms); 172 | if (nw == -1) { 173 | if (new_fileno != -1) { 174 | checked_close(new_fileno); 175 | } 176 | 177 | if (errno == EINTR) { 178 | goto retry_poll; 179 | } 180 | printf("Poll error\n"); 181 | break; 182 | } else if (nw == 1 && wayland_side) { 183 | /* Send message */ 184 | struct iovec the_iovec; 185 | the_iovec.iov_len = packet_size * 4; 186 | the_iovec.iov_base = (char *)&data[cursor]; 187 | struct msghdr msg; 188 | msg.msg_name = NULL; 189 | msg.msg_namelen = 0; 190 | msg.msg_iov = &the_iovec; 191 | msg.msg_iovlen = 1; 192 | msg.msg_control = NULL; 193 | msg.msg_controllen = 0; 194 | msg.msg_flags = 0; 195 | 196 | union { 197 | char buf[CMSG_SPACE(sizeof(int))]; 198 | struct cmsghdr align; 199 | } uc; 200 | memset(uc.buf, 0, sizeof(uc.buf)); 201 | 202 | if (new_fileno != -1) { 203 | msg.msg_control = uc.buf; 204 | msg.msg_controllen = sizeof(uc.buf); 205 | struct cmsghdr *frst = CMSG_FIRSTHDR(&msg); 206 | frst->cmsg_level = SOL_SOCKET; 207 | frst->cmsg_type = SCM_RIGHTS; 208 | memcpy(CMSG_DATA(frst), &new_fileno, 209 | sizeof(int)); 210 | frst->cmsg_len = CMSG_LEN(sizeof(int)); 211 | msg.msg_controllen = CMSG_SPACE(sizeof(int)); 212 | } 213 | 214 | ssize_t ret = sendmsg(way_fds[0], &msg, 0); 215 | if (ret == -1) { 216 | wp_error("Error in sendmsg"); 217 | break; 218 | } 219 | } else if (nw == 1 && !wayland_side) { 220 | ssize_t ret = write(conn_fds[0], (char *)&data[cursor], 221 | packet_size * 4); 222 | if (ret == -1) { 223 | wp_error("Error in write"); 224 | break; 225 | } 226 | } else { 227 | wp_error("Failed to send message before timeout"); 228 | } 229 | if (new_fileno != -1) { 230 | checked_close(new_fileno); 231 | } 232 | 233 | /* Wait up to max_delay for a response. Almost all packets 234 | * should be passed on unmodified; a very small fraction 235 | * are dropped */ 236 | struct pollfd read_pfds[2]; 237 | read_pfds[0].fd = way_fds[0]; 238 | read_pfds[1].fd = conn_fds[0]; 239 | read_pfds[0].events = POLLIN; 240 | read_pfds[1].events = POLLIN; 241 | int nr = poll(read_pfds, 2, 242 | packet_size > 0 ? max_read_delay_ms : 0); 243 | if (nr == -1) { 244 | if (errno == EINTR) { 245 | continue; 246 | } 247 | printf("Poll error\n"); 248 | break; 249 | } else if (nr == 0) { 250 | wp_debug("No reply to sent packet %d", packet_size); 251 | } 252 | for (int i = 0; i < 2; i++) { 253 | if (read_pfds[i].revents & POLLIN) { 254 | char cmsgdata[(CMSG_LEN(28 * sizeof(int32_t)))]; 255 | struct iovec the_iovec; 256 | the_iovec.iov_len = 65536; 257 | the_iovec.iov_base = ignore_buf; 258 | struct msghdr msg; 259 | msg.msg_name = NULL; 260 | msg.msg_namelen = 0; 261 | msg.msg_iov = &the_iovec; 262 | msg.msg_iovlen = 1; 263 | msg.msg_control = &cmsgdata; 264 | msg.msg_controllen = sizeof(cmsgdata); 265 | msg.msg_flags = 0; 266 | ssize_t ret = recvmsg(read_pfds[i].fd, &msg, 0); 267 | if (ret == -1) { 268 | wp_error("Error in recvmsg"); 269 | } 270 | } 271 | } 272 | 273 | cursor += packet_size; 274 | } 275 | checked_close(conn_fds[0]); 276 | checked_close(way_fds[0]); 277 | 278 | pthread_join(thread, NULL); 279 | 280 | free(buf); 281 | free(ignore_buf); 282 | return EXIT_SUCCESS; 283 | } 284 | -------------------------------------------------------------------------------- /test/fuzz_hook_ext.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include "common.h" 27 | #include "main.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | 45 | struct copy_setup { 46 | int conn; 47 | int wayl; 48 | bool is_display_side; 49 | struct main_config *mc; 50 | }; 51 | 52 | static void *start_looper(void *data) 53 | { 54 | struct copy_setup *setup = (struct copy_setup *)data; 55 | main_interface_loop(setup->conn, setup->wayl, -1, setup->mc, 56 | setup->is_display_side); 57 | return NULL; 58 | } 59 | 60 | log_handler_func_t log_funcs[2] = {NULL, NULL}; 61 | int main(int argc, char **argv) 62 | { 63 | if (argc == 1 || !strcmp(argv[1], "--help")) { 64 | printf("Usage: ./fuzz_hook_ext [--log] {input_file}\n"); 65 | printf("A program to run and control Wayland inputs for a linked client/server pair, from a file.\n"); 66 | return EXIT_FAILURE; 67 | } 68 | if (argc > 1 && !strcmp(argv[1], "--log")) { 69 | log_funcs[0] = test_atomic_log_handler; 70 | log_funcs[1] = test_atomic_log_handler; 71 | argc--; 72 | argv++; 73 | } 74 | setup_video_logging(); 75 | 76 | size_t len; 77 | char *buf = read_file_into_mem(argv[1], &len); 78 | if (!buf) { 79 | return EXIT_FAILURE; 80 | } 81 | printf("Loaded %zu bytes\n", len); 82 | 83 | int srv_fds[2], cli_fds[2], conn_fds[2]; 84 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, srv_fds) == -1 || 85 | socketpair(AF_UNIX, SOCK_STREAM, 0, cli_fds) == -1 || 86 | socketpair(AF_UNIX, SOCK_STREAM, 0, conn_fds) == -1) { 87 | printf("Socketpair failed\n"); 88 | return EXIT_FAILURE; 89 | } 90 | 91 | struct main_config config = { 92 | .drm_node = NULL, 93 | .n_worker_threads = 1, 94 | .compression = COMP_NONE, 95 | .compression_level = 0, 96 | .no_gpu = true, /* until we can construct dmabufs here 97 | */ 98 | .only_linear_dmabuf = false, 99 | .video_if_possible = true, 100 | .prefer_hwvideo = false, 101 | }; 102 | 103 | pthread_t thread_a, thread_b; 104 | struct copy_setup server_conf = {.conn = conn_fds[0], 105 | .wayl = srv_fds[1], 106 | .is_display_side = true, 107 | .mc = &config}; 108 | struct copy_setup client_conf = {.conn = conn_fds[1], 109 | .wayl = cli_fds[1], 110 | .is_display_side = false, 111 | .mc = &config}; 112 | if (pthread_create(&thread_a, NULL, start_looper, &server_conf) == -1) { 113 | printf("Thread failed\n"); 114 | } 115 | if (pthread_create(&thread_b, NULL, start_looper, &client_conf) == -1) { 116 | printf("Thread failed\n"); 117 | } 118 | 119 | char *ignore_buf = malloc(65536); 120 | 121 | /* Main loop: RW from socketpairs with sendmsg, with short wait */ 122 | int64_t file_nwords = (int64_t)len / 4; 123 | int64_t cursor = 0; 124 | uint32_t *data = (uint32_t *)buf; 125 | 126 | while (cursor < file_nwords) { 127 | uint32_t header = data[cursor++]; 128 | bool to_server = header & 0x1; 129 | bool add_file = header & 0x2; 130 | int new_fileno = -1; 131 | 132 | if (add_file && cursor < file_nwords) { 133 | uint32_t fsize = data[cursor++]; 134 | if (fsize == 0) { 135 | /* 'copy' sink */ 136 | new_fileno = open("/dev/null", 137 | O_WRONLY | O_NOCTTY); 138 | if (new_fileno == -1) { 139 | wp_error("Failed to open /dev/null"); 140 | } 141 | } else { 142 | /* avoid buffer overflow */ 143 | fsize = fsize > 1000000 ? 1000000 : fsize; 144 | new_fileno = create_anon_file(); 145 | if (ftruncate(new_fileno, (off_t)fsize) == -1) { 146 | wp_error("Failed to resize tempfile"); 147 | checked_close(new_fileno); 148 | break; 149 | } 150 | } 151 | } 152 | 153 | uint32_t packet_size = header >> 2; 154 | int64_t words_left = file_nwords - cursor; 155 | if (packet_size > 2048) { 156 | packet_size = 2048; 157 | } 158 | if (packet_size > (uint32_t)words_left) { 159 | packet_size = (uint32_t)words_left; 160 | } 161 | /* 2 msec max delay for 8KB of data, assuming no system 162 | * interference, should be easily attainable */ 163 | int max_write_delay_ms = 1; 164 | int max_read_delay_ms = 2; 165 | 166 | int send_fd = to_server ? srv_fds[0] : cli_fds[0]; 167 | /* Write packet to stream */ 168 | struct pollfd write_pfd; 169 | write_pfd.fd = send_fd; 170 | write_pfd.events = POLLOUT; 171 | int nw; 172 | retry_poll: 173 | nw = poll(&write_pfd, 1, max_write_delay_ms); 174 | if (nw == -1) { 175 | if (new_fileno != -1) { 176 | checked_close(new_fileno); 177 | } 178 | 179 | if (errno == EINTR) { 180 | goto retry_poll; 181 | } 182 | printf("Poll error\n"); 183 | break; 184 | } else if (nw == 1) { 185 | /* Send message */ 186 | struct iovec the_iovec; 187 | the_iovec.iov_len = packet_size * 4; 188 | the_iovec.iov_base = (char *)&data[cursor]; 189 | struct msghdr msg; 190 | msg.msg_name = NULL; 191 | msg.msg_namelen = 0; 192 | msg.msg_iov = &the_iovec; 193 | msg.msg_iovlen = 1; 194 | msg.msg_control = NULL; 195 | msg.msg_controllen = 0; 196 | msg.msg_flags = 0; 197 | 198 | union { 199 | char buf[CMSG_SPACE(sizeof(int))]; 200 | struct cmsghdr align; 201 | } uc; 202 | memset(uc.buf, 0, sizeof(uc.buf)); 203 | 204 | if (new_fileno != -1) { 205 | msg.msg_control = uc.buf; 206 | msg.msg_controllen = sizeof(uc.buf); 207 | struct cmsghdr *frst = CMSG_FIRSTHDR(&msg); 208 | frst->cmsg_level = SOL_SOCKET; 209 | frst->cmsg_type = SCM_RIGHTS; 210 | memcpy(CMSG_DATA(frst), &new_fileno, 211 | sizeof(int)); 212 | frst->cmsg_len = CMSG_LEN(sizeof(int)); 213 | msg.msg_controllen = CMSG_SPACE(sizeof(int)); 214 | } 215 | 216 | int target_fd = to_server ? srv_fds[0] : cli_fds[0]; 217 | ssize_t ret = sendmsg(target_fd, &msg, 0); 218 | if (ret == -1) { 219 | wp_error("Error in sendmsg"); 220 | break; 221 | } 222 | } else { 223 | wp_error("Failed to send message before timeout"); 224 | } 225 | if (new_fileno != -1) { 226 | checked_close(new_fileno); 227 | } 228 | 229 | /* Wait up to max_delay for a response. Almost all packets 230 | * should be passed on unmodified; a very small fraction 231 | * are dropped */ 232 | struct pollfd read_pfds[2]; 233 | read_pfds[0].fd = srv_fds[0]; 234 | read_pfds[1].fd = cli_fds[0]; 235 | read_pfds[0].events = POLLIN; 236 | read_pfds[1].events = POLLIN; 237 | int nr = poll(read_pfds, 2, 238 | packet_size > 0 ? max_read_delay_ms : 0); 239 | if (nr == -1) { 240 | if (errno == EINTR) { 241 | continue; 242 | } 243 | printf("Poll error\n"); 244 | break; 245 | } else if (nr == 0) { 246 | wp_debug("No reply to sent packet %d", packet_size); 247 | } 248 | for (int i = 0; i < 2; i++) { 249 | if (read_pfds[i].revents & POLLIN) { 250 | char cmsgdata[(CMSG_LEN(28 * sizeof(int32_t)))]; 251 | struct iovec the_iovec; 252 | the_iovec.iov_len = 65536; 253 | the_iovec.iov_base = ignore_buf; 254 | struct msghdr msg; 255 | msg.msg_name = NULL; 256 | msg.msg_namelen = 0; 257 | msg.msg_iov = &the_iovec; 258 | msg.msg_iovlen = 1; 259 | msg.msg_control = &cmsgdata; 260 | msg.msg_controllen = sizeof(cmsgdata); 261 | msg.msg_flags = 0; 262 | ssize_t ret = recvmsg(read_pfds[i].fd, &msg, 0); 263 | if (ret == -1) { 264 | wp_error("Error in recvmsg"); 265 | } 266 | } 267 | } 268 | 269 | cursor += packet_size; 270 | } 271 | checked_close(srv_fds[0]); 272 | checked_close(cli_fds[0]); 273 | 274 | pthread_join(thread_a, NULL); 275 | pthread_join(thread_b, NULL); 276 | 277 | free(buf); 278 | free(ignore_buf); 279 | return EXIT_SUCCESS; 280 | } 281 | -------------------------------------------------------------------------------- /test/startup_failure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Verifying all the ways in which waypipe can fail before even making a connection. 5 | """ 6 | 7 | if __name__ != "__main__": 8 | quit(1) 9 | 10 | import os, subprocess, time, signal, socket 11 | 12 | 13 | def try_unlink(path): 14 | try: 15 | os.unlink(path) 16 | except FileNotFoundError: 17 | pass 18 | 19 | 20 | def make_socket(path): 21 | folder, filename = os.path.split(path) 22 | cwdir = os.open(".", os.O_RDONLY | os.O_DIRECTORY) 23 | 24 | display_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 25 | os.chdir(folder) 26 | display_socket.bind(filename) 27 | display_socket.listen() 28 | os.fchdir(cwdir) 29 | os.close(cwdir) 30 | 31 | return display_socket 32 | 33 | 34 | waypipe_path = os.environ["TEST_WAYPIPE_PATH"] 35 | sleep_path = os.environ["TEST_SLEEP_PATH"] 36 | fake_ssh_path = os.environ["TEST_FAKE_SSH_PATH"] 37 | ld_library_path = ( 38 | os.environ["LD_LIBRARY_PATH"] if "LD_LIBRARY_PATH" in os.environ else "" 39 | ) 40 | 41 | xdg_runtime_dir = os.path.abspath("./run/") 42 | os.makedirs(xdg_runtime_dir, mode=0o700, exist_ok=True) 43 | os.chmod(xdg_runtime_dir, 0o700) 44 | 45 | all_succeeding = True 46 | 47 | wayland_display = "wayland-display" 48 | client_socket_path = xdg_runtime_dir + "/client-socket" 49 | server_socket_path = xdg_runtime_dir + "/server-socket" 50 | ssh_socket_path = xdg_runtime_dir + "/ssh-socket" 51 | wayland_display_path = xdg_runtime_dir + "/" + wayland_display 52 | 53 | try_unlink(wayland_display_path) 54 | display_socket = make_socket(wayland_display_path) 55 | 56 | USE_SOCKETPAIR = 1 << 1 57 | EXPECT_SUCCESS = 1 << 2 58 | EXPECT_TIMEOUT = 1 << 3 59 | EXPECT_FAILURE = 1 << 4 60 | 61 | 62 | def run_test(name, command, env, flags): 63 | try_unlink(client_socket_path) 64 | try_unlink(server_socket_path) 65 | try_unlink(server_socket_path + ".disp.sock") 66 | if flags & USE_SOCKETPAIR: 67 | sockets = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) 68 | conn_socket = 999 69 | os.dup2(sockets[1].fileno(), conn_socket, inheritable=True) 70 | env = dict(env, WAYLAND_SOCKET=str(conn_socket)) 71 | pfds = [conn_socket] 72 | else: 73 | pfds = [] 74 | 75 | timed_out = False 76 | log_path = os.path.join(xdg_runtime_dir, "sfail_{}.txt".format(name)) 77 | logfile = open(log_path, "wb") 78 | print(env, " ".join(command)) 79 | 80 | proc = subprocess.Popen( 81 | command, 82 | env=env, 83 | stdin=subprocess.DEVNULL, 84 | stdout=logfile, 85 | stderr=subprocess.STDOUT, 86 | pass_fds=pfds, 87 | start_new_session=True, 88 | ) 89 | try: 90 | output, none = proc.communicate(timeout=1.0) 91 | except subprocess.TimeoutExpired as e: 92 | # Program is waiting indefinitely for something. 93 | # Kill it, and all children. 94 | pgrp = os.getpgid(proc.pid) 95 | os.killpg(pgrp, signal.SIGKILL) 96 | retcode = None 97 | timed_out = True 98 | else: 99 | retcode = proc.returncode 100 | 101 | logfile.close() 102 | output = open(log_path, "rb").read() 103 | 104 | if flags & USE_SOCKETPAIR: 105 | os.close(conn_socket) 106 | 107 | log_path = os.path.join(xdg_runtime_dir, "weston_out.txt") 108 | with open(log_path, "wb") as out: 109 | out.write(output) 110 | 111 | result = ( 112 | "timeout" 113 | if timed_out 114 | else ("fail({})".format(retcode) if retcode != 0 else "pass") 115 | ) 116 | 117 | global all_succeeding 118 | if flags & EXPECT_SUCCESS: 119 | if timed_out or retcode != 0: 120 | print( 121 | "Run {} failed when it should have succeeded".format(name), 122 | output, 123 | retcode, 124 | "timeout" if timed_out else "notimeout", 125 | ) 126 | all_succeeding = False 127 | else: 128 | print("Run {} passed.".format(name), output) 129 | elif flags & EXPECT_FAILURE: 130 | if timed_out or retcode == 0: 131 | print( 132 | "Run {} succeeded when it should have failed".format(name), 133 | output, 134 | retcode, 135 | "timeout" if timed_out else "notimeout", 136 | ) 137 | all_succeeding = False 138 | else: 139 | print("Run {} passed.".format(name), output) 140 | elif flags & EXPECT_TIMEOUT: 141 | if not timed_out: 142 | print( 143 | "Run {} stopped when it should have continued".format(name), 144 | output, 145 | retcode, 146 | ) 147 | all_succeeding = False 148 | else: 149 | print("Run {} passed.".format(name), output) 150 | else: 151 | raise NotImplementedError 152 | 153 | 154 | wait_cmd = [sleep_path, "10.0"] 155 | invalid_hostname = "@" 156 | fake_ssh_dir = os.path.dirname(fake_ssh_path) 157 | waypipe_dir = os.path.dirname(waypipe_path) 158 | 159 | base_env = {"LD_LIBRARY_PATH": ld_library_path, "PATH": ""} 160 | standard_env = dict(base_env, XDG_RUNTIME_DIR=xdg_runtime_dir) 161 | ssh_only_env = dict(standard_env, PATH=fake_ssh_dir) 162 | ssh_env = dict(standard_env, PATH=fake_ssh_dir + ":" + waypipe_dir) 163 | # Configurations that should fail 164 | run_test( 165 | "b_client_long_disp", 166 | [waypipe_path, "-s", client_socket_path, "client"], 167 | dict(base_env, WAYLAND_DISPLAY=("/" + "x" * 107)), 168 | EXPECT_FAILURE, 169 | ) 170 | run_test( 171 | "b_client_disp_dne", 172 | [waypipe_path, "-s", client_socket_path, "client"], 173 | dict(base_env, WAYLAND_DISPLAY=xdg_runtime_dir + "/dne"), 174 | EXPECT_FAILURE, 175 | ) 176 | run_test( 177 | "b_client_no_env", 178 | [waypipe_path, "-s", client_socket_path, "client"], 179 | base_env, 180 | EXPECT_FAILURE, 181 | ) 182 | run_test( 183 | "b_server_oneshot_no_env", 184 | [waypipe_path, "-o", "-s", server_socket_path, "server"] + wait_cmd, 185 | base_env, 186 | EXPECT_TIMEOUT, 187 | ) 188 | run_test( 189 | "b_client_bad_pipe1", 190 | [waypipe_path, "-s", client_socket_path, "client"], 191 | dict(base_env, WAYLAND_SOCKET="33"), 192 | EXPECT_FAILURE, 193 | ) 194 | run_test( 195 | "b_client_bad_pipe2", 196 | [waypipe_path, "-s", client_socket_path, "client"], 197 | dict(base_env, WAYLAND_SOCKET="777777777777777777777777777"), 198 | EXPECT_FAILURE, 199 | ) 200 | run_test( 201 | "b_client_bad_pipe3", 202 | [waypipe_path, "-s", client_socket_path, "client"], 203 | dict(base_env, WAYLAND_SOCKET="0x33"), 204 | EXPECT_FAILURE, 205 | ) 206 | run_test( 207 | "b_client_nxdg_offset", 208 | [waypipe_path, "-s", client_socket_path, "client"], 209 | dict(base_env, WAYLAND_DISPLAY=wayland_display), 210 | EXPECT_FAILURE, 211 | ) 212 | run_test( 213 | "b_server_no_env", 214 | [waypipe_path, "-s", server_socket_path, "server"] + wait_cmd, 215 | base_env, 216 | EXPECT_FAILURE, 217 | ) 218 | run_test( 219 | "g_ssh_test_nossh_env", 220 | [waypipe_path, "-o", "-s", ssh_socket_path, "ssh", invalid_hostname] + wait_cmd, 221 | dict(standard_env, WAYLAND_DISPLAY=wayland_display), 222 | EXPECT_FAILURE, 223 | ) 224 | 225 | 226 | # Configurations that should succeed 227 | run_test( 228 | "g_help", 229 | [waypipe_path, "--help"], 230 | base_env, 231 | EXPECT_SUCCESS, 232 | ) 233 | run_test( 234 | "g_server_std_env", 235 | [waypipe_path, "-s", server_socket_path, "server"] + wait_cmd, 236 | standard_env, 237 | EXPECT_TIMEOUT, 238 | ) 239 | run_test( 240 | "g_client_std_env", 241 | [waypipe_path, "-s", client_socket_path, "client"], 242 | dict(standard_env, WAYLAND_DISPLAY=wayland_display_path), 243 | EXPECT_TIMEOUT, 244 | ) 245 | run_test( 246 | "g_client_offset_sock", 247 | [waypipe_path, "-s", client_socket_path, "client"], 248 | dict(standard_env, WAYLAND_DISPLAY=wayland_display), 249 | EXPECT_TIMEOUT, 250 | ) 251 | run_test( 252 | "g_client_pipe_env", 253 | [waypipe_path, "-s", client_socket_path, "client"], 254 | dict(standard_env), 255 | EXPECT_TIMEOUT | USE_SOCKETPAIR, 256 | ) 257 | run_test( 258 | "g_ssh_test_oneshot", 259 | [waypipe_path, "-o", "-s", ssh_socket_path, "ssh", invalid_hostname] + wait_cmd, 260 | dict(ssh_env, WAYLAND_DISPLAY=wayland_display), 261 | EXPECT_TIMEOUT, 262 | ) 263 | run_test( 264 | "g_ssh_test_reg", 265 | [waypipe_path, "-s", ssh_socket_path, "ssh", invalid_hostname] + wait_cmd, 266 | dict(ssh_env, WAYLAND_DISPLAY=wayland_display), 267 | EXPECT_TIMEOUT, 268 | ) 269 | run_test( 270 | "g_ssh_test_remotebin", 271 | [ 272 | waypipe_path, 273 | "--oneshot", 274 | "--remote-bin", 275 | waypipe_path, 276 | "-s", 277 | ssh_socket_path, 278 | "ssh", 279 | invalid_hostname, 280 | ] 281 | + wait_cmd, 282 | dict(ssh_only_env, WAYLAND_DISPLAY=wayland_display), 283 | EXPECT_TIMEOUT, 284 | ) 285 | 286 | try_unlink(client_socket_path) 287 | try_unlink(wayland_display_path) 288 | quit(0 if all_succeeding else 1) 289 | -------------------------------------------------------------------------------- /src/interval.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include "shadow.h" 27 | 28 | #include 29 | #include 30 | 31 | struct merge_stack_elem { 32 | int offset; 33 | int count; 34 | }; 35 | struct merge_stack { 36 | struct interval *data; 37 | int size; 38 | int count; 39 | }; 40 | 41 | static int stream_merge(int a_count, const struct interval *__restrict__ a_list, 42 | int b_count, const struct interval *__restrict__ b_list, 43 | struct interval *__restrict__ c_list, int margin) 44 | { 45 | int ia = 0, ib = 0, ic = 0; 46 | int cursor = INT32_MIN; 47 | (void)a_count; 48 | (void)b_count; 49 | 50 | /* the loop exit condition appears to be faster than checking 51 | * ia= cursor + margin) { 68 | c_list[ic++] = sel; 69 | } else { 70 | c_list[ic - 1].end = new_cursor; 71 | } 72 | cursor = new_cursor; 73 | } 74 | 75 | /* add end sentinel */ 76 | c_list[ic] = (struct interval){.start = INT32_MAX, .end = INT32_MAX}; 77 | 78 | return ic; 79 | } 80 | 81 | static int fix_merge_stack_property(int size, struct merge_stack_elem *stack, 82 | struct merge_stack *base, struct merge_stack *temp, 83 | int merge_margin, bool force_compact, int *absorbed) 84 | { 85 | while (size > 1) { 86 | struct merge_stack_elem top = stack[size - 1]; 87 | struct merge_stack_elem nxt = stack[size - 2]; 88 | 89 | if (2 * top.count <= nxt.count && !force_compact) { 90 | return size; 91 | } 92 | 93 | if (buf_ensure_size(top.count + nxt.count + 1, 94 | sizeof(struct interval), &temp->size, 95 | (void **)&temp->data) == -1) { 96 | wp_error("Failed to resize a merge buffer, some damage intervals may be lost"); 97 | return size; 98 | } 99 | 100 | int xs = stream_merge(top.count, &base->data[top.offset], 101 | nxt.count, &base->data[nxt.offset], temp->data, 102 | merge_margin); 103 | /* There are more complicated/multi-buffer alternatives with 104 | * fewer memory copies, but this is already <20% of stream 105 | * merge time */ 106 | memcpy(&base->data[nxt.offset], temp->data, 107 | (size_t)(xs + 1) * sizeof(struct interval)); 108 | base->count = nxt.offset + xs + 1; 109 | 110 | stack[size - 1] = (struct merge_stack_elem){ 111 | .offset = 0, .count = 0}; 112 | stack[size - 2] = (struct merge_stack_elem){ 113 | .offset = nxt.offset, .count = xs}; 114 | size--; 115 | 116 | *absorbed += (top.count + nxt.count - xs); 117 | } 118 | return size; 119 | } 120 | 121 | static int unpack_ext_interval(struct interval *vec, 122 | const struct ext_interval e, int alignment_bits) 123 | { 124 | int iw = 0; 125 | int last_end = INT32_MIN; 126 | for (int ir = 0; ir < e.rep; ir++) { 127 | int start = e.start + ir * e.stride; 128 | int end = start + e.width; 129 | start = (start >> alignment_bits) << alignment_bits; 130 | end = ((end + (1 << alignment_bits) - 1) >> alignment_bits) 131 | << alignment_bits; 132 | 133 | if (start > last_end) { 134 | vec[iw].start = start; 135 | vec[iw].end = end; 136 | last_end = end; 137 | iw++; 138 | } else { 139 | vec[iw - 1].end = end; 140 | last_end = end; 141 | } 142 | } 143 | /* end sentinel */ 144 | vec[iw] = (struct interval){.start = INT32_MAX, .end = INT32_MAX}; 145 | return iw; 146 | } 147 | 148 | /* By writing a mergesort by hand, we can detect duplicates early. 149 | * 150 | * TODO: optimize output with run-length-encoded segments 151 | * TODO: explicit time limiting/adaptive margin! */ 152 | void merge_mergesort(const int old_count, struct interval *old_list, 153 | const int new_count, const struct ext_interval *const new_list, 154 | int *dst_count, struct interval **dst_list, int merge_margin, 155 | int alignment_bits) 156 | { 157 | /* Stack-based mergesort: the buffer at position `i+1` 158 | * should be <= 1/2 times the size of the buffer at 159 | * position `i`; buffers will be merged 160 | * to maintain this invariant */ 161 | // TODO: improve memory management! 162 | struct merge_stack_elem substack[32]; 163 | int substack_size = 0; 164 | memset(substack, 0, sizeof(substack)); 165 | struct merge_stack base = {.data = NULL, .count = 0, .size = 0}; 166 | struct merge_stack temp = {.data = NULL, .count = 0, .size = 0}; 167 | if (old_count) { 168 | /* seed the stack with the previous damage 169 | * interval list, 170 | * including trailing terminator */ 171 | base.data = old_list; 172 | base.size = old_count + 1; 173 | base.count = old_count + 1; 174 | substack[substack_size++] = (struct merge_stack_elem){ 175 | .offset = 0, .count = old_count}; 176 | } 177 | 178 | int src_count = 0, absorbed = 0; 179 | 180 | for (int jn = 0; jn < new_count; jn++) { 181 | struct ext_interval e = new_list[jn]; 182 | /* ignore invalid intervals -- also, if e.start 183 | * is close to INT32_MIN, the stream merge 184 | * breaks */ 185 | if (e.width <= 0 || e.rep <= 0 || e.start < 0) { 186 | continue; 187 | } 188 | 189 | /* To limit CPU time, if it is very likely that 190 | * an interval would be merged anyway, then 191 | * replace it with its containing interval. */ 192 | int remaining = src_count - absorbed; 193 | bool force_combine = (absorbed > 30000) || 194 | 10 * remaining < src_count; 195 | 196 | int64_t intv_end = e.start + e.stride * (int64_t)(e.rep - 1) + 197 | e.width; 198 | if (intv_end >= INT32_MAX) { 199 | /* overflow protection */ 200 | e.width = INT32_MAX - 1 - e.start; 201 | e.rep = 1; 202 | } 203 | /* Remove internal gaps are smaller than the 204 | * margin and hence 205 | * would need to be merged away anyway. */ 206 | if (e.width > e.stride - merge_margin || force_combine) { 207 | e.width = e.stride * (e.rep - 1) + e.width; 208 | e.rep = 1; 209 | } 210 | 211 | if (buf_ensure_size(base.count + e.rep + 1, 212 | sizeof(struct interval), &base.size, 213 | (void **)&base.data) == -1) { 214 | wp_error("Failed to resize a merge buffer, some damage intervals may be lost"); 215 | continue; 216 | } 217 | 218 | struct interval *vec = &base.data[base.count]; 219 | int iw = unpack_ext_interval(vec, e, alignment_bits); 220 | src_count += iw; 221 | 222 | substack[substack_size] = (struct merge_stack_elem){ 223 | .offset = base.count, .count = iw}; 224 | substack_size++; 225 | 226 | base.count += iw + 1; 227 | 228 | /* merge down the stack as far as possible */ 229 | substack_size = fix_merge_stack_property(substack_size, 230 | substack, &base, &temp, merge_margin, false, 231 | &absorbed); 232 | } 233 | 234 | /* collapse the stack into a final interval */ 235 | fix_merge_stack_property(substack_size, substack, &base, &temp, 236 | merge_margin, true, &absorbed); 237 | free(temp.data); 238 | 239 | *dst_list = base.data; 240 | *dst_count = substack[0].count; 241 | } 242 | 243 | /* This value must be larger than 8, or diffs will explode */ 244 | #define MERGE_MARGIN 256 245 | void merge_damage_records(struct damage *base, int nintervals, 246 | const struct ext_interval *const new_list, int alignment_bits) 247 | { 248 | for (int i = 0; i < nintervals; i++) { 249 | base->acc_damage_stat += new_list[i].width * new_list[i].rep; 250 | base->acc_count++; 251 | } 252 | 253 | // Fast return if there is nothing to do 254 | if (base->damage == DAMAGE_EVERYTHING || nintervals <= 0) { 255 | return; 256 | } 257 | if (nintervals >= (1 << 30) || base->ndamage_intvs >= (1 << 30)) { 258 | /* avoid overflow in merge routine; also would be cheaper to 259 | * damage everything at this point; */ 260 | damage_everything(base); 261 | return; 262 | } 263 | 264 | merge_mergesort(base->ndamage_intvs, base->damage, nintervals, new_list, 265 | &base->ndamage_intvs, &base->damage, MERGE_MARGIN, 266 | alignment_bits); 267 | } 268 | 269 | void reset_damage(struct damage *base) 270 | { 271 | if (base->damage != DAMAGE_EVERYTHING) { 272 | free(base->damage); 273 | } 274 | base->damage = NULL; 275 | base->ndamage_intvs = 0; 276 | base->acc_damage_stat = 0; 277 | base->acc_count = 0; 278 | } 279 | void damage_everything(struct damage *base) 280 | { 281 | if (base->damage != DAMAGE_EVERYTHING) { 282 | free(base->damage); 283 | } 284 | base->damage = DAMAGE_EVERYTHING; 285 | base->ndamage_intvs = 0; 286 | } 287 | -------------------------------------------------------------------------------- /protocols/wlr-export-dmabuf-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2018 Rostislav Pehlivanov 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice (including the next 14 | paragraph) shall be included in all copies or substantial portions of the 15 | Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | 26 | 27 | An interface to capture surfaces in an efficient way by exporting DMA-BUFs. 28 | 29 | Warning! The protocol described in this file is experimental and 30 | backward incompatible changes may be made. Backward compatible changes 31 | may be added together with the corresponding interface version bump. 32 | Backward incompatible changes are done by bumping the version number in 33 | the protocol and interface names and resetting the interface version. 34 | Once the protocol is to be declared stable, the 'z' prefix and the 35 | version number in the protocol and interface names are removed and the 36 | interface version number is reset. 37 | 38 | 39 | 40 | 41 | This object is a manager with which to start capturing from sources. 42 | 43 | 44 | 45 | 46 | Capture the next frame of an entire output. 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | All objects created by the manager will still remain valid, until their 57 | appropriate destroy request has been called. 58 | 59 | 60 | 61 | 62 | 63 | 64 | This object represents a single DMA-BUF frame. 65 | 66 | If the capture is successful, the compositor will first send a "frame" 67 | event, followed by one or several "object". When the frame is available 68 | for readout, the "ready" event is sent. 69 | 70 | If the capture failed, the "cancel" event is sent. This can happen anytime 71 | before the "ready" event. 72 | 73 | Once either a "ready" or a "cancel" event is received, the client should 74 | destroy the frame. Once an "object" event is received, the client is 75 | responsible for closing the associated file descriptor. 76 | 77 | All frames are read-only and may not be written into or altered. 78 | 79 | 80 | 81 | 82 | Special flags that should be respected by the client. 83 | 84 | 86 | 87 | 88 | 89 | 90 | Main event supplying the client with information about the frame. If the 91 | capture didn't fail, this event is always emitted first before any other 92 | events. 93 | 94 | This event is followed by a number of "object" as specified by the 95 | "num_objects" argument. 96 | 97 | 99 | 101 | 103 | 105 | 108 | 110 | 112 | 114 | 116 | 118 | 119 | 120 | 121 | 122 | Event which serves to supply the client with the file descriptors 123 | containing the data for each object. 124 | 125 | After receiving this event, the client must always close the file 126 | descriptor as soon as they're done with it and even if the frame fails. 127 | 128 | 130 | 132 | 134 | 136 | 138 | 140 | 141 | 142 | 143 | 144 | This event is sent as soon as the frame is presented, indicating it is 145 | available for reading. This event includes the time at which 146 | presentation happened at. 147 | 148 | The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, 149 | each component being an unsigned 32-bit value. Whole seconds are in 150 | tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, 151 | and the additional fractional part in tv_nsec as nanoseconds. Hence, 152 | for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part 153 | may have an arbitrary offset at start. 154 | 155 | After receiving this event, the client should destroy this object. 156 | 157 | 159 | 161 | 163 | 164 | 165 | 166 | 167 | Indicates reason for cancelling the frame. 168 | 169 | 171 | 173 | 175 | 176 | 177 | 178 | 179 | If the capture failed or if the frame is no longer valid after the 180 | "frame" event has been emitted, this event will be used to inform the 181 | client to scrap the frame. 182 | 183 | If the failure is temporary, the client may capture again the same 184 | source. If the failure is permanent, any further attempts to capture the 185 | same source will fail again. 186 | 187 | After receiving this event, the client should destroy this object. 188 | 189 | 191 | 192 | 193 | 194 | 195 | Unreferences the frame. This request must be called as soon as its no 196 | longer used. 197 | 198 | It can be called at any time by the client. The client will still have 199 | to close any FDs it has been given. 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/kernel.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019 Manuel Stoeckl 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice (including the 13 | * next paragraph) shall be included in all copies or substantial 14 | * portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include "kernel.h" 27 | #include "interval.h" 28 | #include "util.h" 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | static size_t run_interval_diff_C(const int diff_window_size, 37 | const void *__restrict__ imod, void *__restrict__ ibase, 38 | uint32_t *__restrict__ idiff, size_t i, const size_t i_end) 39 | { 40 | const uint64_t *__restrict__ mod = imod; 41 | uint64_t *__restrict__ base = ibase; 42 | uint64_t *__restrict__ diff = (uint64_t *__restrict__)idiff; 43 | 44 | /* we paper over gaps of a given window size, to avoid fine 45 | * grained context switches */ 46 | const size_t i_start = i; 47 | size_t dc = 0; 48 | uint64_t changed_val = i < i_end ? mod[i] : 0; 49 | uint64_t base_val = i < i_end ? base[i] : 0; 50 | i++; 51 | // Alternating scanners, ending with a mispredict each. 52 | bool clear_exit = false; 53 | while (i < i_end) { 54 | while (changed_val == base_val && i < i_end) { 55 | changed_val = mod[i]; 56 | base_val = base[i]; 57 | i++; 58 | } 59 | if (i == i_end) { 60 | /* it's possible that the last value actually; 61 | * see exit block */ 62 | clear_exit = true; 63 | break; 64 | } 65 | uint32_t *ctrl_blocks = (uint32_t *)&diff[dc++]; 66 | ctrl_blocks[0] = (uint32_t)((i - 1) * 2); 67 | diff[dc++] = changed_val; 68 | base[i - 1] = changed_val; 69 | // changed_val != base_val, difference occurs at early 70 | // index 71 | size_t nskip = 0; 72 | // we could only sentinel this assuming a tiny window 73 | // size 74 | while (i < i_end && nskip <= (size_t)diff_window_size / 2) { 75 | base_val = base[i]; 76 | changed_val = mod[i]; 77 | base[i] = changed_val; 78 | i++; 79 | diff[dc++] = changed_val; 80 | nskip++; 81 | nskip *= (base_val == changed_val); 82 | } 83 | dc -= nskip; 84 | ctrl_blocks[1] = (uint32_t)((i - nskip) * 2); 85 | /* our sentinel, at worst, causes overcopy by one. this 86 | * is fine 87 | */ 88 | } 89 | 90 | /* If only the last block changed */ 91 | if ((clear_exit || i_start + 1 == i_end) && changed_val != base_val) { 92 | uint32_t *ctrl_blocks = (uint32_t *)&diff[dc++]; 93 | ctrl_blocks[0] = (uint32_t)(i_end - 1) * 2; 94 | ctrl_blocks[1] = (uint32_t)i_end * 2; 95 | diff[dc++] = changed_val; 96 | base[i_end - 1] = changed_val; 97 | } 98 | return dc * 2; 99 | } 100 | 101 | #ifdef HAVE_AVX512F 102 | static bool avx512f_available(void) 103 | { 104 | return __builtin_cpu_supports("avx512f"); 105 | } 106 | size_t run_interval_diff_avx512f(const int diff_window_size, 107 | const void *__restrict__ imod, void *__restrict__ ibase, 108 | uint32_t *__restrict__ idiff, size_t i, const size_t i_end); 109 | #endif 110 | 111 | #ifdef HAVE_AVX2 112 | static bool avx2_available(void) { return __builtin_cpu_supports("avx2"); } 113 | size_t run_interval_diff_avx2(const int diff_window_size, 114 | const void *__restrict__ imod, void *__restrict__ ibase, 115 | uint32_t *__restrict__ idiff, size_t i, const size_t i_end); 116 | #endif 117 | 118 | #ifdef HAVE_NEON 119 | bool neon_available(void); // in platform.c 120 | size_t run_interval_diff_neon(const int diff_window_size, 121 | const void *__restrict__ imod, void *__restrict__ ibase, 122 | uint32_t *__restrict__ idiff, size_t i, const size_t i_end); 123 | #endif 124 | 125 | #ifdef HAVE_SSE3 126 | static bool sse3_available(void) { return __builtin_cpu_supports("sse3"); } 127 | size_t run_interval_diff_sse3(const int diff_window_size, 128 | const void *__restrict__ imod, void *__restrict__ ibase, 129 | uint32_t *__restrict__ idiff, size_t i, const size_t i_end); 130 | #endif 131 | 132 | interval_diff_fn_t get_diff_function(enum diff_type type, int *alignment_bits) 133 | { 134 | #ifdef HAVE_AVX512F 135 | if ((type == DIFF_FASTEST || type == DIFF_AVX512F) && 136 | avx512f_available()) { 137 | *alignment_bits = 6; 138 | return run_interval_diff_avx512f; 139 | } 140 | #endif 141 | #ifdef HAVE_AVX2 142 | if ((type == DIFF_FASTEST || type == DIFF_AVX2) && avx2_available()) { 143 | *alignment_bits = 6; 144 | return run_interval_diff_avx2; 145 | } 146 | #endif 147 | #ifdef HAVE_NEON 148 | if ((type == DIFF_FASTEST || type == DIFF_NEON) && neon_available()) { 149 | *alignment_bits = 4; 150 | return run_interval_diff_neon; 151 | } 152 | #endif 153 | #ifdef HAVE_SSE3 154 | if ((type == DIFF_FASTEST || type == DIFF_SSE3) && sse3_available()) { 155 | *alignment_bits = 5; 156 | return run_interval_diff_sse3; 157 | } 158 | #endif 159 | if ((type == DIFF_FASTEST || type == DIFF_C)) { 160 | *alignment_bits = 3; 161 | return run_interval_diff_C; 162 | } 163 | *alignment_bits = 0; 164 | return NULL; 165 | } 166 | 167 | /** Construct the main portion of a diff. The provided arguments should 168 | * be validated beforehand. All intervals, as well as the base/changed data 169 | * pointers, should be aligned to the alignment size associated with the 170 | * interval diff function */ 171 | size_t construct_diff_core(interval_diff_fn_t idiff_fn, int alignment_bits, 172 | const struct interval *__restrict__ damaged_intervals, 173 | int n_intervals, void *__restrict__ base, 174 | const void *__restrict__ changed, void *__restrict__ diff) 175 | { 176 | uint32_t *diff_blocks = (uint32_t *)diff; 177 | size_t cursor = 0; 178 | for (int i = 0; i < n_intervals; i++) { 179 | struct interval e = damaged_intervals[i]; 180 | size_t bend = (size_t)e.end >> alignment_bits; 181 | size_t bstart = (size_t)e.start >> alignment_bits; 182 | cursor += (*idiff_fn)(24, changed, base, diff_blocks + cursor, 183 | bstart, bend); 184 | } 185 | return cursor * sizeof(uint32_t); 186 | } 187 | size_t construct_diff_trailing(size_t size, int alignment_bits, 188 | char *__restrict__ base, const char *__restrict__ changed, 189 | char *__restrict__ diff) 190 | { 191 | size_t alignment = 1u << alignment_bits; 192 | size_t ntrailing = size % alignment; 193 | size_t offset = size - ntrailing; 194 | bool tail_change = false; 195 | if (ntrailing > 0) { 196 | for (size_t i = 0; i < ntrailing; i++) { 197 | tail_change |= base[offset + i] != changed[offset + i]; 198 | } 199 | } 200 | if (tail_change) { 201 | for (size_t i = 0; i < ntrailing; i++) { 202 | diff[i] = changed[offset + i]; 203 | base[offset + i] = changed[offset + i]; 204 | } 205 | return ntrailing; 206 | } 207 | return 0; 208 | } 209 | void apply_diff(size_t size, char *__restrict__ target1, 210 | char *__restrict__ target2, size_t diffsize, size_t ntrailing, 211 | const char *__restrict__ diff) 212 | { 213 | size_t nblocks = size / sizeof(uint32_t); 214 | size_t ndiffblocks = diffsize / sizeof(uint32_t); 215 | uint32_t *__restrict__ t1_blocks = (uint32_t *)target1; 216 | uint32_t *__restrict__ t2_blocks = (uint32_t *)target2; 217 | uint32_t *__restrict__ diff_blocks = (uint32_t *)diff; 218 | for (size_t i = 0; i < ndiffblocks;) { 219 | size_t nfrom = (size_t)diff_blocks[i]; 220 | size_t nto = (size_t)diff_blocks[i + 1]; 221 | size_t span = nto - nfrom; 222 | if (nto > nblocks || nfrom >= nto || 223 | i + (nto - nfrom) >= ndiffblocks) { 224 | wp_error("Invalid copy range [%zu,%zu) > %zu=nblocks or [%zu,%zu) > %zu=ndiffblocks", 225 | nfrom, nto, nblocks, i + 1, 226 | i + 1 + span, ndiffblocks); 227 | return; 228 | } 229 | memcpy(t1_blocks + nfrom, diff_blocks + i + 2, 230 | sizeof(uint32_t) * span); 231 | memcpy(t2_blocks + nfrom, diff_blocks + i + 2, 232 | sizeof(uint32_t) * span); 233 | i += span + 2; 234 | } 235 | if (ntrailing > 0) { 236 | size_t offset = size - ntrailing; 237 | for (size_t i = 0; i < ntrailing; i++) { 238 | target1[offset + i] = diff[diffsize + i]; 239 | target2[offset + i] = diff[diffsize + i]; 240 | } 241 | } 242 | } 243 | 244 | void stride_shifted_copy(char *dest, const char *src, size_t src_start, 245 | size_t copy_length, size_t row_length, size_t src_stride, 246 | size_t dst_stride) 247 | { 248 | size_t src_end = src_start + copy_length; 249 | size_t lrow = src_start / src_stride; 250 | size_t trow = src_end / src_stride; 251 | /* special case: inside a segment */ 252 | if (lrow == trow) { 253 | size_t cstart = src_start - lrow * src_stride; 254 | if (cstart < row_length) { 255 | size_t cend = src_end - trow * src_stride; 256 | cend = cend > row_length ? row_length : cend; 257 | memcpy(dest + dst_stride * lrow + cstart, 258 | src + src_start, cend - cstart); 259 | } 260 | return; 261 | } 262 | 263 | /* leading segment */ 264 | if (src_start > lrow * src_stride) { 265 | size_t igap = src_start - lrow * src_stride; 266 | if (igap < row_length) { 267 | memcpy(dest + dst_stride * lrow + igap, src + src_start, 268 | row_length - igap); 269 | } 270 | } 271 | 272 | /* main body */ 273 | size_t srow = (src_start + src_stride - 1) / src_stride; 274 | for (size_t i = srow; i < trow; i++) { 275 | memcpy(dest + dst_stride * i, src + src_stride * i, row_length); 276 | } 277 | 278 | /* trailing segment */ 279 | if (src_end > trow * src_stride) { 280 | size_t local = src_end - trow * src_stride; 281 | local = local > row_length ? row_length : local; 282 | memcpy(dest + dst_stride * trow, src + src_end - local, local); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /protocols/gtk-primary-selection.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2015, 2016 Red Hat 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice (including the next 14 | paragraph) shall be included in all copies or substantial portions of the 15 | Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | 26 | 27 | This protocol provides the ability to have a primary selection device to 28 | match that of the X server. This primary selection is a shortcut to the 29 | common clipboard selection, where text just needs to be selected in order 30 | to allow copying it elsewhere. The de facto way to perform this action 31 | is the middle mouse button, although it is not limited to this one. 32 | 33 | Clients wishing to honor primary selection should create a primary 34 | selection source and set it as the selection through 35 | wp_primary_selection_device.set_selection whenever the text selection 36 | changes. In order to minimize calls in pointer-driven text selection, 37 | it should happen only once after the operation finished. Similarly, 38 | a NULL source should be set when text is unselected. 39 | 40 | wp_primary_selection_offer objects are first announced through the 41 | wp_primary_selection_device.data_offer event. Immediately after this event, 42 | the primary data offer will emit wp_primary_selection_offer.offer events 43 | to let know of the mime types being offered. 44 | 45 | When the primary selection changes, the client with the keyboard focus 46 | will receive wp_primary_selection_device.selection events. Only the client 47 | with the keyboard focus will receive such events with a non-NULL 48 | wp_primary_selection_offer. Across keyboard focus changes, previously 49 | focused clients will receive wp_primary_selection_device.events with a 50 | NULL wp_primary_selection_offer. 51 | 52 | In order to request the primary selection data, the client must pass 53 | a recent serial pertaining to the press event that is triggering the 54 | operation, if the compositor deems the serial valid and recent, the 55 | wp_primary_selection_source.send event will happen in the other end 56 | to let the transfer begin. The client owning the primary selection 57 | should write the requested data, and close the file descriptor 58 | immediately. 59 | 60 | If the primary selection owner client disappeared during the transfer, 61 | the client reading the data will receive a 62 | wp_primary_selection_device.selection event with a NULL 63 | wp_primary_selection_offer, the client should take this as a hint 64 | to finish the reads related to the no longer existing offer. 65 | 66 | The primary selection owner should be checking for errors during 67 | writes, merely cancelling the ongoing transfer if any happened. 68 | 69 | 70 | 71 | 72 | The primary selection device manager is a singleton global object that 73 | provides access to the primary selection. It allows to create 74 | wp_primary_selection_source objects, as well as retrieving the per-seat 75 | wp_primary_selection_device objects. 76 | 77 | 78 | 79 | 80 | Create a new primary selection source. 81 | 82 | 83 | 84 | 85 | 86 | 87 | Create a new data device for a given seat. 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | Destroy the primary selection device manager. 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Replaces the current selection. The previous owner of the primary selection 104 | will receive a wp_primary_selection_source.cancelled event. 105 | 106 | To unset the selection, set the source to NULL. 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Introduces a new wp_primary_selection_offer object that may be used 115 | to receive the current primary selection. Immediately following this 116 | event, the new wp_primary_selection_offer object will send 117 | wp_primary_selection_offer.offer events to describe the offered mime 118 | types. 119 | 120 | 121 | 122 | 123 | 124 | 125 | The wp_primary_selection_device.selection event is sent to notify the 126 | client of a new primary selection. This event is sent after the 127 | wp_primary_selection.data_offer event introducing this object, and after 128 | the offer has announced its mimetypes through 129 | wp_primary_selection_offer.offer. 130 | 131 | The data_offer is valid until a new offer or NULL is received 132 | or until the client loses keyboard focus. The client must destroy the 133 | previous selection data_offer, if any, upon receiving this event. 134 | 135 | 136 | 137 | 138 | 139 | 140 | Destroy the primary selection device. 141 | 142 | 143 | 144 | 145 | 146 | 147 | A wp_primary_selection_offer represents an offer to transfer the contents 148 | of the primary selection clipboard to the client. Similar to 149 | wl_data_offer, the offer also describes the mime types that the source 150 | will transferthat the 151 | data can be converted to and provides the mechanisms for transferring the 152 | data directly to the client. 153 | 154 | 155 | 156 | 157 | To transfer the contents of the primary selection clipboard, the client 158 | issues this request and indicates the mime type that it wants to 159 | receive. The transfer happens through the passed file descriptor 160 | (typically created with the pipe system call). The source client writes 161 | the data in the mime type representation requested and then closes the 162 | file descriptor. 163 | 164 | The receiving client reads from the read end of the pipe until EOF and 165 | closes its end, at which point the transfer is complete. 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | Destroy the primary selection offer. 174 | 175 | 176 | 177 | 178 | 179 | Sent immediately after creating announcing the wp_primary_selection_offer 180 | through wp_primary_selection_device.data_offer. One event is sent per 181 | offered mime type. 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | The source side of a wp_primary_selection_offer, it provides a way to 190 | describe the offered data and respond to requests to transfer the 191 | requested contents of the primary selection clipboard. 192 | 193 | 194 | 195 | 196 | This request adds a mime type to the set of mime types advertised to 197 | targets. Can be called several times to offer multiple types. 198 | 199 | 200 | 201 | 202 | 203 | 204 | Destroy the primary selection source. 205 | 206 | 207 | 208 | 209 | 210 | Request for the current primary selection contents from the client. 211 | Send the specified mime type over the passed file descriptor, then 212 | close it. 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | This primary selection source is no longer valid. The client should 221 | clean up and destroy this primary selection source. 222 | 223 | 224 | 225 | 226 | --------------------------------------------------------------------------------