pax_global_header 0000666 0000000 0000000 00000000064 14414260423 0014512 g ustar 00root root 0000000 0000000 52 comment=033d0e7eb613f1ddfe104d07079a81b8d4e02dac
waypipe-v0.8.6/ 0000775 0000000 0000000 00000000000 14414260423 0013371 5 ustar 00root root 0000000 0000000 waypipe-v0.8.6/.clang-format 0000664 0000000 0000000 00000005360 14414260423 0015750 0 ustar 00root root 0000000 0000000 # Only including options for C only
Language: Cpp
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Linux
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 16
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 8
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp03
TabWidth: 8
UseTab: ForContinuationAndIndentation
waypipe-v0.8.6/.gitignore 0000664 0000000 0000000 00000000103 14414260423 0015353 0 ustar 00root root 0000000 0000000 waypipe
build/
Doxyfile
html
latex
doc
test/matrix
/build-minimal/
waypipe-v0.8.6/CONTRIBUTING.md 0000664 0000000 0000000 00000004254 14414260423 0015627 0 ustar 00root root 0000000 0000000 Contributing guidelines
===============================================================================
## Formatting
To avoid needless time spent formatting things, this project has autoformatting
set up. Yes, it's often ugly, but after using it long enough you'll forget that
code can look nice. Python scripts are formatted with black[0], and C code with
clang-format[1]. The script `autoformat.sh` at the root of the directory should
format all source code files in the project.
[0] https://github.com/python/black
[1] https://clang.llvm.org/docs/ClangFormat.html
## Types
* Typedefs should be used only for function signatures, and never applied to
structs.
* `short`, `long`, and `long long` should not be used, in favor of `int16_t`
and `int64_t`.
* All wire-format structures should use fixed size types. It's safe to assume
that buffers will never be larger than about 1 GB, so buffer sizes and
indices do not require 64 bit types when used in protocol message headers.
* `printf` should be called with the correct format codes. For example, `%zd`
for `ssize_t`, and the `PRIu32` macro for `uint32_t`.
* Avoid unnecessary casts.
## Comments
Explain precisely that which is not obvious. `/* ... */` is preferred to
`// ...` for longer comments; the leading `/*` and trailing `*/ do not need
lines of their own. Use Doxygen style (`/**`) for functions and structs that
need commenting, but not to the point where it hinders source code readability.
Waypipe is not a library.
## Memory and errors
All error conditions should be handled, including the errors produced by
allocation failures. (It is relatively easy to test for allocation failure
by `LD_PRELOAD`ing a library that redefines malloc et al.; see for instance
"mallocfail" and "failmalloc". `ulimit -v` may be less effective.)
Some errors are unrecoverable, and for those cases Waypipe should shut down
cleanly. For instance, if Waypipe cannot replicate a file descriptor, then an
application connected through it will almost certainly crash, and it's better
to have Waypipe exit instead. Other errors can safely ignored -- if fine
grained damage tracking fails, a sane fallback would be to assume that an
entire surface is damaged.
waypipe-v0.8.6/COPYING 0000664 0000000 0000000 00000002305 14414260423 0014424 0 ustar 00root root 0000000 0000000 Copyright © 2019 Manuel Stoeckl
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
---
The above is the version of the MIT "Expat" License used by X.org:
http://cgit.freedesktop.org/xorg/xserver/tree/COPYING
waypipe-v0.8.6/README.md 0000664 0000000 0000000 00000014611 14414260423 0014653 0 ustar 00root root 0000000 0000000 Waypipe
================================================================================
`waypipe` is a proxy for Wayland[0] clients. It forwards Wayland messages and
serializes changes to shared memory buffers over a single socket. This makes
application forwarding similar to `ssh -X` [1] feasible.
[0] [https://wayland.freedesktop.org/](https://wayland.freedesktop.org/)
[1] [https://wiki.archlinux.org/title/OpenSSH#X11_forwarding](https://wiki.archlinux.org/title/OpenSSH#X11_forwarding)
## Usage
`waypipe` should be installed on both the local and remote computers. There is
a user-friendly command line pattern which prefixes a call to `ssh` and
automatically sets up a reverse tunnel for protocol data. For example,
waypipe ssh user@theserver weston-terminal
will run `ssh`, connect to `theserver`, and remotely run `weston-terminal`,
using local and remote `waypipe` processes to synchronize the shared memory
buffers used by Wayland clients between both computers. Command line arguments
before `ssh` apply only to `waypipe`; those after `ssh` belong to `ssh`.
Alternatively, one can launch the local and remote processes by hand, with the
following set of shell commands:
/usr/bin/waypipe -s /tmp/socket-local client &
ssh -R /tmp/socket-remote:/tmp/socket-local -t user@theserver \
/usr/bin/waypipe -s /tmp/socket-remote server -- \
/usr/bin/weston-terminal
kill %1
It's possible to set up the local and remote processes so that, when the
connection between the the sockets used by each end breaks, one can create
a new forwarded socket on the remote side and reconnect the two processes.
For a more detailed example, see the man page.
## Installing
Build with meson[0]. A typical incantation is
cd /path/to/waypipe/ && cd ..
mkdir build-waypipe
meson --buildtype debugoptimized waypipe build-waypipe
ninja -C build-waypipe install
Core build requirements:
* meson (build, >= 0.47. with dependencies `ninja`, `pkg-config`, `python3`)
* C compiler
Optional dependencies:
* liblz4 (for fast compression, >=1.7.0)
* libzstd (for slower compression, >= 0.4.6)
* libgbm (to support programs using OpenGL via DMABUFs)
* libdrm (same as for libgbm)
* ffmpeg (>=3.1, needs avcodec/avutil/swscale for lossy video encoding)
* libva (for hardware video encoding and decoding)
* scdoc (to generate a man page)
* sys/sdt.h (to provide static tracepoints for profiling)
* ssh (runtime, OpenSSH >= 6.7, for Unix domain socket forwarding)
* libx264 (ffmpeg runtime, for software video decoding and encoding)
[0] [https://mesonbuild.com/](https://mesonbuild.com/)
[1] [https://git.sr.ht/~sircmpwn/scdoc](https://git.sr.ht/~sircmpwn/scdoc)
## Reporting issues
Waypipe is developed at [0]; file bug reports or submit patches here.
In general, if a program does not work properly under Waypipe, it is a bug
worth reporting. If possible, before doing so ensure both computers are using
the most recently released version of Waypipe (or are built from git master).
A workaround that may help for some programs using OpenGL or Vulkan is to
run Waypipe with the `--no-gpu` flag, which may force them to use software
rendering and shared memory buffers. (Please still file a bug.)
Some programs may require specific environment variable settings or command
line flags to run remotely; a few examples are given in the man page[1].
Useful information for bug reports includes:
* If a Waypipe process has crashed on either end of the connection,
a full stack trace, with debug symbols. (In gdb, `bt full`).
* If the program uses OpenGL or Vulkan, the graphics cards and drivers on
both computers.
* The output of `waypipe --version` on both ends of the connection
* Logs when Waypipe is run with the `--debug` flag, or when the program
is run with the environment variable setting `WAYLAND_DEBUG=1`.
* Screenshots of any visual glitches.
[0] [https://gitlab.freedesktop.org/mstoeckl/waypipe/](https://gitlab.freedesktop.org/mstoeckl/waypipe/)
[1] [https://gitlab.freedesktop.org/mstoeckl/waypipe/-/blob/master/waypipe.scd](https://gitlab.freedesktop.org/mstoeckl/waypipe/-/blob/master/waypipe.scd)
## Technical Limitations
Waypipe does not have a full view of the Wayland protocol. It includes a
compiled form of the base protocol and several extension protocols, but is not
able to parse all messages that the programs it connects send. Fortunately, the
Wayland wire protocol is partially self-describing, so Waypipe can parse the
messages it needs (those related to resources shared with file descriptors)
while ignoring the rest. This makes Waypipe partially forward-compatible: if a
future protocol comes out about details (for example, about window positioning)
which do not require that file descriptors be sent, then applications will be
able to use that protocol even with older versions of Waypipe. The
tradeoff to allowing messages that Waypipe can not parse is that Waypipe can
only make minor modifications to the wire protocol. In particular, adding or
removing any Wayland protocol objects would require changing all messages that
refer to them, including those messages that Waypipe does not parse. This
precludes, for example, global object deduplication tricks that could reduce
startup time for complicated applications.
Shared memory buffer updates, including those for the contents of windows, are
tracked by keeping a "mirror" copy of the buffer the represents the view which
the opposing instance of Waypipe has. This way, Waypipe can send only the
regions of the buffer that have changed relative to the remote copy. This is
more efficient than resending the entire buffer on every update, which is good
for applications with reasonably static user interfaces (like a text editor or
email client). However, with programs with animations where the interaction
latency matters (like games or certain audio tools), major window updates will
unavoidably produce a lag spike. The additional memory cost of keeping mirrors
is moderate.
The video encoding option for DMABUFs currently maintains a video stream for
each buffer that is used by a window surface. Since surfaces typically rotate
between a small number of buffers, a video encoded window will appear to
flicker as it switches rapidly between the underlying buffers, each of whose
video streams has different encoding artifacts.
The `zwp_linux_explicit_synchronization_v1` Wayland protocol is currently not
supported.
Waypipe does not work between computers that use different byte orders.
waypipe-v0.8.6/autoformat.sh 0000775 0000000 0000000 00000000206 14414260423 0016107 0 ustar 00root root 0000000 0000000 #!/bin/sh
set -e
black -q test/*.py protocols/*.py
clang-format -style=file --assume-filename=C -i src/*.h src/*.c test/*.c test/*.h
waypipe-v0.8.6/meson.build 0000664 0000000 0000000 00000007565 14414260423 0015550 0 ustar 00root root 0000000 0000000 project(
'waypipe',
'c',
license: 'MIT/Expat',
meson_version: '>=0.47.0',
default_options: [
'c_std=c11',
'warning_level=3',
'werror=true',
],
version: '0.8.6',
)
# DEFAULT_SOURCE implies POSIX_C_SOURCE 200809L + extras like CMSG_LEN
# requires glibc >= 4.19 (2014), freebsd libc (since 2016?), musl >= 1.15 (2014)
add_project_arguments('-D_DEFAULT_SOURCE', language: 'c')
# Sometimes ignoring the result of read()/write() is the right thing to do
add_project_arguments('-Wno-unused-result', language: 'c')
cc = meson.get_compiler('c')
config_data = configuration_data()
# mention version
version = '"@0@"'.format(meson.project_version())
git = find_program('git', native: true, required: false)
if git.found()
dir_arg = '--git-dir=@0@/.git'.format(meson.source_root())
commit = run_command([git, dir_arg, 'rev-parse', '--verify', '-q', 'HEAD'])
if commit.returncode() == 0
version = '"@0@ (commit @1@)"'.format(meson.project_version(), commit.stdout().strip())
endif
endif
config_data.set('WAYPIPE_VERSION', version)
# Make build reproducible if possible
python3 = import('python').find_installation()
prefix_finder = 'import os.path; print(os.path.join(os.path.relpath(\'@0@\', \'@1@\'),\'\'))'
r = run_command(python3, '-c', prefix_finder.format(meson.source_root(), meson.build_root()))
relative_dir = r.stdout().strip()
if cc.has_argument('-fmacro-prefix-map=/prefix/to/hide=')
add_project_arguments(
'-fmacro-prefix-map=@0@='.format(relative_dir),
language: 'c',
)
else
add_project_arguments(
'-DWAYPIPE_REL_SRC_DIR="@0@"'.format(relative_dir),
language: 'c',
)
endif
libgbm = dependency('gbm', required: get_option('with_dmabuf'))
libdrm = dependency('libdrm', required: get_option('with_dmabuf'))
if libgbm.found() and libdrm.found()
config_data.set('HAS_DMABUF', 1, description: 'Support DMABUF replication')
has_dmabuf = true
else
has_dmabuf = false
endif
pthreads = dependency('threads')
rt = cc.find_library('rt')
# XXX dtrace -G (Solaris, FreeBSD, NetBSD) isn't supported yet
is_linux = host_machine.system() == 'linux'
is_darwin = host_machine.system() == 'darwin'
if (is_linux or is_darwin) and get_option('with_systemtap') and cc.has_header('sys/sdt.h')
config_data.set('HAS_USDT', 1, description: 'Enable static trace probes')
endif
liblz4 = dependency('liblz4', version: '>=1.7.0', required: get_option('with_lz4'))
if liblz4.found()
config_data.set('HAS_LZ4', 1, description: 'Enable LZ4 compression')
endif
libzstd = dependency('libzstd', version: '>=0.4.6', required: get_option('with_zstd'))
if libzstd.found()
config_data.set('HAS_ZSTD', 1, description: 'Enable Zstd compression')
endif
libavcodec = dependency('libavcodec', required: get_option('with_video'))
libavutil = dependency('libavutil', required: get_option('with_video'))
libswscale = dependency('libswscale', required: get_option('with_video'))
libva = dependency('libva', required: get_option('with_vaapi'))
if libavcodec.found() and libavutil.found() and libswscale.found()
config_data.set('HAS_VIDEO', 1, description: 'Enable video (de)compression')
if libva.found()
config_data.set('HAS_VAAPI', 1, description: 'Enable hardware video (de)compression with VAAPI')
endif
endif
waypipe_includes = [include_directories('protocols'), include_directories('src')]
if libdrm.found()
waypipe_includes += include_directories(libdrm.get_pkgconfig_variable('includedir'))
endif
subdir('protocols')
subdir('src')
subdir('test')
scdoc = dependency('scdoc', version: '>=1.9.4', native: true, required: get_option('man-pages'))
if scdoc.found()
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
sh = find_program('sh', native: true)
mandir = get_option('mandir')
custom_target(
'waypipe.1',
input: 'waypipe.scd',
output: 'waypipe.1',
command: [
sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), 'waypipe.1')
],
install: true,
install_dir: '@0@/man1'.format(mandir)
)
endif
waypipe-v0.8.6/meson_options.txt 0000664 0000000 0000000 00000003030 14414260423 0017022 0 ustar 00root root 0000000 0000000 option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
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')
option('with_dmabuf', type : 'feature', value : 'auto', description : 'Support DMABUFs, the file descriptors used to exchange data for e.g. OpenGL applications')
option('with_lz4', type : 'feature', value : 'auto', description : 'Support LZ4 as a compression mechanism')
option('with_zstd', type : 'feature', value : 'auto', description : 'Support ZStandard as a compression mechanism')
option('with_vaapi', type : 'feature', value : 'auto', description : 'Link with libva and use VAAPI to perform hardware video output color space conversions on GPU')
option('with_systemtap', type: 'boolean', value: true, description: 'Enable tracing using sdt and provide static tracepoints for profiling')
# It is recommended to keep these on; Waypipe will automatically select the highest available instruction set at runtime
option('with_avx512f', type: 'boolean', value: true, description: 'Compile with support for AVX512f SIMD instructions')
option('with_avx2', type: 'boolean', value: true, description: 'Compile with support for AVX2 SIMD instructions')
option('with_sse3', type: 'boolean', value: true, description: 'Compile with support for SSE3 SIMD instructions')
option('with_neon_opts', type: 'boolean', value: true, description: 'Compile with support for ARM64 neon instructions')
waypipe-v0.8.6/minimal_build.sh 0000775 0000000 0000000 00000002002 14414260423 0016527 0 ustar 00root root 0000000 0000000 #!/bin/sh
set -e
echo "This script is a backup build system in case meson/ninja are unavailable."
echo "No optional features or optimizations are included. Waypipe will be slow."
echo "Requirements: python3, gcc, libc+pthreads"
echo "Enter to continue, interrupt to exit."
read unused
mkdir -p build-minimal
cd build-minimal
echo "Generating code..."
python3 ../protocols/symgen.py data ../protocols/function_list.txt protocols.c \
../protocols/*.xml
python3 ../protocols/symgen.py header ../protocols/function_list.txt protocols.h \
../protocols/*.xml
echo '#define WAYPIPE_VERSION "minimal"' > config-waypipe.h
echo "Compiling..."
gcc -D_DEFAULT_SOURCE -Os -I. -I../protocols/ -lpthread -o waypipe protocols.c \
../src/bench.c ../src/client.c ../src/dmabuf.c ../src/handlers.c \
../src/interval.c ../src/kernel.c ../src/mainloop.c ../src/parsing.c \
../src/platform.c ../src/server.c ../src/shadow.c ../src/util.c \
../src/video.c ../src/waypipe.c
cd ..
echo "Done. See ./build-minimal/waypipe"
waypipe-v0.8.6/protocols/ 0000775 0000000 0000000 00000000000 14414260423 0015415 5 ustar 00root root 0000000 0000000 waypipe-v0.8.6/protocols/function_list.txt 0000664 0000000 0000000 00000003257 14414260423 0021045 0 ustar 00root root 0000000 0000000 gtk_primary_selection_offer_req_receive
gtk_primary_selection_source_evt_send
wl_buffer_evt_release
wl_data_offer_req_receive
wl_data_source_evt_send
wl_display_evt_delete_id
wl_display_evt_error
wl_display_req_get_registry
wl_display_req_sync
wl_drm_evt_device
wl_drm_req_create_prime_buffer
wl_keyboard_evt_keymap
wl_registry_evt_global
wl_registry_evt_global_remove
wl_registry_req_bind
wl_shm_req_create_pool
wl_shm_pool_req_create_buffer
wl_shm_pool_req_resize
wl_surface_req_attach
wl_surface_req_commit
wl_surface_req_damage
wl_surface_req_damage_buffer
wl_surface_req_set_buffer_transform
wl_surface_req_set_buffer_scale
wp_presentation_evt_clock_id
wp_presentation_feedback_evt_presented
wp_presentation_req_feedback
zwlr_data_control_offer_v1_req_receive
zwlr_data_control_source_v1_evt_send
zwlr_export_dmabuf_frame_v1_evt_frame
zwlr_export_dmabuf_frame_v1_evt_object
zwlr_export_dmabuf_frame_v1_evt_ready
zwlr_gamma_control_v1_req_set_gamma
zwlr_screencopy_frame_v1_evt_ready
zwlr_screencopy_frame_v1_req_copy
zwp_linux_dmabuf_feedback_v1_evt_done
zwp_linux_dmabuf_feedback_v1_evt_format_table
zwp_linux_dmabuf_feedback_v1_evt_main_device
zwp_linux_dmabuf_feedback_v1_evt_tranche_done
zwp_linux_dmabuf_feedback_v1_evt_tranche_target_device
zwp_linux_dmabuf_feedback_v1_evt_tranche_formats
zwp_linux_dmabuf_feedback_v1_evt_tranche_flags
zwp_linux_buffer_params_v1_evt_created
zwp_linux_buffer_params_v1_req_add
zwp_linux_buffer_params_v1_req_create
zwp_linux_buffer_params_v1_req_create_immed
zwp_linux_dmabuf_v1_evt_modifier
zwp_linux_dmabuf_v1_req_get_default_feedback
zwp_linux_dmabuf_v1_req_get_surface_feedback
zwp_primary_selection_offer_v1_req_receive
zwp_primary_selection_source_v1_evt_send
waypipe-v0.8.6/protocols/gtk-primary-selection.xml 0000664 0000000 0000000 00000023711 14414260423 0022374 0 ustar 00root root 0000000 0000000
Copyright © 2015, 2016 Red Hat
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
This protocol provides the ability to have a primary selection device to
match that of the X server. This primary selection is a shortcut to the
common clipboard selection, where text just needs to be selected in order
to allow copying it elsewhere. The de facto way to perform this action
is the middle mouse button, although it is not limited to this one.
Clients wishing to honor primary selection should create a primary
selection source and set it as the selection through
wp_primary_selection_device.set_selection whenever the text selection
changes. In order to minimize calls in pointer-driven text selection,
it should happen only once after the operation finished. Similarly,
a NULL source should be set when text is unselected.
wp_primary_selection_offer objects are first announced through the
wp_primary_selection_device.data_offer event. Immediately after this event,
the primary data offer will emit wp_primary_selection_offer.offer events
to let know of the mime types being offered.
When the primary selection changes, the client with the keyboard focus
will receive wp_primary_selection_device.selection events. Only the client
with the keyboard focus will receive such events with a non-NULL
wp_primary_selection_offer. Across keyboard focus changes, previously
focused clients will receive wp_primary_selection_device.events with a
NULL wp_primary_selection_offer.
In order to request the primary selection data, the client must pass
a recent serial pertaining to the press event that is triggering the
operation, if the compositor deems the serial valid and recent, the
wp_primary_selection_source.send event will happen in the other end
to let the transfer begin. The client owning the primary selection
should write the requested data, and close the file descriptor
immediately.
If the primary selection owner client disappeared during the transfer,
the client reading the data will receive a
wp_primary_selection_device.selection event with a NULL
wp_primary_selection_offer, the client should take this as a hint
to finish the reads related to the no longer existing offer.
The primary selection owner should be checking for errors during
writes, merely cancelling the ongoing transfer if any happened.
The primary selection device manager is a singleton global object that
provides access to the primary selection. It allows to create
wp_primary_selection_source objects, as well as retrieving the per-seat
wp_primary_selection_device objects.
Create a new primary selection source.
Create a new data device for a given seat.
Destroy the primary selection device manager.
Replaces the current selection. The previous owner of the primary selection
will receive a wp_primary_selection_source.cancelled event.
To unset the selection, set the source to NULL.
Introduces a new wp_primary_selection_offer object that may be used
to receive the current primary selection. Immediately following this
event, the new wp_primary_selection_offer object will send
wp_primary_selection_offer.offer events to describe the offered mime
types.
The wp_primary_selection_device.selection event is sent to notify the
client of a new primary selection. This event is sent after the
wp_primary_selection.data_offer event introducing this object, and after
the offer has announced its mimetypes through
wp_primary_selection_offer.offer.
The data_offer is valid until a new offer or NULL is received
or until the client loses keyboard focus. The client must destroy the
previous selection data_offer, if any, upon receiving this event.
Destroy the primary selection device.
A wp_primary_selection_offer represents an offer to transfer the contents
of the primary selection clipboard to the client. Similar to
wl_data_offer, the offer also describes the mime types that the source
will transferthat the
data can be converted to and provides the mechanisms for transferring the
data directly to the client.
To transfer the contents of the primary selection clipboard, the client
issues this request and indicates the mime type that it wants to
receive. The transfer happens through the passed file descriptor
(typically created with the pipe system call). The source client writes
the data in the mime type representation requested and then closes the
file descriptor.
The receiving client reads from the read end of the pipe until EOF and
closes its end, at which point the transfer is complete.
Destroy the primary selection offer.
Sent immediately after creating announcing the wp_primary_selection_offer
through wp_primary_selection_device.data_offer. One event is sent per
offered mime type.
The source side of a wp_primary_selection_offer, it provides a way to
describe the offered data and respond to requests to transfer the
requested contents of the primary selection clipboard.
This request adds a mime type to the set of mime types advertised to
targets. Can be called several times to offer multiple types.
Destroy the primary selection source.
Request for the current primary selection contents from the client.
Send the specified mime type over the passed file descriptor, then
close it.
This primary selection source is no longer valid. The client should
clean up and destroy this primary selection source.
waypipe-v0.8.6/protocols/input-method-unstable-v2.xml 0000664 0000000 0000000 00000051552 14414260423 0022724 0 ustar 00root root 0000000 0000000
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2011 Intel Corporation
Copyright © 2012-2013 Collabora, Ltd.
Copyright © 2012, 2013 Intel Corporation
Copyright © 2015, 2016 Jan Arne Petersen
Copyright © 2017, 2018 Red Hat, Inc.
Copyright © 2018 Purism SPC
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
This protocol allows applications to act as input methods for compositors.
An input method context is used to manage the state of the input method.
Text strings are UTF-8 encoded, their indices and lengths are in bytes.
This document adheres to the RFC 2119 when using words like "must",
"should", "may", etc.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
An input method object allows for clients to compose text.
The objects connects the client to a text input in an application, and
lets the client to serve as an input method for a seat.
The zwp_input_method_v2 object can occupy two distinct states: active and
inactive. In the active state, the object is associated to and
communicates with a text input. In the inactive state, there is no
associated text input, and the only communication is with the compositor.
Initially, the input method is in the inactive state.
Requests issued in the inactive state must be accepted by the compositor.
Because of the serial mechanism, and the state reset on activate event,
they will not have any effect on the state of the next text input.
There must be no more than one input method object per seat.
Notification that a text input focused on this seat requested the input
method to be activated.
This event serves the purpose of providing the compositor with an
active input method.
This event resets all state associated with previous enable, disable,
surrounding_text, text_change_cause, and content_type events, as well
as the state associated with set_preedit_string, commit_string, and
delete_surrounding_text requests. In addition, it marks the
zwp_input_method_v2 object as active, and makes any existing
zwp_input_popup_surface_v2 objects visible.
The surrounding_text, and content_type events must follow before the
next done event if the text input supports the respective
functionality.
State set with this event is double-buffered. It will get applied on
the next zwp_input_method_v2.done event, and stay valid until changed.
Notification that no focused text input currently needs an active
input method on this seat.
This event marks the zwp_input_method_v2 object as inactive. The
compositor must make all existing zwp_input_popup_surface_v2 objects
invisible until the next activate event.
State set with this event is double-buffered. It will get applied on
the next zwp_input_method_v2.done event, and stay valid until changed.
Updates the surrounding plain text around the cursor, excluding the
preedit text.
If any preedit text is present, it is replaced with the cursor for the
purpose of this event.
The argument text is a buffer containing the preedit string, and must
include the cursor position, and the complete selection. It should
contain additional characters before and after these. There is a
maximum length of wayland messages, so text can not be longer than 4000
bytes.
cursor is the byte offset of the cursor within the text buffer.
anchor is the byte offset of the selection anchor within the text
buffer. If there is no selected text, anchor must be the same as
cursor.
If this event does not arrive before the first done event, the input
method may assume that the text input does not support this
functionality and ignore following surrounding_text events.
Values set with this event are double-buffered. They will get applied
and set to initial values on the next zwp_input_method_v2.done
event.
The initial state for affected fields is empty, meaning that the text
input does not support sending surrounding text. If the empty values
get applied, subsequent attempts to change them may have no effect.
Tells the input method why the text surrounding the cursor changed.
Whenever the client detects an external change in text, cursor, or
anchor position, it must issue this request to the compositor. This
request is intended to give the input method a chance to update the
preedit text in an appropriate way, e.g. by removing it when the user
starts typing with a keyboard.
cause describes the source of the change.
The value set with this event is double-buffered. It will get applied
and set to its initial value on the next zwp_input_method_v2.done
event.
The initial value of cause is input_method.
Indicates the content type and hint for the current
zwp_input_method_v2 instance.
Values set with this event are double-buffered. They will get applied
on the next zwp_input_method_v2.done event.
The initial value for hint is none, and the initial value for purpose
is normal.
Atomically applies state changes recently sent to the client.
The done event establishes and updates the state of the client, and
must be issued after any changes to apply them.
Text input state (content purpose, content hint, surrounding text, and
change cause) is conceptually double-buffered within an input method
context.
Events modify the pending state, as opposed to the current state in use
by the input method. A done event atomically applies all pending state,
replacing the current state. After done, the new pending state is as
documented for each related request.
Events must be applied in the order of arrival.
Neither current nor pending state are modified unless noted otherwise.
Send the commit string text for insertion to the application.
Inserts a string at current cursor position (see commit event
sequence). The string to commit could be either just a single character
after a key press or the result of some composing.
The argument text is a buffer containing the string to insert. There is
a maximum length of wayland messages, so text can not be longer than
4000 bytes.
Values set with this event are double-buffered. They must be applied
and reset to initial on the next zwp_text_input_v3.commit request.
The initial value of text is an empty string.
Send the pre-edit string text to the application text input.
Place a new composing text (pre-edit) at the current cursor position.
Any previously set composing text must be removed. Any previously
existing selected text must be removed. The cursor is moved to a new
position within the preedit string.
The argument text is a buffer containing the preedit string. There is
a maximum length of wayland messages, so text can not be longer than
4000 bytes.
The arguments cursor_begin and cursor_end are counted in bytes relative
to the beginning of the submitted string buffer. Cursor should be
hidden by the text input when both are equal to -1.
cursor_begin indicates the beginning of the cursor. cursor_end
indicates the end of the cursor. It may be equal or different than
cursor_begin.
Values set with this event are double-buffered. They must be applied on
the next zwp_input_method_v2.commit event.
The initial value of text is an empty string. The initial value of
cursor_begin, and cursor_end are both 0.
Remove the surrounding text.
before_length and after_length are the number of bytes before and after
the current cursor index (excluding the preedit text) to delete.
If any preedit text is present, it is replaced with the cursor for the
purpose of this event. In effect before_length is counted from the
beginning of preedit text, and after_length from its end (see commit
event sequence).
Values set with this event are double-buffered. They must be applied
and reset to initial on the next zwp_input_method_v2.commit request.
The initial values of both before_length and after_length are 0.
Apply state changes from commit_string, set_preedit_string and
delete_surrounding_text requests.
The state relating to these events is double-buffered, and each one
modifies the pending state. This request replaces the current state
with the pending state.
The connected text input is expected to proceed by evaluating the
changes in the following order:
1. Replace existing preedit string with the cursor.
2. Delete requested surrounding text.
3. Insert commit string with the cursor at its end.
4. Calculate surrounding text to send.
5. Insert new preedit text in cursor position.
6. Place cursor inside preedit text.
The serial number reflects the last state of the zwp_input_method_v2
object known to the client. The value of the serial argument must be
equal to the number of done events already issued by that object. When
the compositor receives a commit request with a serial different than
the number of past done events, it must proceed as normal, except it
should not change the current state of the zwp_input_method_v2 object.
Creates a new zwp_input_popup_surface_v2 object wrapping a given
surface.
The surface gets assigned the "input_popup" role. If the surface
already has an assigned role, the compositor must issue a protocol
error.
Allow an input method to receive hardware keyboard input and process
key events to generate text events (with pre-edit) over the wire. This
allows input methods which compose multiple key events for inputting
text like it is done for CJK languages.
The compositor should send all keyboard events on the seat to the grab
holder via the returned wl_keyboard object. Nevertheless, the
compositor may decide not to forward any particular event. The
compositor must not further process any event after it has been
forwarded to the grab holder.
Releasing the resulting wl_keyboard object releases the grab.
The input method ceased to be available.
The compositor must issue this event as the only event on the object if
there was another input_method object associated with the same seat at
the time of its creation.
The compositor must issue this request when the object is no longer
usable, e.g. due to seat removal.
The input method context becomes inert and should be destroyed after
deactivation is handled. Any further requests and events except for the
destroy request must be ignored.
Destroys the zwp_text_input_v2 object and any associated child
objects, i.e. zwp_input_popup_surface_v2 and
zwp_input_method_keyboard_grab_v2.
This interface marks a surface as a popup for interacting with an input
method.
The compositor should place it near the active text input area. It must
be visible if and only if the input method is in the active state.
The client must not destroy the underlying wl_surface while the
zwp_input_popup_surface_v2 object exists.
Notify about the position of the area of the text input expressed as a
rectangle in surface local coordinates.
This is a hint to the input method telling it the relative position of
the text being entered.
The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
grab of the wl_keyboard interface associated with the seat.
This event provides a file descriptor to the client which can be
memory-mapped to provide a keyboard mapping description.
A key was pressed or released.
The time argument is a timestamp with millisecond granularity, with an
undefined base.
Notifies clients that the modifier and/or group state has changed, and
it should update its local state.
Informs the client about the keyboard's repeat rate and delay.
This event is sent as soon as the zwp_input_method_keyboard_grab_v2
object has been created, and is guaranteed to be received by the
client before any key press event.
Negative values for either rate or delay are illegal. A rate of zero
will disable any repeating (regardless of the value of delay).
This event can be sent later on as well with a new value if necessary,
so clients should continue listening for the event past the creation
of zwp_input_method_keyboard_grab_v2.
The input method manager allows the client to become the input method on
a chosen seat.
No more than one input method must be associated with any seat at any
given time.
Request a new input zwp_input_method_v2 object associated with a given
seat.
Destroys the zwp_input_method_manager_v2 object.
The zwp_input_method_v2 objects originating from it remain valid.
waypipe-v0.8.6/protocols/linux-dmabuf-unstable-v1.xml 0000664 0000000 0000000 00000070107 14414260423 0022676 0 ustar 00root root 0000000 0000000
Copyright © 2014, 2015 Collabora, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Following the interfaces from:
https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
and the Linux DRM sub-system's AddFb2 ioctl.
This interface offers ways to create generic dmabuf-based wl_buffers.
Clients can use the get_surface_feedback request to get dmabuf feedback
for a particular surface. If the client wants to retrieve feedback not
tied to a surface, they can use the get_default_feedback request.
The following are required from clients:
- Clients must ensure that either all data in the dma-buf is
coherent for all subsequent read access or that coherency is
correctly handled by the underlying kernel-side dma-buf
implementation.
- Don't make any more attachments after sending the buffer to the
compositor. Making more attachments later increases the risk of
the compositor not being able to use (re-import) an existing
dmabuf-based wl_buffer.
The underlying graphics stack must ensure the following:
- The dmabuf file descriptors relayed to the server will stay valid
for the whole lifetime of the wl_buffer. This means the server may
at any time use those fds to import the dmabuf into any kernel
sub-system that might accept it.
However, when the underlying graphics stack fails to deliver the
promise, because of e.g. a device hot-unplug which raises internal
errors, after the wl_buffer has been successfully created the
compositor must not raise protocol errors to the client when dmabuf
import later fails.
To create a wl_buffer from one or more dmabufs, a client creates a
zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params
request. All planes required by the intended format are added with
the 'add' request. Finally, a 'create' or 'create_immed' request is
issued, which has the following outcome depending on the import success.
The 'create' request,
- on success, triggers a 'created' event which provides the final
wl_buffer to the client.
- on failure, triggers a 'failed' event to convey that the server
cannot use the dmabufs received from the client.
For the 'create_immed' request,
- on success, the server immediately imports the added dmabufs to
create a wl_buffer. No event is sent from the server in this case.
- on failure, the server can choose to either:
- terminate the client by raising a fatal error.
- mark the wl_buffer as failed, and send a 'failed' event to the
client. If the client uses a failed wl_buffer as an argument to any
request, the behaviour is compositor implementation-defined.
For all DRM formats and unless specified in another protocol extension,
pre-multiplied alpha is used for pixel values.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
Objects created through this interface, especially wl_buffers, will
remain valid.
This temporary object is used to collect multiple dmabuf handles into
a single batch to create a wl_buffer. It can only be used once and
should be destroyed after a 'created' or 'failed' event has been
received.
This event advertises one buffer format that the server supports.
All the supported formats are advertised once when the client
binds to this interface. A roundtrip after binding guarantees
that the client has received all supported formats.
For the definition of the format codes, see the
zwp_linux_buffer_params_v1::create request.
Starting version 4, the format event is deprecated and must not be
sent by compositors. Instead, use get_default_feedback or
get_surface_feedback.
This event advertises the formats that the server supports, along with
the modifiers supported for each format. All the supported modifiers
for all the supported formats are advertised once when the client
binds to this interface. A roundtrip after binding guarantees that
the client has received all supported format-modifier pairs.
For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi ==
0x00ffffff and modifier_lo == 0xffffffff) is allowed in this event.
It indicates that the server can support the format with an implicit
modifier. When a plane has DRM_FORMAT_MOD_INVALID as its modifier, it
is as if no explicit modifier is specified. The effective modifier
will be derived from the dmabuf.
A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for
a given format supports both explicit modifiers and implicit modifiers.
For the definition of the format and modifier codes, see the
zwp_linux_buffer_params_v1::create and zwp_linux_buffer_params_v1::add
requests.
Starting version 4, the modifier event is deprecated and must not be
sent by compositors. Instead, use get_default_feedback or
get_surface_feedback.
This request creates a new wp_linux_dmabuf_feedback object not bound
to a particular surface. This object will deliver feedback about dmabuf
parameters to use if the client doesn't support per-surface feedback
(see get_surface_feedback).
This request creates a new wp_linux_dmabuf_feedback object for the
specified wl_surface. This object will deliver feedback about dmabuf
parameters to use for buffers attached to this surface.
If the surface is destroyed before the wp_linux_dmabuf_feedback object,
the feedback object becomes inert.
This temporary object is a collection of dmabufs and other
parameters that together form a single logical buffer. The temporary
object may eventually create one wl_buffer unless cancelled by
destroying it before requesting 'create'.
Single-planar formats only require one dmabuf, however
multi-planar formats may require more than one dmabuf. For all
formats, an 'add' request must be called once per plane (even if the
underlying dmabuf fd is identical).
You must use consecutive plane indices ('plane_idx' argument for 'add')
from zero to the number of planes used by the drm_fourcc format code.
All planes required by the format must be given exactly once, but can
be given in any order. Each plane index can be set only once.
Cleans up the temporary data sent to the server for dmabuf-based
wl_buffer creation.
This request adds one dmabuf to the set in this
zwp_linux_buffer_params_v1.
The 64-bit unsigned value combined from modifier_hi and modifier_lo
is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the
fb modifier, which is defined in drm_mode.h of Linux UAPI.
This is an opaque token. Drivers use this token to express tiling,
compression, etc. driver-specific modifications to the base format
defined by the DRM fourcc code.
Starting from version 4, the invalid_format protocol error is sent if
the format + modifier pair was not advertised as supported.
This request raises the PLANE_IDX error if plane_idx is too large.
The error PLANE_SET is raised if attempting to set a plane that
was already set.
This asks for creation of a wl_buffer from the added dmabuf
buffers. The wl_buffer is not created immediately but returned via
the 'created' event if the dmabuf sharing succeeds. The sharing
may fail at runtime for reasons a client cannot predict, in
which case the 'failed' event is triggered.
The 'format' argument is a DRM_FORMAT code, as defined by the
libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the
authoritative source on how the format codes should work.
The 'flags' is a bitfield of the flags defined in enum "flags".
'y_invert' means the that the image needs to be y-flipped.
Flag 'interlaced' means that the frame in the buffer is not
progressive as usual, but interlaced. An interlaced buffer as
supported here must always contain both top and bottom fields.
The top field always begins on the first pixel row. The temporal
ordering between the two fields is top field first, unless
'bottom_first' is specified. It is undefined whether 'bottom_first'
is ignored if 'interlaced' is not set.
This protocol does not convey any information about field rate,
duration, or timing, other than the relative ordering between the
two fields in one buffer. A compositor may have to estimate the
intended field rate from the incoming buffer rate. It is undefined
whether the time of receiving wl_surface.commit with a new buffer
attached, applying the wl_surface state, wl_surface.frame callback
trigger, presentation, or any other point in the compositor cycle
is used to measure the frame or field times. There is no support
for detecting missed or late frames/fields/buffers either, and
there is no support whatsoever for cooperating with interlaced
compositor output.
The composited image quality resulting from the use of interlaced
buffers is explicitly undefined. A compositor may use elaborate
hardware features or software to deinterlace and create progressive
output frames from a sequence of interlaced input buffers, or it
may produce substandard image quality. However, compositors that
cannot guarantee reasonable image quality in all cases are recommended
to just reject all interlaced buffers.
Any argument errors, including non-positive width or height,
mismatch between the number of planes and the format, bad
format, bad offset or stride, may be indicated by fatal protocol
errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS,
OUT_OF_BOUNDS.
Dmabuf import errors in the server that are not obvious client
bugs are returned via the 'failed' event as non-fatal. This
allows attempting dmabuf sharing and falling back in the client
if it fails.
This request can be sent only once in the object's lifetime, after
which the only legal request is destroy. This object should be
destroyed after issuing a 'create' request. Attempting to use this
object after issuing 'create' raises ALREADY_USED protocol error.
It is not mandatory to issue 'create'. If a client wants to
cancel the buffer creation, it can just destroy this object.
This event indicates that the attempted buffer creation was
successful. It provides the new wl_buffer referencing the dmabuf(s).
Upon receiving this event, the client should destroy the
zlinux_dmabuf_params object.
This event indicates that the attempted buffer creation has
failed. It usually means that one of the dmabuf constraints
has not been fulfilled.
Upon receiving this event, the client should destroy the
zlinux_buffer_params object.
This asks for immediate creation of a wl_buffer by importing the
added dmabufs.
In case of import success, no event is sent from the server, and the
wl_buffer is ready to be used by the client.
Upon import failure, either of the following may happen, as seen fit
by the implementation:
- the client is terminated with one of the following fatal protocol
errors:
- INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS,
in case of argument errors such as mismatch between the number
of planes and the format, bad format, non-positive width or
height, or bad offset or stride.
- INVALID_WL_BUFFER, in case the cause for failure is unknown or
plaform specific.
- the server creates an invalid wl_buffer, marks it as failed and
sends a 'failed' event to the client. The result of using this
invalid wl_buffer as an argument in any request by the client is
defined by the compositor implementation.
This takes the same arguments as a 'create' request, and obeys the
same restrictions.
This object advertises dmabuf parameters feedback. This includes the
preferred devices and the supported formats/modifiers.
The parameters are sent once when this object is created and whenever they
change. The done event is always sent once after all parameters have been
sent. When a single parameter changes, all parameters are re-sent by the
compositor.
Compositors can re-send the parameters when the current client buffer
allocations are sub-optimal. Compositors should not re-send the
parameters if re-allocating the buffers would not result in a more optimal
configuration. In particular, compositors should avoid sending the exact
same parameters multiple times in a row.
The tranche_target_device and tranche_modifier events are grouped by
tranches of preference. For each tranche, a tranche_target_device, one
tranche_flags and one or more tranche_modifier events are sent, followed
by a tranche_done event finishing the list. The tranches are sent in
descending order of preference. All formats and modifiers in the same
tranche have the same preference.
To send parameters, the compositor sends one main_device event, tranches
(each consisting of one tranche_target_device event, one tranche_flags
event, tranche_modifier events and then a tranche_done event), then one
done event.
Using this request a client can tell the server that it is not going to
use the wp_linux_dmabuf_feedback object anymore.
This event is sent after all parameters of a wp_linux_dmabuf_feedback
object have been sent.
This allows changes to the wp_linux_dmabuf_feedback parameters to be
seen as atomic, even if they happen via multiple events.
This event provides a file descriptor which can be memory-mapped to
access the format and modifier table.
The table contains a tightly packed array of consecutive format +
modifier pairs. Each pair is 16 bytes wide. It contains a format as a
32-bit unsigned integer, followed by 4 bytes of unused padding, and a
modifier as a 64-bit unsigned integer. The native endianness is used.
The client must map the file descriptor in read-only private mode.
Compositors are not allowed to mutate the table file contents once this
event has been sent. Instead, compositors must create a new, separate
table file and re-send feedback parameters. Compositors are allowed to
store duplicate format + modifier pairs in the table.
This event advertises the main device that the server prefers to use
when direct scan-out to the target device isn't possible. The
advertised main device may be different for each
wp_linux_dmabuf_feedback object, and may change over time.
There is exactly one main device. The compositor must send at least
one preference tranche with tranche_target_device equal to main_device.
Clients need to create buffers that the main device can import and
read from, otherwise creating the dmabuf wl_buffer will fail (see the
wp_linux_buffer_params.create and create_immed requests for details).
The main device will also likely be kept active by the compositor,
so clients can use it instead of waking up another device for power
savings.
In general the device is a DRM node. The DRM node type (primary vs.
render) is unspecified. Clients must not rely on the compositor sending
a particular node type. Clients cannot check two devices for equality
by comparing the dev_t value.
If explicit modifiers are not supported and the client performs buffer
allocations on a different device than the main device, then the client
must force the buffer to have a linear layout.
This event splits tranche_target_device and tranche_modifier events in
preference tranches. It is sent after a set of tranche_target_device
and tranche_modifier events; it represents the end of a tranche. The
next tranche will have a lower preference.
This event advertises the target device that the server prefers to use
for a buffer created given this tranche. The advertised target device
may be different for each preference tranche, and may change over time.
There is exactly one target device per tranche.
The target device may be a scan-out device, for example if the
compositor prefers to directly scan-out a buffer created given this
tranche. The target device may be a rendering device, for example if
the compositor prefers to texture from said buffer.
The client can use this hint to allocate the buffer in a way that makes
it accessible from the target device, ideally directly. The buffer must
still be accessible from the main device, either through direct import
or through a potentially more expensive fallback path. If the buffer
can't be directly imported from the main device then clients must be
prepared for the compositor changing the tranche priority or making
wl_buffer creation fail (see the wp_linux_buffer_params.create and
create_immed requests for details).
If the device is a DRM node, the DRM node type (primary vs. render) is
unspecified. Clients must not rely on the compositor sending a
particular node type. Clients cannot check two devices for equality by
comparing the dev_t value.
This event is tied to a preference tranche, see the tranche_done event.
This event advertises the format + modifier combinations that the
compositor supports.
It carries an array of indices, each referring to a format + modifier
pair in the last received format table (see the format_table event).
Each index is a 16-bit unsigned integer in native endianness.
For legacy support, DRM_FORMAT_MOD_INVALID is an allowed modifier.
It indicates that the server can support the format with an implicit
modifier. When a buffer has DRM_FORMAT_MOD_INVALID as its modifier, it
is as if no explicit modifier is specified. The effective modifier
will be derived from the dmabuf.
A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for
a given format supports both explicit modifiers and implicit modifiers.
Compositors must not send duplicate format + modifier pairs within the
same tranche or across two different tranches with the same target
device and flags.
This event is tied to a preference tranche, see the tranche_done event.
For the definition of the format and modifier codes, see the
wp_linux_buffer_params.create request.
This event sets tranche-specific flags.
The scanout flag is a hint that direct scan-out may be attempted by the
compositor on the target device if the client appropriately allocates a
buffer. How to allocate a buffer that can be scanned out on the target
device is implementation-defined.
This event is tied to a preference tranche, see the tranche_done event.
waypipe-v0.8.6/protocols/meson.build 0000664 0000000 0000000 00000002753 14414260423 0017566 0 ustar 00root root 0000000 0000000
symgen_path = join_paths(meson.current_source_dir(), 'symgen.py')
sendgen_path = join_paths(meson.current_source_dir(), 'sendgen.py')
fn_list = join_paths(meson.current_source_dir(), 'function_list.txt')
# Include a copy of these protocols in the repository, rather than looking
# for packages containing them, to:
# a) avoid versioning problems as new protocols/methods are introduced
# b) keep the minimum build complexity for waypipe low
# c) be able to relay through newer protocols than are default on a system
protocols = [
'wayland.xml',
'xdg-shell.xml',
'presentation-time.xml',
'linux-dmabuf-unstable-v1.xml',
'gtk-primary-selection.xml',
'input-method-unstable-v2.xml',
'primary-selection-unstable-v1.xml',
'virtual-keyboard-unstable-v1.xml',
'wlr-screencopy-unstable-v1.xml',
'wlr-export-dmabuf-unstable-v1.xml',
'wlr-data-control-unstable-v1.xml',
'wlr-gamma-control-unstable-v1.xml',
'wayland-drm.xml',
]
protocols_src = []
protocols_src += custom_target(
'protocol code',
output: 'protocols.c',
input: protocols,
depend_files: [fn_list, symgen_path],
command: [python3, symgen_path, 'data', fn_list, '@OUTPUT@', '@INPUT@'],
)
protocols_src += custom_target(
'protocol header',
output: 'protocols.h',
input: protocols,
depend_files: [fn_list, symgen_path],
command: [python3, symgen_path, 'header', fn_list, '@OUTPUT@', '@INPUT@'],
)
# For use in test
abs_protocols = []
foreach xml : protocols
abs_protocols += join_paths(meson.current_source_dir(), xml)
endforeach
waypipe-v0.8.6/protocols/presentation-time.xml 0000664 0000000 0000000 00000030502 14414260423 0021606 0 ustar 00root root 0000000 0000000
Copyright © 2013-2014 Collabora, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
The main feature of this interface is accurate presentation
timing feedback to ensure smooth video playback while maintaining
audio/video synchronization. Some features use the concept of a
presentation clock, which is defined in the
presentation.clock_id event.
A content update for a wl_surface is submitted by a
wl_surface.commit request. Request 'feedback' associates with
the wl_surface.commit and provides feedback on the content
update, particularly the final realized presentation time.
When the final realized presentation time is available, e.g.
after a framebuffer flip completes, the requested
presentation_feedback.presented events are sent. The final
presentation time can differ from the compositor's predicted
display update time and the update's target time, especially
when the compositor misses its target vertical blanking period.
These fatal protocol errors may be emitted in response to
illegal presentation requests.
Informs the server that the client will no longer be using
this protocol object. Existing objects created by this object
are not affected.
Request presentation feedback for the current content submission
on the given surface. This creates a new presentation_feedback
object, which will deliver the feedback information once. If
multiple presentation_feedback objects are created for the same
submission, they will all deliver the same information.
For details on what information is returned, see the
presentation_feedback interface.
This event tells the client in which clock domain the
compositor interprets the timestamps used by the presentation
extension. This clock is called the presentation clock.
The compositor sends this event when the client binds to the
presentation interface. The presentation clock does not change
during the lifetime of the client connection.
The clock identifier is platform dependent. On Linux/glibc,
the identifier value is one of the clockid_t values accepted
by clock_gettime(). clock_gettime() is defined by
POSIX.1-2001.
Timestamps in this clock domain are expressed as tv_sec_hi,
tv_sec_lo, tv_nsec triples, each component being an unsigned
32-bit value. Whole seconds are in tv_sec which is a 64-bit
value combined from tv_sec_hi and tv_sec_lo, and the
additional fractional part in tv_nsec as nanoseconds. Hence,
for valid timestamps tv_nsec must be in [0, 999999999].
Note that clock_id applies only to the presentation clock,
and implies nothing about e.g. the timestamps used in the
Wayland core protocol input events.
Compositors should prefer a clock which does not jump and is
not slewed e.g. by NTP. The absolute value of the clock is
irrelevant. Precision of one millisecond or better is
recommended. Clients must be able to query the current clock
value directly, not by asking the compositor.
A presentation_feedback object returns an indication that a
wl_surface content update has become visible to the user.
One object corresponds to one content update submission
(wl_surface.commit). There are two possible outcomes: the
content update is presented to the user, and a presentation
timestamp delivered; or, the user did not see the content
update because it was superseded or its surface destroyed,
and the content update is discarded.
Once a presentation_feedback object has delivered a 'presented'
or 'discarded' event it is automatically destroyed.
As presentation can be synchronized to only one output at a
time, this event tells which output it was. This event is only
sent prior to the presented event.
As clients may bind to the same global wl_output multiple
times, this event is sent for each bound instance that matches
the synchronized output. If a client has not bound to the
right wl_output global at all, this event is not sent.
These flags provide information about how the presentation of
the related content update was done. The intent is to help
clients assess the reliability of the feedback and the visual
quality with respect to possible tearing and timings.
The presentation was synchronized to the "vertical retrace" by
the display hardware such that tearing does not happen.
Relying on software scheduling is not acceptable for this
flag. If presentation is done by a copy to the active
frontbuffer, then it must guarantee that tearing cannot
happen.
The display hardware provided measurements that the hardware
driver converted into a presentation timestamp. Sampling a
clock in software is not acceptable for this flag.
The display hardware signalled that it started using the new
image content. The opposite of this is e.g. a timer being used
to guess when the display hardware has switched to the new
image content.
The presentation of this update was done zero-copy. This means
the buffer from the client was given to display hardware as
is, without copying it. Compositing with OpenGL counts as
copying, even if textured directly from the client buffer.
Possible zero-copy cases include direct scanout of a
fullscreen surface and a surface on a hardware overlay.
The associated content update was displayed to the user at the
indicated time (tv_sec_hi/lo, tv_nsec). For the interpretation of
the timestamp, see presentation.clock_id event.
The timestamp corresponds to the time when the content update
turned into light the first time on the surface's main output.
Compositors may approximate this from the framebuffer flip
completion events from the system, and the latency of the
physical display path if known.
This event is preceded by all related sync_output events
telling which output's refresh cycle the feedback corresponds
to, i.e. the main output for the surface. Compositors are
recommended to choose the output containing the largest part
of the wl_surface, or keeping the output they previously
chose. Having a stable presentation output association helps
clients predict future output refreshes (vblank).
The 'refresh' argument gives the compositor's prediction of how
many nanoseconds after tv_sec, tv_nsec the very next output
refresh may occur. This is to further aid clients in
predicting future refreshes, i.e., estimating the timestamps
targeting the next few vblanks. If such prediction cannot
usefully be done, the argument is zero.
If the output does not have a constant refresh rate, explicit
video mode switches excluded, then the refresh argument must
be zero.
The 64-bit value combined from seq_hi and seq_lo is the value
of the output's vertical retrace counter when the content
update was first scanned out to the display. This value must
be compatible with the definition of MSC in
GLX_OML_sync_control specification. Note, that if the display
path has a non-zero latency, the time instant specified by
this counter may differ from the timestamp's.
If the output does not have a concept of vertical retrace or a
refresh cycle, or the output device is self-refreshing without
a way to query the refresh count, then the arguments seq_hi
and seq_lo must be zero.
The content update was never displayed to the user.
waypipe-v0.8.6/protocols/primary-selection-unstable-v1.xml 0000664 0000000 0000000 00000024345 14414260423 0023754 0 ustar 00root root 0000000 0000000
Copyright © 2015, 2016 Red Hat
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
This protocol provides the ability to have a primary selection device to
match that of the X server. This primary selection is a shortcut to the
common clipboard selection, where text just needs to be selected in order
to allow copying it elsewhere. The de facto way to perform this action
is the middle mouse button, although it is not limited to this one.
Clients wishing to honor primary selection should create a primary
selection source and set it as the selection through
wp_primary_selection_device.set_selection whenever the text selection
changes. In order to minimize calls in pointer-driven text selection,
it should happen only once after the operation finished. Similarly,
a NULL source should be set when text is unselected.
wp_primary_selection_offer objects are first announced through the
wp_primary_selection_device.data_offer event. Immediately after this event,
the primary data offer will emit wp_primary_selection_offer.offer events
to let know of the mime types being offered.
When the primary selection changes, the client with the keyboard focus
will receive wp_primary_selection_device.selection events. Only the client
with the keyboard focus will receive such events with a non-NULL
wp_primary_selection_offer. Across keyboard focus changes, previously
focused clients will receive wp_primary_selection_device.events with a
NULL wp_primary_selection_offer.
In order to request the primary selection data, the client must pass
a recent serial pertaining to the press event that is triggering the
operation, if the compositor deems the serial valid and recent, the
wp_primary_selection_source.send event will happen in the other end
to let the transfer begin. The client owning the primary selection
should write the requested data, and close the file descriptor
immediately.
If the primary selection owner client disappeared during the transfer,
the client reading the data will receive a
wp_primary_selection_device.selection event with a NULL
wp_primary_selection_offer, the client should take this as a hint
to finish the reads related to the no longer existing offer.
The primary selection owner should be checking for errors during
writes, merely cancelling the ongoing transfer if any happened.
The primary selection device manager is a singleton global object that
provides access to the primary selection. It allows to create
wp_primary_selection_source objects, as well as retrieving the per-seat
wp_primary_selection_device objects.
Create a new primary selection source.
Create a new data device for a given seat.
Destroy the primary selection device manager.
Replaces the current selection. The previous owner of the primary
selection will receive a wp_primary_selection_source.cancelled event.
To unset the selection, set the source to NULL.
Introduces a new wp_primary_selection_offer object that may be used
to receive the current primary selection. Immediately following this
event, the new wp_primary_selection_offer object will send
wp_primary_selection_offer.offer events to describe the offered mime
types.
The wp_primary_selection_device.selection event is sent to notify the
client of a new primary selection. This event is sent after the
wp_primary_selection.data_offer event introducing this object, and after
the offer has announced its mimetypes through
wp_primary_selection_offer.offer.
The data_offer is valid until a new offer or NULL is received
or until the client loses keyboard focus. The client must destroy the
previous selection data_offer, if any, upon receiving this event.
Destroy the primary selection device.
A wp_primary_selection_offer represents an offer to transfer the contents
of the primary selection clipboard to the client. Similar to
wl_data_offer, the offer also describes the mime types that the data can
be converted to and provides the mechanisms for transferring the data
directly to the client.
To transfer the contents of the primary selection clipboard, the client
issues this request and indicates the mime type that it wants to
receive. The transfer happens through the passed file descriptor
(typically created with the pipe system call). The source client writes
the data in the mime type representation requested and then closes the
file descriptor.
The receiving client reads from the read end of the pipe until EOF and
closes its end, at which point the transfer is complete.
Destroy the primary selection offer.
Sent immediately after creating announcing the
wp_primary_selection_offer through
wp_primary_selection_device.data_offer. One event is sent per offered
mime type.
The source side of a wp_primary_selection_offer, it provides a way to
describe the offered data and respond to requests to transfer the
requested contents of the primary selection clipboard.
This request adds a mime type to the set of mime types advertised to
targets. Can be called several times to offer multiple types.
Destroy the primary selection source.
Request for the current primary selection contents from the client.
Send the specified mime type over the passed file descriptor, then
close it.
This primary selection source is no longer valid. The client should
clean up and destroy this primary selection source.
waypipe-v0.8.6/protocols/sendgen.py 0000664 0000000 0000000 00000015372 14414260423 0017422 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
import os, sys, fnmatch
import xml.etree.ElementTree as ET
"""
A static protocol code generator for the task of creating the wire representation
of a list of events/requests
"""
wltype_to_ctypes = {
"uint": "uint32_t ",
"fixed": "uint32_t ",
"int": "int32_t ",
"object": "struct wp_objid ",
"new_id": "struct wp_objid ",
"string": "const char *",
"fd": "int ",
}
def write_enum(ostream, iface_name, enum):
enum_name = enum.attrib["name"]
is_bitfield = "bitfield" in enum.attrib and enum.attrib["bitfield"] == "true"
for entry in enum:
if entry.tag != "entry":
continue
entry_name = entry.attrib["name"]
entry_value = entry.attrib["value"]
full_name = (iface_name + "_" + enum_name + "_" + entry_name).upper()
print("#define {} {}".format(full_name, entry_value), file=ostream)
def is_exportable(func_name, export_list):
for e in export_list:
if fnmatch.fnmatchcase(func_name, e):
return True
return False
def write_func(ostream, iface_name, func, is_request, func_no, export_list):
func_name = (
iface_name + "_" + ("req" if is_request else "evt") + "_" + func.attrib["name"]
)
for_export = is_exportable(func_name, export_list)
if not for_export:
return
c_sig = ["struct transfer_states *ts", "struct wp_objid " + iface_name + "_id"]
w_args = []
num_fd_args = 0
num_reg_args = 0
num_obj_args = 0
num_new_args = 0
num_stretch_args = 0
for arg in func:
if arg.tag != "arg":
continue
arg_name = arg.attrib["name"]
arg_type = arg.attrib["type"]
arg_interface = arg.attrib["interface"] if "interface" in arg.attrib else None
if arg_type == "new_id" and arg_interface is None:
# Special case, for wl_registry_bind
c_sig.append("const char *interface")
c_sig.append("uint32_t version")
c_sig.append("struct wp_objid id")
w_args.append(("interface", "string", None))
w_args.append(("version", "uint", None))
w_args.append((arg_name, "new_id", None))
num_obj_args += 1
num_new_args += 1
num_reg_args += 3
num_stretch_args += 1
continue
if arg_type == "array":
c_sig.append("int " + arg_name + "_count")
c_sig.append("const uint8_t *" + arg_name + "_val")
else:
c_sig.append(wltype_to_ctypes[arg_type] + arg_name)
w_args.append((arg_name, arg_type, arg_interface))
if arg_type == "fd":
num_fd_args += 1
else:
num_reg_args += 1
if arg_type == "object" or arg_type == "new_id":
num_obj_args += 1
if arg_type == "new_id":
num_new_args += 1
if arg_type in ("array", "string"):
num_stretch_args += 1
send_signature = "static void send_{}({}) ".format(func_name, ", ".join(c_sig))
W = lambda *x: print(*x, file=ostream)
# Write function definition
W(send_signature + " {")
W("\tts->fd_size = 0;")
W("\tts->msg_space[0] = {}.id;".format(iface_name + "_id"))
W("\tts->msg_size = 2;")
tmp_names = ["ctx"]
for i, (arg_name, arg_type, arg_interface) in enumerate(w_args):
if arg_type == "array":
raise NotImplementedError()
continue
elif arg_type == "fd":
W("\tts->fd_space[ts->fd_size++] = {};".format(arg_name))
continue
elif arg_type == "string":
W("\tserialize_string(ts, {});".format(arg_name))
continue
elif arg_type == "object" or arg_type == "new_id":
W("\tts->msg_space[ts->msg_size++] = {}.id;".format(arg_name))
elif arg_type == "int":
W("\tts->msg_space[ts->msg_size++] = (uint32_t){};".format(arg_name))
elif arg_type == "uint" or arg_type == "fixed":
W("\tts->msg_space[ts->msg_size++] = {};".format(arg_name))
else:
raise KeyError(arg_type)
W("\tts->msg_space[1] = ((uint32_t)ts->msg_size << 18) | {};".format(func_no))
if is_request:
W("\tts->send(ts, ts->app, ts->comp);")
else:
W("\tts->send(ts, ts->comp, ts->app);")
W("}")
if __name__ == "__main__":
req_file, dest = sys.argv[1:3]
sources = sys.argv[3:]
assert dest.endswith(".h")
dest_shortname = dest[:-2]
header_flag = dest_shortname.upper().replace("/", "_") + "_H"
export_list = open(req_file).read().split("\n")
with open(dest, "w") as ostream:
W = lambda *x: print(*x, file=ostream)
W("#ifndef {}".format(header_flag))
W("#include ")
W("#include ")
W("#include ")
W("struct test_state;")
W("struct wp_objid { uint32_t id; };")
W("struct transfer_states {")
W("\tuint32_t msg_space[256];")
W("\tint fd_space[16];")
W("\tunsigned int msg_size;")
W("\tunsigned int fd_size;")
W("\tstruct test_state *app;")
W("\tstruct test_state *comp;")
W(
"\tvoid (*send)(struct transfer_states *, struct test_state *src, struct test_state *dst);"
)
W("};")
# note: this script assumes that serialize_string will be used
W("static void serialize_string(struct transfer_states *ts, const char *str) {")
W("\tif (str) {")
W("\t\tsize_t slen = strlen(str) + 1;")
W("\t\tts->msg_space[ts->msg_size] = (uint32_t)slen;")
W("\t\tmemcpy(&ts->msg_space[ts->msg_size + 1], str, slen);")
W("\t\tts->msg_size += ((uint32_t)slen + 0x7) >> 2;")
W("\t} else {")
W("\t\tts->msg_space[ts->msg_size++] = 0;")
W("\t}")
W("}")
for source in sorted(sources):
tree = ET.parse(source)
root = tree.getroot()
for interface in root:
if interface.tag != "interface":
continue
iface_name = interface.attrib["name"]
func_data = []
nreq, nevt = 0, 0
for item in interface:
if item.tag == "enum":
write_enum(ostream, iface_name, item)
elif item.tag == "request":
write_func(ostream, iface_name, item, True, nreq, export_list)
nreq += 1
elif item.tag == "event":
write_func(ostream, iface_name, item, False, nevt, export_list)
nevt += 1
elif item.tag == "description":
pass
else:
raise Exception(item.tag)
W("#endif /* {} */".format(header_flag))
waypipe-v0.8.6/protocols/symgen.py 0000775 0000000 0000000 00000035677 14414260423 0017316 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
import os, sys, fnmatch
import xml.etree.ElementTree as ET
import argparse
"""
A static protocol code generator.
"""
wltype_to_ctypes = {
"uint": "uint32_t ",
"fixed": "uint32_t ",
"int": "int32_t ",
"object": "struct wp_object *",
"new_id": "struct wp_object *",
"string": "const char *",
"fd": "int ",
}
def superstring(a, b):
na, nb = len(a), len(b)
if nb > na:
b, a, nb, na = a, b, na, nb
# A contains B
for i in range(na - nb + 1):
if a[i : nb + i] == b:
return a
# suffix of B is prefix of A
ba_overlap = 0
for i in range(1, nb):
if b[-i:] == a[:i]:
ba_overlap = i
# suffix of A is prefix of B
ab_overlap = 0
for i in range(1, nb):
if a[-i:] == b[:i]:
ab_overlap = i
if ba_overlap > ab_overlap:
return b + a[ba_overlap:]
else:
return a + b[ab_overlap:]
def get_offset(haystack, needle):
for i in range(len(haystack) - len(needle) + 1):
if haystack[i : i + len(needle)] == needle:
return i
return None
def shortest_superstring(strings):
"""
Given strings L_1,...L_n over domain U, report an approximation
of the shortest superstring of the lists, and offsets of the
L_i into this string. Has O(n^3) runtime; O(n^2 polylog) is possible.
"""
if not len(strings):
return None
pool = []
for s in strings:
if s not in pool:
pool.append(s)
while len(pool) > 1:
max_overlap = 0
best = None
for i in range(len(pool)):
for j in range(i):
d = len(pool[i]) + len(pool[j]) - len(superstring(pool[i], pool[j]))
if d >= max_overlap:
max_overlap = d
best = (j, i)
s = superstring(pool[best[0]], pool[best[1]])
del pool[best[1]]
del pool[best[0]]
pool.append(s)
sstring = pool[0]
for s in strings:
assert get_offset(sstring, s) != None, ("substring property", sstring, s)
return sstring
def write_enum(is_header, ostream, iface_name, enum):
if not is_header:
return
enum_name = enum.attrib["name"]
is_bitfield = "bitfield" in enum.attrib and enum.attrib["bitfield"] == "true"
long_name = iface_name + "_" + enum_name
print("enum " + long_name + " {", file=ostream)
for entry in enum:
if entry.tag != "entry":
continue
entry_name = entry.attrib["name"]
entry_value = entry.attrib["value"]
full_name = long_name.upper() + "_" + entry_name.upper()
print("\t" + full_name + " = " + entry_value + ",", file=ostream)
print("};", file=ostream)
def write_version(is_header, ostream, iface_name, version):
if not is_header:
return
print(
"#define " + iface_name.upper() + "_INTERFACE_VERSION " + str(version),
file=ostream,
)
def is_exportable(func_name, export_list):
for e in export_list:
if fnmatch.fnmatchcase(func_name, e):
return True
return False
def write_func(is_header, ostream, func_name, func):
c_sig = ["struct context *ctx"]
w_args = []
num_fd_args = 0
num_reg_args = 0
num_obj_args = 0
num_new_args = 0
num_stretch_args = 0
for arg in func:
if arg.tag != "arg":
continue
arg_name = arg.attrib["name"]
arg_type = arg.attrib["type"]
arg_interface = arg.attrib["interface"] if "interface" in arg.attrib else None
if arg_type == "new_id" and arg_interface is None:
# Special case, for wl_registry_bind
c_sig.append("const char *interface")
c_sig.append("uint32_t version")
c_sig.append("struct wp_object *id")
w_args.append(("interface", "string", None))
w_args.append(("version", "uint", None))
w_args.append((arg_name, "new_id", None))
num_obj_args += 1
num_new_args += 1
num_reg_args += 3
num_stretch_args += 1
continue
if arg_type == "array":
c_sig.append("uint32_t " + arg_name + "_count")
c_sig.append("const uint8_t *" + arg_name + "_val")
else:
c_sig.append(wltype_to_ctypes[arg_type] + arg_name)
w_args.append((arg_name, arg_type, arg_interface))
if arg_type == "fd":
num_fd_args += 1
else:
num_reg_args += 1
if arg_type == "object" or arg_type == "new_id":
num_obj_args += 1
if arg_type == "new_id":
num_new_args += 1
if arg_type in ("array", "string"):
num_stretch_args += 1
do_signature = "void do_{}({});".format(func_name, ", ".join(c_sig))
handle_signature = "static void call_{}(struct context *ctx, const uint32_t *payload, const int *fds, struct message_tracker *mt)".format(
func_name
)
W = lambda *x: print(*x, file=ostream)
if is_header:
W(do_signature)
if not is_header:
# Write function definition
W(do_signature)
W(handle_signature + " {")
if num_reg_args > 0:
W("\tunsigned int i = 0;")
if num_fd_args > 0:
W("\tunsigned int k = 0;")
tmp_names = ["ctx"]
n_fds_left = num_fd_args
n_reg_left = num_reg_args
for i, (arg_name, arg_type, arg_interface) in enumerate(w_args):
if arg_type == "array":
n_reg_left -= 1
W(
"\tconst uint8_t *arg{}_b = (const uint8_t *)&payload[i + 1];".format(
i
)
)
W("\tuint32_t arg{}_a = payload[i];".format(i))
if n_reg_left > 0:
W("\ti += 1 + (unsigned int)((arg{}_a + 0x3) >> 2);".format(i))
tmp_names.append("arg{}_a".format(i))
tmp_names.append("arg{}_b".format(i))
continue
tmp_names.append("arg{}".format(i))
if arg_type == "fd":
n_fds_left -= 1
W("\tint arg{} = fds[{}];".format(i, "k++" if n_fds_left > 0 else "k"))
continue
n_reg_left -= 1
if arg_type == "string":
W("\tconst char *arg{} = (const char *)&payload[i + 1];".format(i))
W("\tif (!payload[i]) arg{} = NULL;".format(i))
if n_reg_left > 0:
W("\ti += 1 + ((payload[i] + 0x3) >> 2);")
continue
i_incr = "i++" if n_reg_left > 0 else "i"
if arg_type == "object" or arg_type == "new_id":
if arg_interface is None:
intf_str = "NULL"
else:
intf_str = "&intf_" + arg_interface
W(
"\tstruct wp_object *arg{} = get_object(mt, payload[{}], {});".format(
i, i_incr, intf_str
)
)
elif arg_type == "int":
W("\tint32_t arg{} = (int32_t)payload[{}];".format(i, i_incr))
elif arg_type == "uint" or arg_type == "fixed":
W("\tuint32_t arg{} = payload[{}];".format(i, i_incr))
W("\tdo_{}({});".format(func_name, ", ".join(tmp_names)))
if num_obj_args == 0:
W("\t(void)mt;")
if num_fd_args == 0:
W("\t(void)fds;")
if num_reg_args == 0:
W("\t(void)payload;")
W("}")
def load_msg_data(func_name, func, for_export):
w_args = []
for arg in func:
if arg.tag != "arg":
continue
arg_name = arg.attrib["name"]
arg_type = arg.attrib["type"]
arg_interface = arg.attrib["interface"] if "interface" in arg.attrib else None
if arg_type == "new_id" and arg_interface is None:
w_args.append(("interface", "string", None))
w_args.append(("version", "uint", None))
w_args.append((arg_name, "new_id", None))
else:
w_args.append((arg_name, arg_type, arg_interface))
new_objs = []
for arg_name, arg_type, arg_interface in w_args:
if arg_type == "new_id":
new_objs.append(
"&intf_" + arg_interface if arg_interface is not None else "NULL"
)
# gap coding: 0=end,1=new_obj,2=array,3=string
num_fd_args = 0
gaps = [0]
gap_ends = []
for arg_name, arg_type, arg_interface in w_args:
if arg_type == "fd":
num_fd_args += 1
continue
gaps[-1] += 1
if arg_type in ("new_id", "string", "array"):
gap_ends.append({"new_id": 1, "string": 3, "array": 2}[arg_type])
gaps.append(0)
gap_ends.append(0)
gap_codes = [str(g * 4 + e) for g, e in zip(gaps, gap_ends)]
is_destructor = "type" in func.attrib and func.attrib["type"] == "destructor"
is_request = item.tag == "request"
short_name = func.attrib["name"]
return (
is_request,
func_name,
short_name,
new_objs,
gap_codes,
is_destructor,
num_fd_args,
for_export,
)
def write_interface(
ostream, iface_name, func_data, gap_code_array, new_obj_array, dest_name
):
reqs, evts = [], []
for x in func_data:
if x[0]:
reqs.append(x)
else:
evts.append(x)
W = lambda *x: print(*x, file=ostream)
if len(reqs) > 0 or len(evts) > 0:
W("static const struct msg_data msgs_" + iface_name + "[] = {")
msg_names = []
for x in reqs + evts:
(
is_request,
func_name,
short_name,
new_objs,
gap_codes,
is_destructor,
num_fd_args,
for_export,
) = x
msg_names.append(short_name)
mda = []
mda.append(
"gaps_{} + {}".format(dest_name, get_offset(gap_code_array, gap_codes))
)
if len(new_objs) > 0:
mda.append(
"objt_{} + {}".format(dest_name, get_offset(new_obj_array, new_objs))
)
else:
mda.append("NULL")
mda.append(("call_" + func_name) if for_export else "NULL")
mda.append(str(num_fd_args))
mda.append("true" if is_destructor else "false")
W("\t{" + ", ".join(mda) + "},")
mcn = "NULL"
if len(reqs) > 0 or len(evts) > 0:
W("};")
mcn = "msgs_" + iface_name
W("const struct wp_interface intf_" + iface_name + " = {")
W("\t" + mcn + ",")
W("\t" + str(len(reqs)) + ",")
W("\t" + str(len(evts)) + ",")
W('\t"{}",'.format(iface_name))
W('\t"{}",'.format("\\0".join(msg_names)))
W("};")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("mode", help="Either 'header' or 'data'.")
parser.add_argument(
"export_list", help="List of events/requests which need parsing."
)
parser.add_argument("output_file", help="C file to create.")
parser.add_argument("protocols", nargs="+", help="XML protocol files to use.")
args = parser.parse_args()
is_header = {"data": False, "header": True}[args.mode]
if is_header:
assert args.output_file[-2:] == ".h"
else:
assert args.output_file[-2:] == ".c"
dest_name = os.path.basename(args.output_file)[:-2].replace("-", "_")
export_list = open(args.export_list).read().split("\n")
intfset = set()
for source in args.protocols:
tree = ET.parse(source)
root = tree.getroot()
for intf in root:
if intf.tag == "interface":
intfset.add(intf.attrib["name"])
for msg in intf:
for arg in msg:
if "interface" in arg.attrib:
intfset.add(arg.attrib["interface"])
interfaces = sorted(intfset)
header_guard = "{}_H".format(dest_name.upper())
with open(args.output_file, "w") as ostream:
W = lambda *x: print(*x, file=ostream)
if is_header:
W("#ifndef {}".format(header_guard))
W("#define {}".format(header_guard))
W()
W('#include "symgen_types.h"')
if not is_header:
W("#include ")
for intf in interfaces:
W("extern const struct wp_interface intf_{};".format(intf))
gap_code_list = []
new_obj_list = []
interface_data = []
for source in sorted(args.protocols):
tree = ET.parse(source)
root = tree.getroot()
for interface in root:
if interface.tag != "interface":
continue
iface_name = interface.attrib["name"]
write_version(
is_header, ostream, iface_name, interface.attrib["version"]
)
func_data = []
for item in interface:
if item.tag == "enum":
write_enum(is_header, ostream, iface_name, item)
elif item.tag == "request" or item.tag == "event":
is_req = item.tag == "request"
func_name = (
iface_name
+ "_"
+ ("req" if is_req else "evt")
+ "_"
+ item.attrib["name"]
)
for_export = is_exportable(func_name, export_list)
if for_export:
write_func(is_header, ostream, func_name, item)
if not is_header:
func_data.append(load_msg_data(func_name, item, for_export))
elif item.tag == "description":
pass
else:
raise Exception(item.tag)
for x in func_data:
gap_code_list.append(x[4])
new_obj_list.append(x[3])
interface_data.append((iface_name, func_data))
if not is_header:
gap_code_array = shortest_superstring(gap_code_list)
new_obj_array = shortest_superstring(new_obj_list)
if new_obj_array is not None:
W("static const struct wp_interface *objt_" + dest_name + "[] = {")
W("\t" + ",\n\t".join(new_obj_array))
W("};")
if gap_code_array is not None:
W("static const uint16_t gaps_" + dest_name + "[] = {")
W("\t" + ",\n\t".join(gap_code_array))
W("};")
for iface_name, func_data in interface_data:
write_interface(
ostream,
iface_name,
func_data,
gap_code_array,
new_obj_array,
dest_name,
)
if is_header:
W()
W("#endif /* {} */".format(header_guard))
waypipe-v0.8.6/protocols/symgen_types.h 0000664 0000000 0000000 00000002546 14414260423 0020323 0 ustar 00root root 0000000 0000000 #ifndef SYMGEN_TYPES_H
#define SYMGEN_TYPES_H
#include
#include
struct context;
struct message_tracker;
struct wp_object;
typedef void (*wp_callfn_t)(struct context *ctx, const uint32_t *payload, const int *fds, struct message_tracker *mt);
#define GAP_CODE_END 0x0
#define GAP_CODE_OBJ 0x1
#define GAP_CODE_ARR 0x2
#define GAP_CODE_STR 0x3
struct msg_data {
/* Number of 4-byte blocks until next nontrivial input.
* (Note: 16-bit length is sufficient since message lengths also 16-bit)
* Lowest 2 bits indicate if what follows is end/obj/array/string */
const uint16_t* gaps;
/* Pointer to new object types, can be null if none indicated */
const struct wp_interface **new_objs;
/* Function pointer to parse + invoke do_ handler */
const wp_callfn_t call;
/* Number of associated file descriptors */
const int16_t n_fds;
/* Whether message destroys the object */
bool is_destructor;
};
struct wp_interface {
/* msgs[0..nreq-1] are reqs; msgs[nreq..nreq+nevt-1] are evts */
const struct msg_data *msgs;
const int nreq, nevt;
/* The name of the interface */
const char *name;
/* The names of the messages, in order; stored tightly packed */
const char *msg_names;
};
/* User should define this function. */
struct wp_object *get_object(struct message_tracker *mt, uint32_t id, const struct wp_interface *intf);
#endif /* SYMGEN_TYPES_H */
waypipe-v0.8.6/protocols/virtual-keyboard-unstable-v1.xml 0000664 0000000 0000000 00000011426 14414260423 0023566 0 ustar 00root root 0000000 0000000
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2013 Intel Corporation
Copyright © 2012-2013 Collabora, Ltd.
Copyright © 2018 Purism SPC
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
The virtual keyboard provides an application with requests which emulate
the behaviour of a physical keyboard.
This interface can be used by clients on its own to provide raw input
events, or it can accompany the input method protocol.
Provide a file descriptor to the compositor which can be
memory-mapped to provide a keyboard mapping description.
Format carries a value from the keymap_format enumeration.
A key was pressed or released.
The time argument is a timestamp with millisecond granularity, with an
undefined base. All requests regarding a single object must share the
same clock.
Keymap must be set before issuing this request.
State carries a value from the key_state enumeration.
Notifies the compositor that the modifier and/or group state has
changed, and it should update state.
The client should use wl_keyboard.modifiers event to synchronize its
internal state with seat state.
Keymap must be set before issuing this request.
A virtual keyboard manager allows an application to provide keyboard
input events as if they came from a physical keyboard.
Creates a new virtual keyboard associated to a seat.
If the compositor enables a keyboard to perform arbitrary actions, it
should present an error when an untrusted client requests a new
keyboard.
waypipe-v0.8.6/protocols/wayland-drm.xml 0000664 0000000 0000000 00000017305 14414260423 0020364 0 ustar 00root root 0000000 0000000
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2011 Intel Corporation
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that\n the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
Bitmask of capabilities.
waypipe-v0.8.6/protocols/wayland.xml 0000664 0000000 0000000 00000435643 14414260423 0017615 0 ustar 00root root 0000000 0000000
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2011 Intel Corporation
Copyright © 2012-2013 Collabora, Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice (including the
next paragraph) shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The core global object. This is a special singleton object. It
is used for internal Wayland protocol features.
The sync request asks the server to emit the 'done' event
on the returned wl_callback object. Since requests are
handled in-order and events are delivered in-order, this can
be used as a barrier to ensure all previous requests and the
resulting events have been handled.
The object returned by this request will be destroyed by the
compositor after the callback is fired and as such the client must not
attempt to use it after that point.
The callback_data passed in the callback is the event serial.
This request creates a registry object that allows the client
to list and bind the global objects available from the
compositor.
It should be noted that the server side resources consumed in
response to a get_registry request can only be released when the
client disconnects, not when the client side proxy is destroyed.
Therefore, clients should invoke get_registry as infrequently as
possible to avoid wasting memory.
The error event is sent out when a fatal (non-recoverable)
error has occurred. The object_id argument is the object
where the error occurred, most often in response to a request
to that object. The code identifies the error and is defined
by the object interface. As such, each interface defines its
own set of error codes. The message is a brief description
of the error, for (debugging) convenience.
These errors are global and can be emitted in response to any
server request.
This event is used internally by the object ID management
logic. When a client deletes an object that it had created,
the server will send this event to acknowledge that it has
seen the delete request. When the client receives this event,
it will know that it can safely reuse the object ID.
The singleton global registry object. The server has a number of
global objects that are available to all clients. These objects
typically represent an actual object in the server (for example,
an input device) or they are singleton objects that provide
extension functionality.
When a client creates a registry object, the registry object
will emit a global event for each global currently in the
registry. Globals come and go as a result of device or
monitor hotplugs, reconfiguration or other events, and the
registry will send out global and global_remove events to
keep the client up to date with the changes. To mark the end
of the initial burst of events, the client can use the
wl_display.sync request immediately after calling
wl_display.get_registry.
A client can bind to a global object by using the bind
request. This creates a client-side handle that lets the object
emit events to the client and lets the client invoke requests on
the object.
Binds a new, client-created object to the server using the
specified name as the identifier.
Notify the client of global objects.
The event notifies the client that a global object with
the given name is now available, and it implements the
given version of the given interface.
Notify the client of removed global objects.
This event notifies the client that the global identified
by name is no longer available. If the client bound to
the global using the bind request, the client should now
destroy that object.
The object remains valid and requests to the object will be
ignored until the client destroys it, to avoid races between
the global going away and a client sending a request to it.
Clients can handle the 'done' event to get notified when
the related request is done.
Note, because wl_callback objects are created from multiple independent
factory interfaces, the wl_callback interface is frozen at version 1.
Notify the client when the related request is done.
A compositor. This object is a singleton global. The
compositor is in charge of combining the contents of multiple
surfaces into one displayable output.
Ask the compositor to create a new surface.
Ask the compositor to create a new region.
The wl_shm_pool object encapsulates a piece of memory shared
between the compositor and client. Through the wl_shm_pool
object, the client can allocate shared memory wl_buffer objects.
All objects created through the same pool share the same
underlying mapped memory. Reusing the mapped memory avoids the
setup/teardown overhead and is useful when interactively resizing
a surface or for many small buffers.
Create a wl_buffer object from the pool.
The buffer is created offset bytes into the pool and has
width and height as specified. The stride argument specifies
the number of bytes from the beginning of one row to the beginning
of the next. The format is the pixel format of the buffer and
must be one of those advertised through the wl_shm.format event.
A buffer will keep a reference to the pool it was created from
so it is valid to destroy the pool immediately after creating
a buffer from it.
Destroy the shared memory pool.
The mmapped memory will be released when all
buffers that have been created from this pool
are gone.
This request will cause the server to remap the backing memory
for the pool from the file descriptor passed when the pool was
created, but using the new size. This request can only be
used to make the pool bigger.
This request only changes the amount of bytes that are mmapped
by the server and does not touch the file corresponding to the
file descriptor passed at creation time. It is the client's
responsibility to ensure that the file is at least as big as
the new pool size.
A singleton global object that provides support for shared
memory.
Clients can create wl_shm_pool objects using the create_pool
request.
On binding the wl_shm object one or more format events
are emitted to inform clients about the valid pixel formats
that can be used for buffers.
These errors can be emitted in response to wl_shm requests.
This describes the memory layout of an individual pixel.
All renderers should support argb8888 and xrgb8888 but any other
formats are optional and may not be supported by the particular
renderer in use.
The drm format codes match the macros defined in drm_fourcc.h, except
argb8888 and xrgb8888. The formats actually supported by the compositor
will be reported by the format event.
For all wl_shm formats and unless specified in another protocol
extension, pre-multiplied alpha is used for pixel values.
Create a new wl_shm_pool object.
The pool can be used to create shared memory based buffer
objects. The server will mmap size bytes of the passed file
descriptor, to use as backing memory for the pool.
Informs the client about a valid pixel format that
can be used for buffers. Known formats include
argb8888 and xrgb8888.
A buffer provides the content for a wl_surface. Buffers are
created through factory interfaces such as wl_shm, wp_linux_buffer_params
(from the linux-dmabuf protocol extension) or similar. It has a width and
a height and can be attached to a wl_surface, but the mechanism by which a
client provides and updates the contents is defined by the buffer factory
interface.
If the buffer uses a format that has an alpha channel, the alpha channel
is assumed to be premultiplied in the color channels unless otherwise
specified.
Note, because wl_buffer objects are created from multiple independent
factory interfaces, the wl_buffer interface is frozen at version 1.
Destroy a buffer. If and how you need to release the backing
storage is defined by the buffer factory interface.
For possible side-effects to a surface, see wl_surface.attach.
Sent when this wl_buffer is no longer used by the compositor.
The client is now free to reuse or destroy this buffer and its
backing storage.
If a client receives a release event before the frame callback
requested in the same wl_surface.commit that attaches this
wl_buffer to a surface, then the client is immediately free to
reuse the buffer and its backing storage, and does not need a
second buffer for the next surface content update. Typically
this is possible, when the compositor maintains a copy of the
wl_surface contents, e.g. as a GL texture. This is an important
optimization for GL(ES) compositors with wl_shm clients.
A wl_data_offer represents a piece of data offered for transfer
by another client (the source client). It is used by the
copy-and-paste and drag-and-drop mechanisms. The offer
describes the different mime types that the data can be
converted to and provides the mechanism for transferring the
data directly from the source client.
Indicate that the client can accept the given mime type, or
NULL for not accepted.
For objects of version 2 or older, this request is used by the
client to give feedback whether the client can receive the given
mime type, or NULL if none is accepted; the feedback does not
determine whether the drag-and-drop operation succeeds or not.
For objects of version 3 or newer, this request determines the
final result of the drag-and-drop operation. If the end result
is that no mime types were accepted, the drag-and-drop operation
will be cancelled and the corresponding drag source will receive
wl_data_source.cancelled. Clients may still use this event in
conjunction with wl_data_source.action for feedback.
To transfer the offered data, the client issues this request
and indicates the mime type it wants to receive. The transfer
happens through the passed file descriptor (typically created
with the pipe system call). The source client writes the data
in the mime type representation requested and then closes the
file descriptor.
The receiving client reads from the read end of the pipe until
EOF and then closes its end, at which point the transfer is
complete.
This request may happen multiple times for different mime types,
both before and after wl_data_device.drop. Drag-and-drop destination
clients may preemptively fetch data or examine it more closely to
determine acceptance.
Destroy the data offer.
Sent immediately after creating the wl_data_offer object. One
event per offered mime type.
Notifies the compositor that the drag destination successfully
finished the drag-and-drop operation.
Upon receiving this request, the compositor will emit
wl_data_source.dnd_finished on the drag source client.
It is a client error to perform other requests than
wl_data_offer.destroy after this one. It is also an error to perform
this request after a NULL mime type has been set in
wl_data_offer.accept or no action was received through
wl_data_offer.action.
If wl_data_offer.finish request is received for a non drag and drop
operation, the invalid_finish protocol error is raised.
Sets the actions that the destination side client supports for
this operation. This request may trigger the emission of
wl_data_source.action and wl_data_offer.action events if the compositor
needs to change the selected action.
This request can be called multiple times throughout the
drag-and-drop operation, typically in response to wl_data_device.enter
or wl_data_device.motion events.
This request determines the final result of the drag-and-drop
operation. If the end result is that no action is accepted,
the drag source will receive wl_data_source.cancelled.
The dnd_actions argument must contain only values expressed in the
wl_data_device_manager.dnd_actions enum, and the preferred_action
argument must only contain one of those values set, otherwise it
will result in a protocol error.
While managing an "ask" action, the destination drag-and-drop client
may perform further wl_data_offer.receive requests, and is expected
to perform one last wl_data_offer.set_actions request with a preferred
action other than "ask" (and optionally wl_data_offer.accept) before
requesting wl_data_offer.finish, in order to convey the action selected
by the user. If the preferred action is not in the
wl_data_offer.source_actions mask, an error will be raised.
If the "ask" action is dismissed (e.g. user cancellation), the client
is expected to perform wl_data_offer.destroy right away.
This request can only be made on drag-and-drop offers, a protocol error
will be raised otherwise.
This event indicates the actions offered by the data source. It
will be sent immediately after creating the wl_data_offer object,
or anytime the source side changes its offered actions through
wl_data_source.set_actions.
This event indicates the action selected by the compositor after
matching the source/destination side actions. Only one action (or
none) will be offered here.
This event can be emitted multiple times during the drag-and-drop
operation in response to destination side action changes through
wl_data_offer.set_actions.
This event will no longer be emitted after wl_data_device.drop
happened on the drag-and-drop destination, the client must
honor the last action received, or the last preferred one set
through wl_data_offer.set_actions when handling an "ask" action.
Compositors may also change the selected action on the fly, mainly
in response to keyboard modifier changes during the drag-and-drop
operation.
The most recent action received is always the valid one. Prior to
receiving wl_data_device.drop, the chosen action may change (e.g.
due to keyboard modifiers being pressed). At the time of receiving
wl_data_device.drop the drag-and-drop destination must honor the
last action received.
Action changes may still happen after wl_data_device.drop,
especially on "ask" actions, where the drag-and-drop destination
may choose another action afterwards. Action changes happening
at this stage are always the result of inter-client negotiation, the
compositor shall no longer be able to induce a different action.
Upon "ask" actions, it is expected that the drag-and-drop destination
may potentially choose a different action and/or mime type,
based on wl_data_offer.source_actions and finally chosen by the
user (e.g. popping up a menu with the available options). The
final wl_data_offer.set_actions and wl_data_offer.accept requests
must happen before the call to wl_data_offer.finish.
The wl_data_source object is the source side of a wl_data_offer.
It is created by the source client in a data transfer and
provides a way to describe the offered data and a way to respond
to requests to transfer the data.
This request adds a mime type to the set of mime types
advertised to targets. Can be called several times to offer
multiple types.
Destroy the data source.
Sent when a target accepts pointer_focus or motion events. If
a target does not accept any of the offered types, type is NULL.
Used for feedback during drag-and-drop.
Request for data from the client. Send the data as the
specified mime type over the passed file descriptor, then
close it.
This data source is no longer valid. There are several reasons why
this could happen:
- The data source has been replaced by another data source.
- The drag-and-drop operation was performed, but the drop destination
did not accept any of the mime types offered through
wl_data_source.target.
- The drag-and-drop operation was performed, but the drop destination
did not select any of the actions present in the mask offered through
wl_data_source.action.
- The drag-and-drop operation was performed but didn't happen over a
surface.
- The compositor cancelled the drag-and-drop operation (e.g. compositor
dependent timeouts to avoid stale drag-and-drop transfers).
The client should clean up and destroy this data source.
For objects of version 2 or older, wl_data_source.cancelled will
only be emitted if the data source was replaced by another data
source.
Sets the actions that the source side client supports for this
operation. This request may trigger wl_data_source.action and
wl_data_offer.action events if the compositor needs to change the
selected action.
The dnd_actions argument must contain only values expressed in the
wl_data_device_manager.dnd_actions enum, otherwise it will result
in a protocol error.
This request must be made once only, and can only be made on sources
used in drag-and-drop, so it must be performed before
wl_data_device.start_drag. Attempting to use the source other than
for drag-and-drop will raise a protocol error.
The user performed the drop action. This event does not indicate
acceptance, wl_data_source.cancelled may still be emitted afterwards
if the drop destination does not accept any mime type.
However, this event might however not be received if the compositor
cancelled the drag-and-drop operation before this event could happen.
Note that the data_source may still be used in the future and should
not be destroyed here.
The drop destination finished interoperating with this data
source, so the client is now free to destroy this data source and
free all associated data.
If the action used to perform the operation was "move", the
source can now delete the transferred data.
This event indicates the action selected by the compositor after
matching the source/destination side actions. Only one action (or
none) will be offered here.
This event can be emitted multiple times during the drag-and-drop
operation, mainly in response to destination side changes through
wl_data_offer.set_actions, and as the data device enters/leaves
surfaces.
It is only possible to receive this event after
wl_data_source.dnd_drop_performed if the drag-and-drop operation
ended in an "ask" action, in which case the final wl_data_source.action
event will happen immediately before wl_data_source.dnd_finished.
Compositors may also change the selected action on the fly, mainly
in response to keyboard modifier changes during the drag-and-drop
operation.
The most recent action received is always the valid one. The chosen
action may change alongside negotiation (e.g. an "ask" action can turn
into a "move" operation), so the effects of the final action must
always be applied in wl_data_offer.dnd_finished.
Clients can trigger cursor surface changes from this point, so
they reflect the current action.
There is one wl_data_device per seat which can be obtained
from the global wl_data_device_manager singleton.
A wl_data_device provides access to inter-client data transfer
mechanisms such as copy-and-paste and drag-and-drop.
This request asks the compositor to start a drag-and-drop
operation on behalf of the client.
The source argument is the data source that provides the data
for the eventual data transfer. If source is NULL, enter, leave
and motion events are sent only to the client that initiated the
drag and the client is expected to handle the data passing
internally. If source is destroyed, the drag-and-drop session will be
cancelled.
The origin surface is the surface where the drag originates and
the client must have an active implicit grab that matches the
serial.
The icon surface is an optional (can be NULL) surface that
provides an icon to be moved around with the cursor. Initially,
the top-left corner of the icon surface is placed at the cursor
hotspot, but subsequent wl_surface.attach request can move the
relative position. Attach requests must be confirmed with
wl_surface.commit as usual. The icon surface is given the role of
a drag-and-drop icon. If the icon surface already has another role,
it raises a protocol error.
The input region is ignored for wl_surfaces with the role of a
drag-and-drop icon.
This request asks the compositor to set the selection
to the data from the source on behalf of the client.
To unset the selection, set the source to NULL.
The data_offer event introduces a new wl_data_offer object,
which will subsequently be used in either the
data_device.enter event (for drag-and-drop) or the
data_device.selection event (for selections). Immediately
following the data_device.data_offer event, the new data_offer
object will send out data_offer.offer events to describe the
mime types it offers.
This event is sent when an active drag-and-drop pointer enters
a surface owned by the client. The position of the pointer at
enter time is provided by the x and y arguments, in surface-local
coordinates.
This event is sent when the drag-and-drop pointer leaves the
surface and the session ends. The client must destroy the
wl_data_offer introduced at enter time at this point.
This event is sent when the drag-and-drop pointer moves within
the currently focused surface. The new position of the pointer
is provided by the x and y arguments, in surface-local
coordinates.
The event is sent when a drag-and-drop operation is ended
because the implicit grab is removed.
The drag-and-drop destination is expected to honor the last action
received through wl_data_offer.action, if the resulting action is
"copy" or "move", the destination can still perform
wl_data_offer.receive requests, and is expected to end all
transfers with a wl_data_offer.finish request.
If the resulting action is "ask", the action will not be considered
final. The drag-and-drop destination is expected to perform one last
wl_data_offer.set_actions request, or wl_data_offer.destroy in order
to cancel the operation.
The selection event is sent out to notify the client of a new
wl_data_offer for the selection for this device. The
data_device.data_offer and the data_offer.offer events are
sent out immediately before this event to introduce the data
offer object. The selection event is sent to a client
immediately before receiving keyboard focus and when a new
selection is set while the client has keyboard focus. The
data_offer is valid until a new data_offer or NULL is received
or until the client loses keyboard focus. Switching surface with
keyboard focus within the same client doesn't mean a new selection
will be sent. The client must destroy the previous selection
data_offer, if any, upon receiving this event.
This request destroys the data device.
The wl_data_device_manager is a singleton global object that
provides access to inter-client data transfer mechanisms such as
copy-and-paste and drag-and-drop. These mechanisms are tied to
a wl_seat and this interface lets a client get a wl_data_device
corresponding to a wl_seat.
Depending on the version bound, the objects created from the bound
wl_data_device_manager object will have different requirements for
functioning properly. See wl_data_source.set_actions,
wl_data_offer.accept and wl_data_offer.finish for details.
Create a new data source.
Create a new data device for a given seat.
This is a bitmask of the available/preferred actions in a
drag-and-drop operation.
In the compositor, the selected action is a result of matching the
actions offered by the source and destination sides. "action" events
with a "none" action will be sent to both source and destination if
there is no match. All further checks will effectively happen on
(source actions ∩ destination actions).
In addition, compositors may also pick different actions in
reaction to key modifiers being pressed. One common design that
is used in major toolkits (and the behavior recommended for
compositors) is:
- If no modifiers are pressed, the first match (in bit order)
will be used.
- Pressing Shift selects "move", if enabled in the mask.
- Pressing Control selects "copy", if enabled in the mask.
Behavior beyond that is considered implementation-dependent.
Compositors may for example bind other modifiers (like Alt/Meta)
or drags initiated with other buttons than BTN_LEFT to specific
actions (e.g. "ask").
This interface is implemented by servers that provide
desktop-style user interfaces.
It allows clients to associate a wl_shell_surface with
a basic surface.
Note! This protocol is deprecated and not intended for production use.
For desktop-style user interfaces, use xdg_shell. Compositors and clients
should not implement this interface.
Create a shell surface for an existing surface. This gives
the wl_surface the role of a shell surface. If the wl_surface
already has another role, it raises a protocol error.
Only one shell surface can be associated with a given surface.
An interface that may be implemented by a wl_surface, for
implementations that provide a desktop-style user interface.
It provides requests to treat surfaces like toplevel, fullscreen
or popup windows, move, resize or maximize them, associate
metadata like title and class, etc.
On the server side the object is automatically destroyed when
the related wl_surface is destroyed. On the client side,
wl_shell_surface_destroy() must be called before destroying
the wl_surface object.
A client must respond to a ping event with a pong request or
the client may be deemed unresponsive.
Start a pointer-driven move of the surface.
This request must be used in response to a button press event.
The server may ignore move requests depending on the state of
the surface (e.g. fullscreen or maximized).
These values are used to indicate which edge of a surface
is being dragged in a resize operation. The server may
use this information to adapt its behavior, e.g. choose
an appropriate cursor image.
Start a pointer-driven resizing of the surface.
This request must be used in response to a button press event.
The server may ignore resize requests depending on the state of
the surface (e.g. fullscreen or maximized).
Map the surface as a toplevel surface.
A toplevel surface is not fullscreen, maximized or transient.
These flags specify details of the expected behaviour
of transient surfaces. Used in the set_transient request.
Map the surface relative to an existing surface.
The x and y arguments specify the location of the upper left
corner of the surface relative to the upper left corner of the
parent surface, in surface-local coordinates.
The flags argument controls details of the transient behaviour.
Hints to indicate to the compositor how to deal with a conflict
between the dimensions of the surface and the dimensions of the
output. The compositor is free to ignore this parameter.
Map the surface as a fullscreen surface.
If an output parameter is given then the surface will be made
fullscreen on that output. If the client does not specify the
output then the compositor will apply its policy - usually
choosing the output on which the surface has the biggest surface
area.
The client may specify a method to resolve a size conflict
between the output size and the surface size - this is provided
through the method parameter.
The framerate parameter is used only when the method is set
to "driver", to indicate the preferred framerate. A value of 0
indicates that the client does not care about framerate. The
framerate is specified in mHz, that is framerate of 60000 is 60Hz.
A method of "scale" or "driver" implies a scaling operation of
the surface, either via a direct scaling operation or a change of
the output mode. This will override any kind of output scaling, so
that mapping a surface with a buffer size equal to the mode can
fill the screen independent of buffer_scale.
A method of "fill" means we don't scale up the buffer, however
any output scale is applied. This means that you may run into
an edge case where the application maps a buffer with the same
size of the output mode but buffer_scale 1 (thus making a
surface larger than the output). In this case it is allowed to
downscale the results to fit the screen.
The compositor must reply to this request with a configure event
with the dimensions for the output on which the surface will
be made fullscreen.
Map the surface as a popup.
A popup surface is a transient surface with an added pointer
grab.
An existing implicit grab will be changed to owner-events mode,
and the popup grab will continue after the implicit grab ends
(i.e. releasing the mouse button does not cause the popup to
be unmapped).
The popup grab continues until the window is destroyed or a
mouse button is pressed in any other client's window. A click
in any of the client's surfaces is reported as normal, however,
clicks in other clients' surfaces will be discarded and trigger
the callback.
The x and y arguments specify the location of the upper left
corner of the surface relative to the upper left corner of the
parent surface, in surface-local coordinates.
Map the surface as a maximized surface.
If an output parameter is given then the surface will be
maximized on that output. If the client does not specify the
output then the compositor will apply its policy - usually
choosing the output on which the surface has the biggest surface
area.
The compositor will reply with a configure event telling
the expected new surface size. The operation is completed
on the next buffer attach to this surface.
A maximized surface typically fills the entire output it is
bound to, except for desktop elements such as panels. This is
the main difference between a maximized shell surface and a
fullscreen shell surface.
The details depend on the compositor implementation.
Set a short title for the surface.
This string may be used to identify the surface in a task bar,
window list, or other user interface elements provided by the
compositor.
The string must be encoded in UTF-8.
Set a class for the surface.
The surface class identifies the general class of applications
to which the surface belongs. A common convention is to use the
file name (or the full path if it is a non-standard location) of
the application's .desktop file as the class.
Ping a client to check if it is receiving events and sending
requests. A client is expected to reply with a pong request.
The configure event asks the client to resize its surface.
The size is a hint, in the sense that the client is free to
ignore it if it doesn't resize, pick a smaller size (to
satisfy aspect ratio or resize in steps of NxM pixels).
The edges parameter provides a hint about how the surface
was resized. The client may use this information to decide
how to adjust its content to the new size (e.g. a scrolling
area might adjust its content position to leave the viewable
content unmoved).
The client is free to dismiss all but the last configure
event it received.
The width and height arguments specify the size of the window
in surface-local coordinates.
The popup_done event is sent out when a popup grab is broken,
that is, when the user clicks a surface that doesn't belong
to the client owning the popup surface.
A surface is a rectangular area that may be displayed on zero
or more outputs, and shown any number of times at the compositor's
discretion. They can present wl_buffers, receive user input, and
define a local coordinate system.
The size of a surface (and relative positions on it) is described
in surface-local coordinates, which may differ from the buffer
coordinates of the pixel content, in case a buffer_transform
or a buffer_scale is used.
A surface without a "role" is fairly useless: a compositor does
not know where, when or how to present it. The role is the
purpose of a wl_surface. Examples of roles are a cursor for a
pointer (as set by wl_pointer.set_cursor), a drag icon
(wl_data_device.start_drag), a sub-surface
(wl_subcompositor.get_subsurface), and a window as defined by a
shell protocol (e.g. wl_shell.get_shell_surface).
A surface can have only one role at a time. Initially a
wl_surface does not have a role. Once a wl_surface is given a
role, it is set permanently for the whole lifetime of the
wl_surface object. Giving the current role again is allowed,
unless explicitly forbidden by the relevant interface
specification.
Surface roles are given by requests in other interfaces such as
wl_pointer.set_cursor. The request should explicitly mention
that this request gives a role to a wl_surface. Often, this
request also creates a new protocol object that represents the
role and adds additional functionality to wl_surface. When a
client wants to destroy a wl_surface, they must destroy this role
object before the wl_surface, otherwise a defunct_role_object error is
sent.
Destroying the role object does not remove the role from the
wl_surface, but it may stop the wl_surface from "playing the role".
For instance, if a wl_subsurface object is destroyed, the wl_surface
it was created for will be unmapped and forget its position and
z-order. It is allowed to create a wl_subsurface for the same
wl_surface again, but it is not allowed to use the wl_surface as
a cursor (cursor is a different role than sub-surface, and role
switching is not allowed).
These errors can be emitted in response to wl_surface requests.
Deletes the surface and invalidates its object ID.
Set a buffer as the content of this surface.
The new size of the surface is calculated based on the buffer
size transformed by the inverse buffer_transform and the
inverse buffer_scale. This means that at commit time the supplied
buffer size must be an integer multiple of the buffer_scale. If
that's not the case, an invalid_size error is sent.
The x and y arguments specify the location of the new pending
buffer's upper left corner, relative to the current buffer's upper
left corner, in surface-local coordinates. In other words, the
x and y, combined with the new surface size define in which
directions the surface's size changes. Setting anything other than 0
as x and y arguments is discouraged, and should instead be replaced
with using the separate wl_surface.offset request.
When the bound wl_surface version is 5 or higher, passing any
non-zero x or y is a protocol violation, and will result in an
'invalid_offset' error being raised. The x and y arguments are ignored
and do not change the pending state. To achieve equivalent semantics,
use wl_surface.offset.
Surface contents are double-buffered state, see wl_surface.commit.
The initial surface contents are void; there is no content.
wl_surface.attach assigns the given wl_buffer as the pending
wl_buffer. wl_surface.commit makes the pending wl_buffer the new
surface contents, and the size of the surface becomes the size
calculated from the wl_buffer, as described above. After commit,
there is no pending buffer until the next attach.
Committing a pending wl_buffer allows the compositor to read the
pixels in the wl_buffer. The compositor may access the pixels at
any time after the wl_surface.commit request. When the compositor
will not access the pixels anymore, it will send the
wl_buffer.release event. Only after receiving wl_buffer.release,
the client may reuse the wl_buffer. A wl_buffer that has been
attached and then replaced by another attach instead of committed
will not receive a release event, and is not used by the
compositor.
If a pending wl_buffer has been committed to more than one wl_surface,
the delivery of wl_buffer.release events becomes undefined. A well
behaved client should not rely on wl_buffer.release events in this
case. Alternatively, a client could create multiple wl_buffer objects
from the same backing storage or use wp_linux_buffer_release.
Destroying the wl_buffer after wl_buffer.release does not change
the surface contents. Destroying the wl_buffer before wl_buffer.release
is allowed as long as the underlying buffer storage isn't re-used (this
can happen e.g. on client process termination). However, if the client
destroys the wl_buffer before receiving the wl_buffer.release event and
mutates the underlying buffer storage, the surface contents become
undefined immediately.
If wl_surface.attach is sent with a NULL wl_buffer, the
following wl_surface.commit will remove the surface content.
This request is used to describe the regions where the pending
buffer is different from the current surface contents, and where
the surface therefore needs to be repainted. The compositor
ignores the parts of the damage that fall outside of the surface.
Damage is double-buffered state, see wl_surface.commit.
The damage rectangle is specified in surface-local coordinates,
where x and y specify the upper left corner of the damage rectangle.
The initial value for pending damage is empty: no damage.
wl_surface.damage adds pending damage: the new pending damage
is the union of old pending damage and the given rectangle.
wl_surface.commit assigns pending damage as the current damage,
and clears pending damage. The server will clear the current
damage as it repaints the surface.
Note! New clients should not use this request. Instead damage can be
posted with wl_surface.damage_buffer which uses buffer coordinates
instead of surface coordinates.
Request a notification when it is a good time to start drawing a new
frame, by creating a frame callback. This is useful for throttling
redrawing operations, and driving animations.
When a client is animating on a wl_surface, it can use the 'frame'
request to get notified when it is a good time to draw and commit the
next frame of animation. If the client commits an update earlier than
that, it is likely that some updates will not make it to the display,
and the client is wasting resources by drawing too often.
The frame request will take effect on the next wl_surface.commit.
The notification will only be posted for one frame unless
requested again. For a wl_surface, the notifications are posted in
the order the frame requests were committed.
The server must send the notifications so that a client
will not send excessive updates, while still allowing
the highest possible update rate for clients that wait for the reply
before drawing again. The server should give some time for the client
to draw and commit after sending the frame callback events to let it
hit the next output refresh.
A server should avoid signaling the frame callbacks if the
surface is not visible in any way, e.g. the surface is off-screen,
or completely obscured by other opaque surfaces.
The object returned by this request will be destroyed by the
compositor after the callback is fired and as such the client must not
attempt to use it after that point.
The callback_data passed in the callback is the current time, in
milliseconds, with an undefined base.
This request sets the region of the surface that contains
opaque content.
The opaque region is an optimization hint for the compositor
that lets it optimize the redrawing of content behind opaque
regions. Setting an opaque region is not required for correct
behaviour, but marking transparent content as opaque will result
in repaint artifacts.
The opaque region is specified in surface-local coordinates.
The compositor ignores the parts of the opaque region that fall
outside of the surface.
Opaque region is double-buffered state, see wl_surface.commit.
wl_surface.set_opaque_region changes the pending opaque region.
wl_surface.commit copies the pending region to the current region.
Otherwise, the pending and current regions are never changed.
The initial value for an opaque region is empty. Setting the pending
opaque region has copy semantics, and the wl_region object can be
destroyed immediately. A NULL wl_region causes the pending opaque
region to be set to empty.
This request sets the region of the surface that can receive
pointer and touch events.
Input events happening outside of this region will try the next
surface in the server surface stack. The compositor ignores the
parts of the input region that fall outside of the surface.
The input region is specified in surface-local coordinates.
Input region is double-buffered state, see wl_surface.commit.
wl_surface.set_input_region changes the pending input region.
wl_surface.commit copies the pending region to the current region.
Otherwise the pending and current regions are never changed,
except cursor and icon surfaces are special cases, see
wl_pointer.set_cursor and wl_data_device.start_drag.
The initial value for an input region is infinite. That means the
whole surface will accept input. Setting the pending input region
has copy semantics, and the wl_region object can be destroyed
immediately. A NULL wl_region causes the input region to be set
to infinite.
Surface state (input, opaque, and damage regions, attached buffers,
etc.) is double-buffered. Protocol requests modify the pending state,
as opposed to the current state in use by the compositor. A commit
request atomically applies all pending state, replacing the current
state. After commit, the new pending state is as documented for each
related request.
On commit, a pending wl_buffer is applied first, and all other state
second. This means that all coordinates in double-buffered state are
relative to the new wl_buffer coming into use, except for
wl_surface.attach itself. If there is no pending wl_buffer, the
coordinates are relative to the current surface contents.
All requests that need a commit to become effective are documented
to affect double-buffered state.
Other interfaces may add further double-buffered surface state.
This is emitted whenever a surface's creation, movement, or resizing
results in some part of it being within the scanout region of an
output.
Note that a surface may be overlapping with zero or more outputs.
This is emitted whenever a surface's creation, movement, or resizing
results in it no longer having any part of it within the scanout region
of an output.
Clients should not use the number of outputs the surface is on for frame
throttling purposes. The surface might be hidden even if no leave event
has been sent, and the compositor might expect new surface content
updates even if no enter event has been sent. The frame event should be
used instead.
This request sets an optional transformation on how the compositor
interprets the contents of the buffer attached to the surface. The
accepted values for the transform parameter are the values for
wl_output.transform.
Buffer transform is double-buffered state, see wl_surface.commit.
A newly created surface has its buffer transformation set to normal.
wl_surface.set_buffer_transform changes the pending buffer
transformation. wl_surface.commit copies the pending buffer
transformation to the current one. Otherwise, the pending and current
values are never changed.
The purpose of this request is to allow clients to render content
according to the output transform, thus permitting the compositor to
use certain optimizations even if the display is rotated. Using
hardware overlays and scanning out a client buffer for fullscreen
surfaces are examples of such optimizations. Those optimizations are
highly dependent on the compositor implementation, so the use of this
request should be considered on a case-by-case basis.
Note that if the transform value includes 90 or 270 degree rotation,
the width of the buffer will become the surface height and the height
of the buffer will become the surface width.
If transform is not one of the values from the
wl_output.transform enum the invalid_transform protocol error
is raised.
This request sets an optional scaling factor on how the compositor
interprets the contents of the buffer attached to the window.
Buffer scale is double-buffered state, see wl_surface.commit.
A newly created surface has its buffer scale set to 1.
wl_surface.set_buffer_scale changes the pending buffer scale.
wl_surface.commit copies the pending buffer scale to the current one.
Otherwise, the pending and current values are never changed.
The purpose of this request is to allow clients to supply higher
resolution buffer data for use on high resolution outputs. It is
intended that you pick the same buffer scale as the scale of the
output that the surface is displayed on. This means the compositor
can avoid scaling when rendering the surface on that output.
Note that if the scale is larger than 1, then you have to attach
a buffer that is larger (by a factor of scale in each dimension)
than the desired surface size.
If scale is not positive the invalid_scale protocol error is
raised.
This request is used to describe the regions where the pending
buffer is different from the current surface contents, and where
the surface therefore needs to be repainted. The compositor
ignores the parts of the damage that fall outside of the surface.
Damage is double-buffered state, see wl_surface.commit.
The damage rectangle is specified in buffer coordinates,
where x and y specify the upper left corner of the damage rectangle.
The initial value for pending damage is empty: no damage.
wl_surface.damage_buffer adds pending damage: the new pending
damage is the union of old pending damage and the given rectangle.
wl_surface.commit assigns pending damage as the current damage,
and clears pending damage. The server will clear the current
damage as it repaints the surface.
This request differs from wl_surface.damage in only one way - it
takes damage in buffer coordinates instead of surface-local
coordinates. While this generally is more intuitive than surface
coordinates, it is especially desirable when using wp_viewport
or when a drawing library (like EGL) is unaware of buffer scale
and buffer transform.
Note: Because buffer transformation changes and damage requests may
be interleaved in the protocol stream, it is impossible to determine
the actual mapping between surface and buffer damage until
wl_surface.commit time. Therefore, compositors wishing to take both
kinds of damage into account will have to accumulate damage from the
two requests separately and only transform from one to the other
after receiving the wl_surface.commit.
The x and y arguments specify the location of the new pending
buffer's upper left corner, relative to the current buffer's upper
left corner, in surface-local coordinates. In other words, the
x and y, combined with the new surface size define in which
directions the surface's size changes.
Surface location offset is double-buffered state, see
wl_surface.commit.
This request is semantically equivalent to and the replaces the x and y
arguments in the wl_surface.attach request in wl_surface versions prior
to 5. See wl_surface.attach for details.
This event indicates the preferred buffer scale for this surface. It is
sent whenever the compositor's preference changes.
It is intended that scaling aware clients use this event to scale their
content and use wl_surface.set_buffer_scale to indicate the scale they
have rendered with. This allows clients to supply a higher detail
buffer.
This event indicates the preferred buffer transform for this surface.
It is sent whenever the compositor's preference changes.
It is intended that transform aware clients use this event to apply the
transform to their content and use wl_surface.set_buffer_transform to
indicate the transform they have rendered with.
A seat is a group of keyboards, pointer and touch devices. This
object is published as a global during start up, or when such a
device is hot plugged. A seat typically has a pointer and
maintains a keyboard focus and a pointer focus.
This is a bitmask of capabilities this seat has; if a member is
set, then it is present on the seat.
These errors can be emitted in response to wl_seat requests.
This is emitted whenever a seat gains or loses the pointer,
keyboard or touch capabilities. The argument is a capability
enum containing the complete set of capabilities this seat has.
When the pointer capability is added, a client may create a
wl_pointer object using the wl_seat.get_pointer request. This object
will receive pointer events until the capability is removed in the
future.
When the pointer capability is removed, a client should destroy the
wl_pointer objects associated with the seat where the capability was
removed, using the wl_pointer.release request. No further pointer
events will be received on these objects.
In some compositors, if a seat regains the pointer capability and a
client has a previously obtained wl_pointer object of version 4 or
less, that object may start sending pointer events again. This
behavior is considered a misinterpretation of the intended behavior
and must not be relied upon by the client. wl_pointer objects of
version 5 or later must not send events if created before the most
recent event notifying the client of an added pointer capability.
The above behavior also applies to wl_keyboard and wl_touch with the
keyboard and touch capabilities, respectively.
The ID provided will be initialized to the wl_pointer interface
for this seat.
This request only takes effect if the seat has the pointer
capability, or has had the pointer capability in the past.
It is a protocol violation to issue this request on a seat that has
never had the pointer capability. The missing_capability error will
be sent in this case.
The ID provided will be initialized to the wl_keyboard interface
for this seat.
This request only takes effect if the seat has the keyboard
capability, or has had the keyboard capability in the past.
It is a protocol violation to issue this request on a seat that has
never had the keyboard capability. The missing_capability error will
be sent in this case.
The ID provided will be initialized to the wl_touch interface
for this seat.
This request only takes effect if the seat has the touch
capability, or has had the touch capability in the past.
It is a protocol violation to issue this request on a seat that has
never had the touch capability. The missing_capability error will
be sent in this case.
In a multi-seat configuration the seat name can be used by clients to
help identify which physical devices the seat represents.
The seat name is a UTF-8 string with no convention defined for its
contents. Each name is unique among all wl_seat globals. The name is
only guaranteed to be unique for the current compositor instance.
The same seat names are used for all clients. Thus, the name can be
shared across processes to refer to a specific wl_seat global.
The name event is sent after binding to the seat global. This event is
only sent once per seat object, and the name does not change over the
lifetime of the wl_seat global.
Compositors may re-use the same seat name if the wl_seat global is
destroyed and re-created later.
Using this request a client can tell the server that it is not going to
use the seat object anymore.
The wl_pointer interface represents one or more input devices,
such as mice, which control the pointer location and pointer_focus
of a seat.
The wl_pointer interface generates motion, enter and leave
events for the surfaces that the pointer is located over,
and button and axis events for button presses, button releases
and scrolling.
Set the pointer surface, i.e., the surface that contains the
pointer image (cursor). This request gives the surface the role
of a cursor. If the surface already has another role, it raises
a protocol error.
The cursor actually changes only if the pointer
focus for this device is one of the requesting client's surfaces
or the surface parameter is the current pointer surface. If
there was a previous surface set with this request it is
replaced. If surface is NULL, the pointer image is hidden.
The parameters hotspot_x and hotspot_y define the position of
the pointer surface relative to the pointer location. Its
top-left corner is always at (x, y) - (hotspot_x, hotspot_y),
where (x, y) are the coordinates of the pointer location, in
surface-local coordinates.
On surface.attach requests to the pointer surface, hotspot_x
and hotspot_y are decremented by the x and y parameters
passed to the request. Attach must be confirmed by
wl_surface.commit as usual.
The hotspot can also be updated by passing the currently set
pointer surface to this request with new values for hotspot_x
and hotspot_y.
The input region is ignored for wl_surfaces with the role of
a cursor. When the use as a cursor ends, the wl_surface is
unmapped.
The serial parameter must match the latest wl_pointer.enter
serial number sent to the client. Otherwise the request will be
ignored.
Notification that this seat's pointer is focused on a certain
surface.
When a seat's focus enters a surface, the pointer image
is undefined and a client should respond to this event by setting
an appropriate pointer image with the set_cursor request.
Notification that this seat's pointer is no longer focused on
a certain surface.
The leave notification is sent before the enter notification
for the new focus.
Notification of pointer location change. The arguments
surface_x and surface_y are the location relative to the
focused surface.
Describes the physical state of a button that produced the button
event.
Mouse button click and release notifications.
The location of the click is given by the last motion or
enter event.
The time argument is a timestamp with millisecond
granularity, with an undefined base.
The button is a button code as defined in the Linux kernel's
linux/input-event-codes.h header file, e.g. BTN_LEFT.
Any 16-bit button code value is reserved for future additions to the
kernel's event code list. All other button codes above 0xFFFF are
currently undefined but may be used in future versions of this
protocol.
Describes the axis types of scroll events.
Scroll and other axis notifications.
For scroll events (vertical and horizontal scroll axes), the
value parameter is the length of a vector along the specified
axis in a coordinate space identical to those of motion events,
representing a relative movement along the specified axis.
For devices that support movements non-parallel to axes multiple
axis events will be emitted.
When applicable, for example for touch pads, the server can
choose to emit scroll events where the motion vector is
equivalent to a motion event vector.
When applicable, a client can transform its content relative to the
scroll distance.
Using this request a client can tell the server that it is not going to
use the pointer object anymore.
This request destroys the pointer proxy object, so clients must not call
wl_pointer_destroy() after using this request.
Indicates the end of a set of events that logically belong together.
A client is expected to accumulate the data in all events within the
frame before proceeding.
All wl_pointer events before a wl_pointer.frame event belong
logically together. For example, in a diagonal scroll motion the
compositor will send an optional wl_pointer.axis_source event, two
wl_pointer.axis events (horizontal and vertical) and finally a
wl_pointer.frame event. The client may use this information to
calculate a diagonal vector for scrolling.
When multiple wl_pointer.axis events occur within the same frame,
the motion vector is the combined motion of all events.
When a wl_pointer.axis and a wl_pointer.axis_stop event occur within
the same frame, this indicates that axis movement in one axis has
stopped but continues in the other axis.
When multiple wl_pointer.axis_stop events occur within the same
frame, this indicates that these axes stopped in the same instance.
A wl_pointer.frame event is sent for every logical event group,
even if the group only contains a single wl_pointer event.
Specifically, a client may get a sequence: motion, frame, button,
frame, axis, frame, axis_stop, frame.
The wl_pointer.enter and wl_pointer.leave events are logical events
generated by the compositor and not the hardware. These events are
also grouped by a wl_pointer.frame. When a pointer moves from one
surface to another, a compositor should group the
wl_pointer.leave event within the same wl_pointer.frame.
However, a client must not rely on wl_pointer.leave and
wl_pointer.enter being in the same wl_pointer.frame.
Compositor-specific policies may require the wl_pointer.leave and
wl_pointer.enter event being split across multiple wl_pointer.frame
groups.
Describes the source types for axis events. This indicates to the
client how an axis event was physically generated; a client may
adjust the user interface accordingly. For example, scroll events
from a "finger" source may be in a smooth coordinate space with
kinetic scrolling whereas a "wheel" source may be in discrete steps
of a number of lines.
The "continuous" axis source is a device generating events in a
continuous coordinate space, but using something other than a
finger. One example for this source is button-based scrolling where
the vertical motion of a device is converted to scroll events while
a button is held down.
The "wheel tilt" axis source indicates that the actual device is a
wheel but the scroll event is not caused by a rotation but a
(usually sideways) tilt of the wheel.
Source information for scroll and other axes.
This event does not occur on its own. It is sent before a
wl_pointer.frame event and carries the source information for
all events within that frame.
The source specifies how this event was generated. If the source is
wl_pointer.axis_source.finger, a wl_pointer.axis_stop event will be
sent when the user lifts the finger off the device.
If the source is wl_pointer.axis_source.wheel,
wl_pointer.axis_source.wheel_tilt or
wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event may
or may not be sent. Whether a compositor sends an axis_stop event
for these sources is hardware-specific and implementation-dependent;
clients must not rely on receiving an axis_stop event for these
scroll sources and should treat scroll sequences from these scroll
sources as unterminated by default.
This event is optional. If the source is unknown for a particular
axis event sequence, no event is sent.
Only one wl_pointer.axis_source event is permitted per frame.
The order of wl_pointer.axis_discrete and wl_pointer.axis_source is
not guaranteed.
Stop notification for scroll and other axes.
For some wl_pointer.axis_source types, a wl_pointer.axis_stop event
is sent to notify a client that the axis sequence has terminated.
This enables the client to implement kinetic scrolling.
See the wl_pointer.axis_source documentation for information on when
this event may be generated.
Any wl_pointer.axis events with the same axis_source after this
event should be considered as the start of a new axis motion.
The timestamp is to be interpreted identical to the timestamp in the
wl_pointer.axis event. The timestamp value may be the same as a
preceding wl_pointer.axis event.
Discrete step information for scroll and other axes.
This event carries the axis value of the wl_pointer.axis event in
discrete steps (e.g. mouse wheel clicks).
This event is deprecated with wl_pointer version 8 - this event is not
sent to clients supporting version 8 or later.
This event does not occur on its own, it is coupled with a
wl_pointer.axis event that represents this axis value on a
continuous scale. The protocol guarantees that each axis_discrete
event is always followed by exactly one axis event with the same
axis number within the same wl_pointer.frame. Note that the protocol
allows for other events to occur between the axis_discrete and
its coupled axis event, including other axis_discrete or axis
events. A wl_pointer.frame must not contain more than one axis_discrete
event per axis type.
This event is optional; continuous scrolling devices
like two-finger scrolling on touchpads do not have discrete
steps and do not generate this event.
The discrete value carries the directional information. e.g. a value
of -2 is two steps towards the negative direction of this axis.
The axis number is identical to the axis number in the associated
axis event.
The order of wl_pointer.axis_discrete and wl_pointer.axis_source is
not guaranteed.
Discrete high-resolution scroll information.
This event carries high-resolution wheel scroll information,
with each multiple of 120 representing one logical scroll step
(a wheel detent). For example, an axis_value120 of 30 is one quarter of
a logical scroll step in the positive direction, a value120 of
-240 are two logical scroll steps in the negative direction within the
same hardware event.
Clients that rely on discrete scrolling should accumulate the
value120 to multiples of 120 before processing the event.
The value120 must not be zero.
This event replaces the wl_pointer.axis_discrete event in clients
supporting wl_pointer version 8 or later.
Where a wl_pointer.axis_source event occurs in the same
wl_pointer.frame, the axis source applies to this event.
The order of wl_pointer.axis_value120 and wl_pointer.axis_source is
not guaranteed.
This specifies the direction of the physical motion that caused a
wl_pointer.axis event, relative to the wl_pointer.axis direction.
Relative directional information of the entity causing the axis
motion.
For a wl_pointer.axis event, the wl_pointer.axis_relative_direction
event specifies the movement direction of the entity causing the
wl_pointer.axis event. For example:
- if a user's fingers on a touchpad move down and this
causes a wl_pointer.axis vertical_scroll down event, the physical
direction is 'identical'
- if a user's fingers on a touchpad move down and this causes a
wl_pointer.axis vertical_scroll up scroll up event ('natural
scrolling'), the physical direction is 'inverted'.
A client may use this information to adjust scroll motion of
components. Specifically, enabling natural scrolling causes the
content to change direction compared to traditional scrolling.
Some widgets like volume control sliders should usually match the
physical direction regardless of whether natural scrolling is
active. This event enables clients to match the scroll direction of
a widget to the physical direction.
This event does not occur on its own, it is coupled with a
wl_pointer.axis event that represents this axis value.
The protocol guarantees that each axis_relative_direction event is
always followed by exactly one axis event with the same
axis number within the same wl_pointer.frame. Note that the protocol
allows for other events to occur between the axis_relative_direction
and its coupled axis event.
The axis number is identical to the axis number in the associated
axis event.
The order of wl_pointer.axis_relative_direction,
wl_pointer.axis_discrete and wl_pointer.axis_source is not
guaranteed.
The wl_keyboard interface represents one or more keyboards
associated with a seat.
This specifies the format of the keymap provided to the
client with the wl_keyboard.keymap event.
This event provides a file descriptor to the client which can be
memory-mapped in read-only mode to provide a keyboard mapping
description.
From version 7 onwards, the fd must be mapped with MAP_PRIVATE by
the recipient, as MAP_SHARED may fail.
Notification that this seat's keyboard focus is on a certain
surface.
The compositor must send the wl_keyboard.modifiers event after this
event.
Notification that this seat's keyboard focus is no longer on
a certain surface.
The leave notification is sent before the enter notification
for the new focus.
After this event client must assume that all keys, including modifiers,
are lifted and also it must stop key repeating if there's some going on.
Describes the physical state of a key that produced the key event.
A key was pressed or released.
The time argument is a timestamp with millisecond
granularity, with an undefined base.
The key is a platform-specific key code that can be interpreted
by feeding it to the keyboard mapping (see the keymap event).
If this event produces a change in modifiers, then the resulting
wl_keyboard.modifiers event must be sent after this event.
Notifies clients that the modifier and/or group state has
changed, and it should update its local state.
Informs the client about the keyboard's repeat rate and delay.
This event is sent as soon as the wl_keyboard object has been created,
and is guaranteed to be received by the client before any key press
event.
Negative values for either rate or delay are illegal. A rate of zero
will disable any repeating (regardless of the value of delay).
This event can be sent later on as well with a new value if necessary,
so clients should continue listening for the event past the creation
of wl_keyboard.
The wl_touch interface represents a touchscreen
associated with a seat.
Touch interactions can consist of one or more contacts.
For each contact, a series of events is generated, starting
with a down event, followed by zero or more motion events,
and ending with an up event. Events relating to the same
contact point can be identified by the ID of the sequence.
A new touch point has appeared on the surface. This touch point is
assigned a unique ID. Future events from this touch point reference
this ID. The ID ceases to be valid after a touch up event and may be
reused in the future.
The touch point has disappeared. No further events will be sent for
this touch point and the touch point's ID is released and may be
reused in a future touch down event.
A touch point has changed coordinates.
Indicates the end of a set of events that logically belong together.
A client is expected to accumulate the data in all events within the
frame before proceeding.
A wl_touch.frame terminates at least one event but otherwise no
guarantee is provided about the set of events within a frame. A client
must assume that any state not updated in a frame is unchanged from the
previously known state.
Sent if the compositor decides the touch stream is a global
gesture. No further events are sent to the clients from that
particular gesture. Touch cancellation applies to all touch points
currently active on this client's surface. The client is
responsible for finalizing the touch points, future touch points on
this surface may reuse the touch point ID.
Sent when a touchpoint has changed its shape.
This event does not occur on its own. It is sent before a
wl_touch.frame event and carries the new shape information for
any previously reported, or new touch points of that frame.
Other events describing the touch point such as wl_touch.down,
wl_touch.motion or wl_touch.orientation may be sent within the
same wl_touch.frame. A client should treat these events as a single
logical touch point update. The order of wl_touch.shape,
wl_touch.orientation and wl_touch.motion is not guaranteed.
A wl_touch.down event is guaranteed to occur before the first
wl_touch.shape event for this touch ID but both events may occur within
the same wl_touch.frame.
A touchpoint shape is approximated by an ellipse through the major and
minor axis length. The major axis length describes the longer diameter
of the ellipse, while the minor axis length describes the shorter
diameter. Major and minor are orthogonal and both are specified in
surface-local coordinates. The center of the ellipse is always at the
touchpoint location as reported by wl_touch.down or wl_touch.move.
This event is only sent by the compositor if the touch device supports
shape reports. The client has to make reasonable assumptions about the
shape if it did not receive this event.
Sent when a touchpoint has changed its orientation.
This event does not occur on its own. It is sent before a
wl_touch.frame event and carries the new shape information for
any previously reported, or new touch points of that frame.
Other events describing the touch point such as wl_touch.down,
wl_touch.motion or wl_touch.shape may be sent within the
same wl_touch.frame. A client should treat these events as a single
logical touch point update. The order of wl_touch.shape,
wl_touch.orientation and wl_touch.motion is not guaranteed.
A wl_touch.down event is guaranteed to occur before the first
wl_touch.orientation event for this touch ID but both events may occur
within the same wl_touch.frame.
The orientation describes the clockwise angle of a touchpoint's major
axis to the positive surface y-axis and is normalized to the -180 to
+180 degree range. The granularity of orientation depends on the touch
device, some devices only support binary rotation values between 0 and
90 degrees.
This event is only sent by the compositor if the touch device supports
orientation reports.
An output describes part of the compositor geometry. The
compositor works in the 'compositor coordinate system' and an
output corresponds to a rectangular area in that space that is
actually visible. This typically corresponds to a monitor that
displays part of the compositor space. This object is published
as global during start up, or when a monitor is hotplugged.
This enumeration describes how the physical
pixels on an output are laid out.
This describes the transform that a compositor will apply to a
surface to compensate for the rotation or mirroring of an
output device.
The flipped values correspond to an initial flip around a
vertical axis followed by rotation.
The purpose is mainly to allow clients to render accordingly and
tell the compositor, so that for fullscreen surfaces, the
compositor will still be able to scan out directly from client
surfaces.
The geometry event describes geometric properties of the output.
The event is sent when binding to the output object and whenever
any of the properties change.
The physical size can be set to zero if it doesn't make sense for this
output (e.g. for projectors or virtual outputs).
The geometry event will be followed by a done event (starting from
version 2).
Note: wl_output only advertises partial information about the output
position and identification. Some compositors, for instance those not
implementing a desktop-style output layout or those exposing virtual
outputs, might fake this information. Instead of using x and y, clients
should use xdg_output.logical_position. Instead of using make and model,
clients should use name and description.
These flags describe properties of an output mode.
They are used in the flags bitfield of the mode event.
The mode event describes an available mode for the output.
The event is sent when binding to the output object and there
will always be one mode, the current mode. The event is sent
again if an output changes mode, for the mode that is now
current. In other words, the current mode is always the last
mode that was received with the current flag set.
Non-current modes are deprecated. A compositor can decide to only
advertise the current mode and never send other modes. Clients
should not rely on non-current modes.
The size of a mode is given in physical hardware units of
the output device. This is not necessarily the same as
the output size in the global compositor space. For instance,
the output may be scaled, as described in wl_output.scale,
or transformed, as described in wl_output.transform. Clients
willing to retrieve the output size in the global compositor
space should use xdg_output.logical_size instead.
The vertical refresh rate can be set to zero if it doesn't make
sense for this output (e.g. for virtual outputs).
The mode event will be followed by a done event (starting from
version 2).
Clients should not use the refresh rate to schedule frames. Instead,
they should use the wl_surface.frame event or the presentation-time
protocol.
Note: this information is not always meaningful for all outputs. Some
compositors, such as those exposing virtual outputs, might fake the
refresh rate or the size.
This event is sent after all other properties have been
sent after binding to the output object and after any
other property changes done after that. This allows
changes to the output properties to be seen as
atomic, even if they happen via multiple events.
This event contains scaling geometry information
that is not in the geometry event. It may be sent after
binding the output object or if the output scale changes
later. If it is not sent, the client should assume a
scale of 1.
A scale larger than 1 means that the compositor will
automatically scale surface buffers by this amount
when rendering. This is used for very high resolution
displays where applications rendering at the native
resolution would be too small to be legible.
It is intended that scaling aware clients track the
current output of a surface, and if it is on a scaled
output it should use wl_surface.set_buffer_scale with
the scale of the output. That way the compositor can
avoid scaling the surface, and the client can supply
a higher detail image.
The scale event will be followed by a done event.
Using this request a client can tell the server that it is not going to
use the output object anymore.
Many compositors will assign user-friendly names to their outputs, show
them to the user, allow the user to refer to an output, etc. The client
may wish to know this name as well to offer the user similar behaviors.
The name is a UTF-8 string with no convention defined for its contents.
Each name is unique among all wl_output globals. The name is only
guaranteed to be unique for the compositor instance.
The same output name is used for all clients for a given wl_output
global. Thus, the name can be shared across processes to refer to a
specific wl_output global.
The name is not guaranteed to be persistent across sessions, thus cannot
be used to reliably identify an output in e.g. configuration files.
Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
not assume that the name is a reflection of an underlying DRM connector,
X11 connection, etc.
The name event is sent after binding the output object. This event is
only sent once per output object, and the name does not change over the
lifetime of the wl_output global.
Compositors may re-use the same output name if the wl_output global is
destroyed and re-created later. Compositors should avoid re-using the
same name if possible.
The name event will be followed by a done event.
Many compositors can produce human-readable descriptions of their
outputs. The client may wish to know this description as well, e.g. for
output selection purposes.
The description is a UTF-8 string with no convention defined for its
contents. The description is not guaranteed to be unique among all
wl_output globals. Examples might include 'Foocorp 11" Display' or
'Virtual X11 output via :1'.
The description event is sent after binding the output object and
whenever the description changes. The description is optional, and may
not be sent at all.
The description event will be followed by a done event.
A region object describes an area.
Region objects are used to describe the opaque and input
regions of a surface.
Destroy the region. This will invalidate the object ID.
Add the specified rectangle to the region.
Subtract the specified rectangle from the region.
The global interface exposing sub-surface compositing capabilities.
A wl_surface, that has sub-surfaces associated, is called the
parent surface. Sub-surfaces can be arbitrarily nested and create
a tree of sub-surfaces.
The root surface in a tree of sub-surfaces is the main
surface. The main surface cannot be a sub-surface, because
sub-surfaces must always have a parent.
A main surface with its sub-surfaces forms a (compound) window.
For window management purposes, this set of wl_surface objects is
to be considered as a single window, and it should also behave as
such.
The aim of sub-surfaces is to offload some of the compositing work
within a window from clients to the compositor. A prime example is
a video player with decorations and video in separate wl_surface
objects. This should allow the compositor to pass YUV video buffer
processing to dedicated overlay hardware when possible.
Informs the server that the client will not be using this
protocol object anymore. This does not affect any other
objects, wl_subsurface objects included.
Create a sub-surface interface for the given surface, and
associate it with the given parent surface. This turns a
plain wl_surface into a sub-surface.
The to-be sub-surface must not already have another role, and it
must not have an existing wl_subsurface object. Otherwise the
bad_surface protocol error is raised.
Adding sub-surfaces to a parent is a double-buffered operation on the
parent (see wl_surface.commit). The effect of adding a sub-surface
becomes visible on the next time the state of the parent surface is
applied.
The parent surface must not be one of the child surface's descendants,
and the parent must be different from the child surface, otherwise the
bad_parent protocol error is raised.
This request modifies the behaviour of wl_surface.commit request on
the sub-surface, see the documentation on wl_subsurface interface.
An additional interface to a wl_surface object, which has been
made a sub-surface. A sub-surface has one parent surface. A
sub-surface's size and position are not limited to that of the parent.
Particularly, a sub-surface is not automatically clipped to its
parent's area.
A sub-surface becomes mapped, when a non-NULL wl_buffer is applied
and the parent surface is mapped. The order of which one happens
first is irrelevant. A sub-surface is hidden if the parent becomes
hidden, or if a NULL wl_buffer is applied. These rules apply
recursively through the tree of surfaces.
The behaviour of a wl_surface.commit request on a sub-surface
depends on the sub-surface's mode. The possible modes are
synchronized and desynchronized, see methods
wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized
mode caches the wl_surface state to be applied when the parent's
state gets applied, and desynchronized mode applies the pending
wl_surface state directly. A sub-surface is initially in the
synchronized mode.
Sub-surfaces also have another kind of state, which is managed by
wl_subsurface requests, as opposed to wl_surface requests. This
state includes the sub-surface position relative to the parent
surface (wl_subsurface.set_position), and the stacking order of
the parent and its sub-surfaces (wl_subsurface.place_above and
.place_below). This state is applied when the parent surface's
wl_surface state is applied, regardless of the sub-surface's mode.
As the exception, set_sync and set_desync are effective immediately.
The main surface can be thought to be always in desynchronized mode,
since it does not have a parent in the sub-surfaces sense.
Even if a sub-surface is in desynchronized mode, it will behave as
in synchronized mode, if its parent surface behaves as in
synchronized mode. This rule is applied recursively throughout the
tree of surfaces. This means, that one can set a sub-surface into
synchronized mode, and then assume that all its child and grand-child
sub-surfaces are synchronized, too, without explicitly setting them.
Destroying a sub-surface takes effect immediately. If you need to
synchronize the removal of a sub-surface to the parent surface update,
unmap the sub-surface first by attaching a NULL wl_buffer, update parent,
and then destroy the sub-surface.
If the parent wl_surface object is destroyed, the sub-surface is
unmapped.
The sub-surface interface is removed from the wl_surface object
that was turned into a sub-surface with a
wl_subcompositor.get_subsurface request. The wl_surface's association
to the parent is deleted. The wl_surface is unmapped immediately.
This schedules a sub-surface position change.
The sub-surface will be moved so that its origin (top left
corner pixel) will be at the location x, y of the parent surface
coordinate system. The coordinates are not restricted to the parent
surface area. Negative values are allowed.
The scheduled coordinates will take effect whenever the state of the
parent surface is applied. When this happens depends on whether the
parent surface is in synchronized mode or not. See
wl_subsurface.set_sync and wl_subsurface.set_desync for details.
If more than one set_position request is invoked by the client before
the commit of the parent surface, the position of a new request always
replaces the scheduled position from any previous request.
The initial position is 0, 0.
This sub-surface is taken from the stack, and put back just
above the reference surface, changing the z-order of the sub-surfaces.
The reference surface must be one of the sibling surfaces, or the
parent surface. Using any other surface, including this sub-surface,
will cause a protocol error.
The z-order is double-buffered. Requests are handled in order and
applied immediately to a pending state. The final pending state is
copied to the active state the next time the state of the parent
surface is applied. When this happens depends on whether the parent
surface is in synchronized mode or not. See wl_subsurface.set_sync and
wl_subsurface.set_desync for details.
A new sub-surface is initially added as the top-most in the stack
of its siblings and parent.
The sub-surface is placed just below the reference surface.
See wl_subsurface.place_above.
Change the commit behaviour of the sub-surface to synchronized
mode, also described as the parent dependent mode.
In synchronized mode, wl_surface.commit on a sub-surface will
accumulate the committed state in a cache, but the state will
not be applied and hence will not change the compositor output.
The cached state is applied to the sub-surface immediately after
the parent surface's state is applied. This ensures atomic
updates of the parent and all its synchronized sub-surfaces.
Applying the cached state will invalidate the cache, so further
parent surface commits do not (re-)apply old state.
See wl_subsurface for the recursive effect of this mode.
Change the commit behaviour of the sub-surface to desynchronized
mode, also described as independent or freely running mode.
In desynchronized mode, wl_surface.commit on a sub-surface will
apply the pending state directly, without caching, as happens
normally with a wl_surface. Calling wl_surface.commit on the
parent surface has no effect on the sub-surface's wl_surface
state. This mode allows a sub-surface to be updated on its own.
If cached state exists when wl_surface.commit is called in
desynchronized mode, the pending state is added to the cached
state, and applied as a whole. This invalidates the cache.
Note: even if a sub-surface is set to desynchronized, a parent
sub-surface may override it to behave as synchronized. For details,
see wl_subsurface.
If a surface's parent surface behaves as desynchronized, then
the cached state is applied on set_desync.
waypipe-v0.8.6/protocols/wlr-data-control-unstable-v1.xml 0000664 0000000 0000000 00000027416 14414260423 0023501 0 ustar 00root root 0000000 0000000
Copyright © 2018 Simon Ser
Copyright © 2019 Ivan Molodetskikh
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
This protocol allows a privileged client to control data devices. In
particular, the client will be able to manage the current selection and take
the role of a clipboard manager.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
This interface is a manager that allows creating per-seat data device
controls.
Create a new data source.
Create a data device that can be used to manage a seat's selection.
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
This interface allows a client to manage a seat's selection.
When the seat is destroyed, this object becomes inert.
This request asks the compositor to set the selection to the data from
the source on behalf of the client.
The given source may not be used in any further set_selection or
set_primary_selection requests. Attempting to use a previously used
source is a protocol error.
To unset the selection, set the source to NULL.
Destroys the data device object.
The data_offer event introduces a new wlr_data_control_offer object,
which will subsequently be used in either the
wlr_data_control_device.selection event (for the regular clipboard
selections) or the wlr_data_control_device.primary_selection event (for
the primary clipboard selections). Immediately following the
wlr_data_control_device.data_offer event, the new data_offer object
will send out wlr_data_control_offer.offer events to describe the MIME
types it offers.
The selection event is sent out to notify the client of a new
wlr_data_control_offer for the selection for this device. The
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
events are sent out immediately before this event to introduce the data
offer object. The selection event is sent to a client when a new
selection is set. The wlr_data_control_offer is valid until a new
wlr_data_control_offer or NULL is received. The client must destroy the
previous selection wlr_data_control_offer, if any, upon receiving this
event.
The first selection event is sent upon binding the
wlr_data_control_device object.
This data control object is no longer valid and should be destroyed by
the client.
The primary_selection event is sent out to notify the client of a new
wlr_data_control_offer for the primary selection for this device. The
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
events are sent out immediately before this event to introduce the data
offer object. The primary_selection event is sent to a client when a
new primary selection is set. The wlr_data_control_offer is valid until
a new wlr_data_control_offer or NULL is received. The client must
destroy the previous primary selection wlr_data_control_offer, if any,
upon receiving this event.
If the compositor supports primary selection, the first
primary_selection event is sent upon binding the
wlr_data_control_device object.
This request asks the compositor to set the primary selection to the
data from the source on behalf of the client.
The given source may not be used in any further set_selection or
set_primary_selection requests. Attempting to use a previously used
source is a protocol error.
To unset the primary selection, set the source to NULL.
The compositor will ignore this request if it does not support primary
selection.
The wlr_data_control_source object is the source side of a
wlr_data_control_offer. It is created by the source client in a data
transfer and provides a way to describe the offered data and a way to
respond to requests to transfer the data.
This request adds a MIME type to the set of MIME types advertised to
targets. Can be called several times to offer multiple types.
Calling this after wlr_data_control_device.set_selection is a protocol
error.
Destroys the data source object.
Request for data from the client. Send the data as the specified MIME
type over the passed file descriptor, then close it.
This data source is no longer valid. The data source has been replaced
by another data source.
The client should clean up and destroy this data source.
A wlr_data_control_offer represents a piece of data offered for transfer
by another client (the source client). The offer describes the different
MIME types that the data can be converted to and provides the mechanism
for transferring the data directly from the source client.
To transfer the offered data, the client issues this request and
indicates the MIME type it wants to receive. The transfer happens
through the passed file descriptor (typically created with the pipe
system call). The source client writes the data in the MIME type
representation requested and then closes the file descriptor.
The receiving client reads from the read end of the pipe until EOF and
then closes its end, at which point the transfer is complete.
This request may happen multiple times for different MIME types.
Destroys the data offer object.
Sent immediately after creating the wlr_data_control_offer object.
One event per offered MIME type.
waypipe-v0.8.6/protocols/wlr-export-dmabuf-unstable-v1.xml 0000664 0000000 0000000 00000021727 14414260423 0023666 0 ustar 00root root 0000000 0000000
Copyright © 2018 Rostislav Pehlivanov
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
An interface to capture surfaces in an efficient way by exporting DMA-BUFs.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
This object is a manager with which to start capturing from sources.
Capture the next frame of an entire output.
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
This object represents a single DMA-BUF frame.
If the capture is successful, the compositor will first send a "frame"
event, followed by one or several "object". When the frame is available
for readout, the "ready" event is sent.
If the capture failed, the "cancel" event is sent. This can happen anytime
before the "ready" event.
Once either a "ready" or a "cancel" event is received, the client should
destroy the frame. Once an "object" event is received, the client is
responsible for closing the associated file descriptor.
All frames are read-only and may not be written into or altered.
Special flags that should be respected by the client.
Main event supplying the client with information about the frame. If the
capture didn't fail, this event is always emitted first before any other
events.
This event is followed by a number of "object" as specified by the
"num_objects" argument.
Event which serves to supply the client with the file descriptors
containing the data for each object.
After receiving this event, the client must always close the file
descriptor as soon as they're done with it and even if the frame fails.
This event is sent as soon as the frame is presented, indicating it is
available for reading. This event includes the time at which
presentation happened at.
The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
each component being an unsigned 32-bit value. Whole seconds are in
tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
and the additional fractional part in tv_nsec as nanoseconds. Hence,
for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
may have an arbitrary offset at start.
After receiving this event, the client should destroy this object.
Indicates reason for cancelling the frame.
If the capture failed or if the frame is no longer valid after the
"frame" event has been emitted, this event will be used to inform the
client to scrap the frame.
If the failure is temporary, the client may capture again the same
source. If the failure is permanent, any further attempts to capture the
same source will fail again.
After receiving this event, the client should destroy this object.
Unreferences the frame. This request must be called as soon as its no
longer used.
It can be called at any time by the client. The client will still have
to close any FDs it has been given.
waypipe-v0.8.6/protocols/wlr-gamma-control-unstable-v1.xml 0000664 0000000 0000000 00000012603 14414260423 0023642 0 ustar 00root root 0000000 0000000
Copyright © 2015 Giulio camuffo
Copyright © 2018 Simon Ser
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
This protocol allows a privileged client to set the gamma tables for
outputs.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
This interface is a manager that allows creating per-output gamma
controls.
Create a gamma control that can be used to adjust gamma tables for the
provided output.
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
This interface allows a client to adjust gamma tables for a particular
output.
The client will receive the gamma size, and will then be able to set gamma
tables. At any time the compositor can send a failed event indicating that
this object is no longer valid.
There can only be at most one gamma control object per output, which
has exclusive access to this particular output. When the gamma control
object is destroyed, the gamma table is restored to its original value.
Advertise the size of each gamma ramp.
This event is sent immediately when the gamma control object is created.
Set the gamma table. The file descriptor can be memory-mapped to provide
the raw gamma table, which contains successive gamma ramps for the red,
green and blue channels. Each gamma ramp is an array of 16-byte unsigned
integers which has the same length as the gamma size.
The file descriptor data must have the same length as three times the
gamma size.
This event indicates that the gamma control is no longer valid. This
can happen for a number of reasons, including:
- The output doesn't support gamma tables
- Setting the gamma tables failed
- Another client already has exclusive gamma control for this output
- The compositor has transferred gamma control to another client
Upon receiving this event, the client should destroy this object.
Destroys the gamma control object. If the object is still valid, this
restores the original gamma tables.
waypipe-v0.8.6/protocols/wlr-screencopy-unstable-v1.xml 0000664 0000000 0000000 00000023666 14414260423 0023267 0 ustar 00root root 0000000 0000000
Copyright © 2018 Simon Ser
Copyright © 2019 Andri Yngvason
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
This protocol allows clients to ask the compositor to copy part of the
screen content to a client buffer.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
This object is a manager which offers requests to start capturing from a
source.
Capture the next frame of an entire output.
Capture the next frame of an output's region.
The region is given in output logical coordinates, see
xdg_output.logical_size. The region will be clipped to the output's
extents.
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
This object represents a single frame.
When created, a series of buffer events will be sent, each representing a
supported buffer type. The "buffer_done" event is sent afterwards to
indicate that all supported buffer types have been enumerated. The client
will then be able to send a "copy" request. If the capture is successful,
the compositor will send a "flags" followed by a "ready" event.
For objects version 2 or lower, wl_shm buffers are always supported, ie.
the "buffer" event is guaranteed to be sent.
If the capture failed, the "failed" event is sent. This can happen anytime
before the "ready" event.
Once either a "ready" or a "failed" event is received, the client should
destroy the frame.
Provides information about wl_shm buffer parameters that need to be
used for this frame. This event is sent once after the frame is created
if wl_shm buffers are supported.
Copy the frame to the supplied buffer. The buffer must have a the
correct size, see zwlr_screencopy_frame_v1.buffer and
zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a
supported format.
If the frame is successfully copied, a "flags" and a "ready" events are
sent. Otherwise, a "failed" event is sent.
Provides flags about the frame. This event is sent once before the
"ready" event.
Called as soon as the frame is copied, indicating it is available
for reading. This event includes the time at which presentation happened
at.
The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
each component being an unsigned 32-bit value. Whole seconds are in
tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
and the additional fractional part in tv_nsec as nanoseconds. Hence,
for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
may have an arbitrary offset at start.
After receiving this event, the client should destroy the object.
This event indicates that the attempted frame copy has failed.
After receiving this event, the client should destroy the object.
Destroys the frame. This request can be sent at any time by the client.
Same as copy, except it waits until there is damage to copy.
This event is sent right before the ready event when copy_with_damage is
requested. It may be generated multiple times for each copy_with_damage
request.
The arguments describe a box around an area that has changed since the
last copy request that was derived from the current screencopy manager
instance.
The union of all regions received between the call to copy_with_damage
and a ready event is the total damage since the prior ready event.
Provides information about linux-dmabuf buffer parameters that need to
be used for this frame. This event is sent once after the frame is
created if linux-dmabuf buffers are supported.
This event is sent once after all buffer events have been sent.
The client should proceed to create a buffer of one of the supported
types, and send a "copy" request.
waypipe-v0.8.6/protocols/xdg-shell.xml 0000664 0000000 0000000 00000152663 14414260423 0020043 0 ustar 00root root 0000000 0000000
Copyright © 2008-2013 Kristian Høgsberg
Copyright © 2013 Rafael Antognolli
Copyright © 2013 Jasper St. Pierre
Copyright © 2010-2013 Intel Corporation
Copyright © 2015-2017 Samsung Electronics Co., Ltd
Copyright © 2015-2017 Red Hat Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
The xdg_wm_base interface is exposed as a global object enabling clients
to turn their wl_surfaces into windows in a desktop environment. It
defines the basic functionality needed for clients and the compositor to
create windows that can be dragged, resized, maximized, etc, as well as
creating transient windows such as popup menus.
Destroy this xdg_wm_base object.
Destroying a bound xdg_wm_base object while there are surfaces
still alive created by this xdg_wm_base object instance is illegal
and will result in a protocol error.
Create a positioner object. A positioner object is used to position
surfaces relative to some parent surface. See the interface description
and xdg_surface.get_popup for details.
This creates an xdg_surface for the given surface. While xdg_surface
itself is not a role, the corresponding surface may only be assigned
a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is
illegal to create an xdg_surface for a wl_surface which already has an
assigned role and this will result in a protocol error.
This creates an xdg_surface for the given surface. An xdg_surface is
used as basis to define a role to a given surface, such as xdg_toplevel
or xdg_popup. It also manages functionality shared between xdg_surface
based surface roles.
See the documentation of xdg_surface for more details about what an
xdg_surface is and how it is used.
A client must respond to a ping event with a pong request or
the client may be deemed unresponsive. See xdg_wm_base.ping.
The ping event asks the client if it's still alive. Pass the
serial specified in the event back to the compositor by sending
a "pong" request back with the specified serial. See xdg_wm_base.pong.
Compositors can use this to determine if the client is still
alive. It's unspecified what will happen if the client doesn't
respond to the ping request, or in what timeframe. Clients should
try to respond in a reasonable amount of time.
A compositor is free to ping in any way it wants, but a client must
always respond to any xdg_wm_base object it created.
The xdg_positioner provides a collection of rules for the placement of a
child surface relative to a parent surface. Rules can be defined to ensure
the child surface remains within the visible area's borders, and to
specify how the child surface changes its position, such as sliding along
an axis, or flipping around a rectangle. These positioner-created rules are
constrained by the requirement that a child surface must intersect with or
be at least partially adjacent to its parent surface.
See the various requests for details about possible rules.
At the time of the request, the compositor makes a copy of the rules
specified by the xdg_positioner. Thus, after the request is complete the
xdg_positioner object can be destroyed or reused; further changes to the
object will have no effect on previous usages.
For an xdg_positioner object to be considered complete, it must have a
non-zero size set by set_size, and a non-zero anchor rectangle set by
set_anchor_rect. Passing an incomplete xdg_positioner object when
positioning a surface raises an error.
Notify the compositor that the xdg_positioner will no longer be used.
Set the size of the surface that is to be positioned with the positioner
object. The size is in surface-local coordinates and corresponds to the
window geometry. See xdg_surface.set_window_geometry.
If a zero or negative size is set the invalid_input error is raised.
Specify the anchor rectangle within the parent surface that the child
surface will be placed relative to. The rectangle is relative to the
window geometry as defined by xdg_surface.set_window_geometry of the
parent surface.
When the xdg_positioner object is used to position a child surface, the
anchor rectangle may not extend outside the window geometry of the
positioned child's parent surface.
If a negative size is set the invalid_input error is raised.
Defines the anchor point for the anchor rectangle. The specified anchor
is used derive an anchor point that the child surface will be
positioned relative to. If a corner anchor is set (e.g. 'top_left' or
'bottom_right'), the anchor point will be at the specified corner;
otherwise, the derived anchor point will be centered on the specified
edge, or in the center of the anchor rectangle if no edge is specified.
Defines in what direction a surface should be positioned, relative to
the anchor point of the parent surface. If a corner gravity is
specified (e.g. 'bottom_right' or 'top_left'), then the child surface
will be placed towards the specified gravity; otherwise, the child
surface will be centered over the anchor point on any axis that had no
gravity specified.
The constraint adjustment value define ways the compositor will adjust
the position of the surface, if the unadjusted position would result
in the surface being partly constrained.
Whether a surface is considered 'constrained' is left to the compositor
to determine. For example, the surface may be partly outside the
compositor's defined 'work area', thus necessitating the child surface's
position be adjusted until it is entirely inside the work area.
The adjustments can be combined, according to a defined precedence: 1)
Flip, 2) Slide, 3) Resize.
Don't alter the surface position even if it is constrained on some
axis, for example partially outside the edge of an output.
Slide the surface along the x axis until it is no longer constrained.
First try to slide towards the direction of the gravity on the x axis
until either the edge in the opposite direction of the gravity is
unconstrained or the edge in the direction of the gravity is
constrained.
Then try to slide towards the opposite direction of the gravity on the
x axis until either the edge in the direction of the gravity is
unconstrained or the edge in the opposite direction of the gravity is
constrained.
Slide the surface along the y axis until it is no longer constrained.
First try to slide towards the direction of the gravity on the y axis
until either the edge in the opposite direction of the gravity is
unconstrained or the edge in the direction of the gravity is
constrained.
Then try to slide towards the opposite direction of the gravity on the
y axis until either the edge in the direction of the gravity is
unconstrained or the edge in the opposite direction of the gravity is
constrained.
Invert the anchor and gravity on the x axis if the surface is
constrained on the x axis. For example, if the left edge of the
surface is constrained, the gravity is 'left' and the anchor is
'left', change the gravity to 'right' and the anchor to 'right'.
If the adjusted position also ends up being constrained, the resulting
position of the flip_x adjustment will be the one before the
adjustment.
Invert the anchor and gravity on the y axis if the surface is
constrained on the y axis. For example, if the bottom edge of the
surface is constrained, the gravity is 'bottom' and the anchor is
'bottom', change the gravity to 'top' and the anchor to 'top'.
The adjusted position is calculated given the original anchor
rectangle and offset, but with the new flipped anchor and gravity
values.
If the adjusted position also ends up being constrained, the resulting
position of the flip_y adjustment will be the one before the
adjustment.
Resize the surface horizontally so that it is completely
unconstrained.
Resize the surface vertically so that it is completely unconstrained.
Specify how the window should be positioned if the originally intended
position caused the surface to be constrained, meaning at least
partially outside positioning boundaries set by the compositor. The
adjustment is set by constructing a bitmask describing the adjustment to
be made when the surface is constrained on that axis.
If no bit for one axis is set, the compositor will assume that the child
surface should not change its position on that axis when constrained.
If more than one bit for one axis is set, the order of how adjustments
are applied is specified in the corresponding adjustment descriptions.
The default adjustment is none.
Specify the surface position offset relative to the position of the
anchor on the anchor rectangle and the anchor on the surface. For
example if the anchor of the anchor rectangle is at (x, y), the surface
has the gravity bottom|right, and the offset is (ox, oy), the calculated
surface position will be (x + ox, y + oy). The offset position of the
surface is the one used for constraint testing. See
set_constraint_adjustment.
An example use case is placing a popup menu on top of a user interface
element, while aligning the user interface element of the parent surface
with some user interface element placed somewhere in the popup surface.
When set reactive, the surface is reconstrained if the conditions used
for constraining changed, e.g. the parent window moved.
If the conditions changed and the popup was reconstrained, an
xdg_popup.configure event is sent with updated geometry, followed by an
xdg_surface.configure event.
Set the parent window geometry the compositor should use when
positioning the popup. The compositor may use this information to
determine the future state the popup should be constrained using. If
this doesn't match the dimension of the parent the popup is eventually
positioned against, the behavior is undefined.
The arguments are given in the surface-local coordinate space.
Set the serial of an xdg_surface.configure event this positioner will be
used in response to. The compositor may use this information together
with set_parent_size to determine what future state the popup should be
constrained using.
An interface that may be implemented by a wl_surface, for
implementations that provide a desktop-style user interface.
It provides a base set of functionality required to construct user
interface elements requiring management by the compositor, such as
toplevel windows, menus, etc. The types of functionality are split into
xdg_surface roles.
Creating an xdg_surface does not set the role for a wl_surface. In order
to map an xdg_surface, the client must create a role-specific object
using, e.g., get_toplevel, get_popup. The wl_surface for any given
xdg_surface can have at most one role, and may not be assigned any role
not based on xdg_surface.
A role must be assigned before any other requests are made to the
xdg_surface object.
The client must call wl_surface.commit on the corresponding wl_surface
for the xdg_surface state to take effect.
Creating an xdg_surface from a wl_surface which has a buffer attached or
committed is a client error, and any attempts by a client to attach or
manipulate a buffer prior to the first xdg_surface.configure call must
also be treated as errors.
After creating a role-specific object and setting it up, the client must
perform an initial commit without any buffer attached. The compositor
will reply with an xdg_surface.configure event. The client must
acknowledge it and is then allowed to attach a buffer to map the surface.
Mapping an xdg_surface-based role surface is defined as making it
possible for the surface to be shown by the compositor. Note that
a mapped surface is not guaranteed to be visible once it is mapped.
For an xdg_surface to be mapped by the compositor, the following
conditions must be met:
(1) the client has assigned an xdg_surface-based role to the surface
(2) the client has set and committed the xdg_surface state and the
role-dependent state to the surface
(3) the client has committed a buffer to the surface
A newly-unmapped surface is considered to have met condition (1) out
of the 3 required conditions for mapping a surface if its role surface
has not been destroyed.
Destroy the xdg_surface object. An xdg_surface must only be destroyed
after its role object has been destroyed.
This creates an xdg_toplevel object for the given xdg_surface and gives
the associated wl_surface the xdg_toplevel role.
See the documentation of xdg_toplevel for more details about what an
xdg_toplevel is and how it is used.
This creates an xdg_popup object for the given xdg_surface and gives
the associated wl_surface the xdg_popup role.
If null is passed as a parent, a parent surface must be specified using
some other protocol, before committing the initial state.
See the documentation of xdg_popup for more details about what an
xdg_popup is and how it is used.
The window geometry of a surface is its "visible bounds" from the
user's perspective. Client-side decorations often have invisible
portions like drop-shadows which should be ignored for the
purposes of aligning, placing and constraining windows.
The window geometry is double buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
When maintaining a position, the compositor should treat the (x, y)
coordinate of the window geometry as the top left corner of the window.
A client changing the (x, y) window geometry coordinate should in
general not alter the position of the window.
Once the window geometry of the surface is set, it is not possible to
unset it, and it will remain the same until set_window_geometry is
called again, even if a new subsurface or buffer is attached.
If never set, the value is the full bounds of the surface,
including any subsurfaces. This updates dynamically on every
commit. This unset is meant for extremely simple clients.
The arguments are given in the surface-local coordinate space of
the wl_surface associated with this xdg_surface.
The width and height must be greater than zero. Setting an invalid size
will raise an error. When applied, the effective window geometry will be
the set window geometry clamped to the bounding rectangle of the
combined geometry of the surface of the xdg_surface and the associated
subsurfaces.
When a configure event is received, if a client commits the
surface in response to the configure event, then the client
must make an ack_configure request sometime before the commit
request, passing along the serial of the configure event.
For instance, for toplevel surfaces the compositor might use this
information to move a surface to the top left only when the client has
drawn itself for the maximized or fullscreen state.
If the client receives multiple configure events before it
can respond to one, it only has to ack the last configure event.
A client is not required to commit immediately after sending
an ack_configure request - it may even ack_configure several times
before its next surface commit.
A client may send multiple ack_configure requests before committing, but
only the last request sent before a commit indicates which configure
event the client really is responding to.
The configure event marks the end of a configure sequence. A configure
sequence is a set of one or more events configuring the state of the
xdg_surface, including the final xdg_surface.configure event.
Where applicable, xdg_surface surface roles will during a configure
sequence extend this event as a latched state sent as events before the
xdg_surface.configure event. Such events should be considered to make up
a set of atomically applied configuration states, where the
xdg_surface.configure commits the accumulated state.
Clients should arrange their surface for the new states, and then send
an ack_configure request with the serial sent in this configure event at
some point before committing the new surface.
If the client receives multiple configure events before it can respond
to one, it is free to discard all but the last event it received.
This interface defines an xdg_surface role which allows a surface to,
among other things, set window-like properties such as maximize,
fullscreen, and minimize, set application-specific metadata like title and
id, and well as trigger user interactive operations such as interactive
resize and move.
Unmapping an xdg_toplevel means that the surface cannot be shown
by the compositor until it is explicitly mapped again.
All active operations (e.g., move, resize) are canceled and all
attributes (e.g. title, state, stacking, ...) are discarded for
an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
the state it had right after xdg_surface.get_toplevel. The client
can re-map the toplevel by perfoming a commit without any buffer
attached, waiting for a configure event and handling it as usual (see
xdg_surface description).
Attaching a null buffer to a toplevel unmaps the surface.
This request destroys the role surface and unmaps the surface;
see "Unmapping" behavior in interface section for details.
Set the "parent" of this surface. This surface should be stacked
above the parent surface and all other ancestor surfaces.
Parent windows should be set on dialogs, toolboxes, or other
"auxiliary" surfaces, so that the parent is raised when the dialog
is raised.
Setting a null parent for a child window removes any parent-child
relationship for the child. Setting a null parent for a window which
currently has no parent is a no-op.
If the parent is unmapped then its children are managed as
though the parent of the now-unmapped parent has become the
parent of this surface. If no parent exists for the now-unmapped
parent then the children are managed as though they have no
parent surface.
Set a short title for the surface.
This string may be used to identify the surface in a task bar,
window list, or other user interface elements provided by the
compositor.
The string must be encoded in UTF-8.
Set an application identifier for the surface.
The app ID identifies the general class of applications to which
the surface belongs. The compositor can use this to group multiple
surfaces together, or to determine how to launch a new application.
For D-Bus activatable applications, the app ID is used as the D-Bus
service name.
The compositor shell will try to group application surfaces together
by their app ID. As a best practice, it is suggested to select app
ID's that match the basename of the application's .desktop file.
For example, "org.freedesktop.FooViewer" where the .desktop file is
"org.freedesktop.FooViewer.desktop".
Like other properties, a set_app_id request can be sent after the
xdg_toplevel has been mapped to update the property.
See the desktop-entry specification [0] for more details on
application identifiers and how they relate to well-known D-Bus
names and .desktop files.
[0] http://standards.freedesktop.org/desktop-entry-spec/
Clients implementing client-side decorations might want to show
a context menu when right-clicking on the decorations, giving the
user a menu that they can use to maximize or minimize the window.
This request asks the compositor to pop up such a window menu at
the given position, relative to the local surface coordinates of
the parent surface. There are no guarantees as to what menu items
the window menu contains.
This request must be used in response to some sort of user action
like a button press, key press, or touch down event.
Start an interactive, user-driven move of the surface.
This request must be used in response to some sort of user action
like a button press, key press, or touch down event. The passed
serial is used to determine the type of interactive move (touch,
pointer, etc).
The server may ignore move requests depending on the state of
the surface (e.g. fullscreen or maximized), or if the passed serial
is no longer valid.
If triggered, the surface will lose the focus of the device
(wl_pointer, wl_touch, etc) used for the move. It is up to the
compositor to visually indicate that the move is taking place, such as
updating a pointer cursor, during the move. There is no guarantee
that the device focus will return when the move is completed.
These values are used to indicate which edge of a surface
is being dragged in a resize operation.
Start a user-driven, interactive resize of the surface.
This request must be used in response to some sort of user action
like a button press, key press, or touch down event. The passed
serial is used to determine the type of interactive resize (touch,
pointer, etc).
The server may ignore resize requests depending on the state of
the surface (e.g. fullscreen or maximized).
If triggered, the client will receive configure events with the
"resize" state enum value and the expected sizes. See the "resize"
enum value for more details about what is required. The client
must also acknowledge configure events using "ack_configure". After
the resize is completed, the client will receive another "configure"
event without the resize state.
If triggered, the surface also will lose the focus of the device
(wl_pointer, wl_touch, etc) used for the resize. It is up to the
compositor to visually indicate that the resize is taking place,
such as updating a pointer cursor, during the resize. There is no
guarantee that the device focus will return when the resize is
completed.
The edges parameter specifies how the surface should be resized,
and is one of the values of the resize_edge enum. The compositor
may use this information to update the surface position for
example when dragging the top left corner. The compositor may also
use this information to adapt its behavior, e.g. choose an
appropriate cursor image.
The different state values used on the surface. This is designed for
state values like maximized, fullscreen. It is paired with the
configure event to ensure that both the client and the compositor
setting the state can be synchronized.
States set in this way are double-buffered. They will get applied on
the next commit.
The surface is maximized. The window geometry specified in the configure
event must be obeyed by the client.
The client should draw without shadow or other
decoration outside of the window geometry.
The surface is fullscreen. The window geometry specified in the
configure event is a maximum; the client cannot resize beyond it. For
a surface to cover the whole fullscreened area, the geometry
dimensions must be obeyed by the client. For more details, see
xdg_toplevel.set_fullscreen.
The surface is being resized. The window geometry specified in the
configure event is a maximum; the client cannot resize beyond it.
Clients that have aspect ratio or cell sizing configuration can use
a smaller size, however.
Client window decorations should be painted as if the window is
active. Do not assume this means that the window actually has
keyboard or pointer focus.
The window is currently in a tiled layout and the left edge is
considered to be adjacent to another part of the tiling grid.
The window is currently in a tiled layout and the right edge is
considered to be adjacent to another part of the tiling grid.
The window is currently in a tiled layout and the top edge is
considered to be adjacent to another part of the tiling grid.
The window is currently in a tiled layout and the bottom edge is
considered to be adjacent to another part of the tiling grid.
Set a maximum size for the window.
The client can specify a maximum size so that the compositor does
not try to configure the window beyond this size.
The width and height arguments are in window geometry coordinates.
See xdg_surface.set_window_geometry.
Values set in this way are double-buffered. They will get applied
on the next commit.
The compositor can use this information to allow or disallow
different states like maximize or fullscreen and draw accurate
animations.
Similarly, a tiling window manager may use this information to
place and resize client windows in a more effective way.
The client should not rely on the compositor to obey the maximum
size. The compositor may decide to ignore the values set by the
client and request a larger size.
If never set, or a value of zero in the request, means that the
client has no expected maximum size in the given dimension.
As a result, a client wishing to reset the maximum size
to an unspecified state can use zero for width and height in the
request.
Requesting a maximum size to be smaller than the minimum size of
a surface is illegal and will result in a protocol error.
The width and height must be greater than or equal to zero. Using
strictly negative values for width and height will result in a
protocol error.
Set a minimum size for the window.
The client can specify a minimum size so that the compositor does
not try to configure the window below this size.
The width and height arguments are in window geometry coordinates.
See xdg_surface.set_window_geometry.
Values set in this way are double-buffered. They will get applied
on the next commit.
The compositor can use this information to allow or disallow
different states like maximize or fullscreen and draw accurate
animations.
Similarly, a tiling window manager may use this information to
place and resize client windows in a more effective way.
The client should not rely on the compositor to obey the minimum
size. The compositor may decide to ignore the values set by the
client and request a smaller size.
If never set, or a value of zero in the request, means that the
client has no expected minimum size in the given dimension.
As a result, a client wishing to reset the minimum size
to an unspecified state can use zero for width and height in the
request.
Requesting a minimum size to be larger than the maximum size of
a surface is illegal and will result in a protocol error.
The width and height must be greater than or equal to zero. Using
strictly negative values for width and height will result in a
protocol error.
Maximize the surface.
After requesting that the surface should be maximized, the compositor
will respond by emitting a configure event. Whether this configure
actually sets the window maximized is subject to compositor policies.
The client must then update its content, drawing in the configured
state. The client must also acknowledge the configure when committing
the new content (see ack_configure).
It is up to the compositor to decide how and where to maximize the
surface, for example which output and what region of the screen should
be used.
If the surface was already maximized, the compositor will still emit
a configure event with the "maximized" state.
If the surface is in a fullscreen state, this request has no direct
effect. It may alter the state the surface is returned to when
unmaximized unless overridden by the compositor.
Unmaximize the surface.
After requesting that the surface should be unmaximized, the compositor
will respond by emitting a configure event. Whether this actually
un-maximizes the window is subject to compositor policies.
If available and applicable, the compositor will include the window
geometry dimensions the window had prior to being maximized in the
configure event. The client must then update its content, drawing it in
the configured state. The client must also acknowledge the configure
when committing the new content (see ack_configure).
It is up to the compositor to position the surface after it was
unmaximized; usually the position the surface had before maximizing, if
applicable.
If the surface was already not maximized, the compositor will still
emit a configure event without the "maximized" state.
If the surface is in a fullscreen state, this request has no direct
effect. It may alter the state the surface is returned to when
unmaximized unless overridden by the compositor.
Make the surface fullscreen.
After requesting that the surface should be fullscreened, the
compositor will respond by emitting a configure event. Whether the
client is actually put into a fullscreen state is subject to compositor
policies. The client must also acknowledge the configure when
committing the new content (see ack_configure).
The output passed by the request indicates the client's preference as
to which display it should be set fullscreen on. If this value is NULL,
it's up to the compositor to choose which display will be used to map
this surface.
If the surface doesn't cover the whole output, the compositor will
position the surface in the center of the output and compensate with
with border fill covering the rest of the output. The content of the
border fill is undefined, but should be assumed to be in some way that
attempts to blend into the surrounding area (e.g. solid black).
If the fullscreened surface is not opaque, the compositor must make
sure that other screen content not part of the same surface tree (made
up of subsurfaces, popups or similarly coupled surfaces) are not
visible below the fullscreened surface.
Make the surface no longer fullscreen.
After requesting that the surface should be unfullscreened, the
compositor will respond by emitting a configure event.
Whether this actually removes the fullscreen state of the client is
subject to compositor policies.
Making a surface unfullscreen sets states for the surface based on the following:
* the state(s) it may have had before becoming fullscreen
* any state(s) decided by the compositor
* any state(s) requested by the client while the surface was fullscreen
The compositor may include the previous window geometry dimensions in
the configure event, if applicable.
The client must also acknowledge the configure when committing the new
content (see ack_configure).
Request that the compositor minimize your surface. There is no
way to know if the surface is currently minimized, nor is there
any way to unset minimization on this surface.
If you are looking to throttle redrawing when minimized, please
instead use the wl_surface.frame event for this, as this will
also work with live previews on windows in Alt-Tab, Expose or
similar compositor features.
This configure event asks the client to resize its toplevel surface or
to change its state. The configured state should not be applied
immediately. See xdg_surface.configure for details.
The width and height arguments specify a hint to the window
about how its surface should be resized in window geometry
coordinates. See set_window_geometry.
If the width or height arguments are zero, it means the client
should decide its own window dimension. This may happen when the
compositor needs to configure the state of the surface but doesn't
have any information about any previous or expected dimension.
The states listed in the event specify how the width/height
arguments should be interpreted, and possibly how it should be
drawn.
Clients must send an ack_configure in response to this event. See
xdg_surface.configure and xdg_surface.ack_configure for details.
The close event is sent by the compositor when the user
wants the surface to be closed. This should be equivalent to
the user clicking the close button in client-side decorations,
if your application has any.
This is only a request that the user intends to close the
window. The client may choose to ignore this request, or show
a dialog to ask the user to save their data, etc.
A popup surface is a short-lived, temporary surface. It can be used to
implement for example menus, popovers, tooltips and other similar user
interface concepts.
A popup can be made to take an explicit grab. See xdg_popup.grab for
details.
When the popup is dismissed, a popup_done event will be sent out, and at
the same time the surface will be unmapped. See the xdg_popup.popup_done
event for details.
Explicitly destroying the xdg_popup object will also dismiss the popup and
unmap the surface. Clients that want to dismiss the popup when another
surface of their own is clicked should dismiss the popup using the destroy
request.
A newly created xdg_popup will be stacked on top of all previously created
xdg_popup surfaces associated with the same xdg_toplevel.
The parent of an xdg_popup must be mapped (see the xdg_surface
description) before the xdg_popup itself.
The client must call wl_surface.commit on the corresponding wl_surface
for the xdg_popup state to take effect.
This destroys the popup. Explicitly destroying the xdg_popup
object will also dismiss the popup, and unmap the surface.
If this xdg_popup is not the "topmost" popup, a protocol error
will be sent.
This request makes the created popup take an explicit grab. An explicit
grab will be dismissed when the user dismisses the popup, or when the
client destroys the xdg_popup. This can be done by the user clicking
outside the surface, using the keyboard, or even locking the screen
through closing the lid or a timeout.
If the compositor denies the grab, the popup will be immediately
dismissed.
This request must be used in response to some sort of user action like a
button press, key press, or touch down event. The serial number of the
event should be passed as 'serial'.
The parent of a grabbing popup must either be an xdg_toplevel surface or
another xdg_popup with an explicit grab. If the parent is another
xdg_popup it means that the popups are nested, with this popup now being
the topmost popup.
Nested popups must be destroyed in the reverse order they were created
in, e.g. the only popup you are allowed to destroy at all times is the
topmost one.
When compositors choose to dismiss a popup, they may dismiss every
nested grabbing popup as well. When a compositor dismisses popups, it
will follow the same dismissing order as required from the client.
The parent of a grabbing popup must either be another xdg_popup with an
active explicit grab, or an xdg_popup or xdg_toplevel, if there are no
explicit grabs already taken.
If the topmost grabbing popup is destroyed, the grab will be returned to
the parent of the popup, if that parent previously had an explicit grab.
If the parent is a grabbing popup which has already been dismissed, this
popup will be immediately dismissed. If the parent is a popup that did
not take an explicit grab, an error will be raised.
During a popup grab, the client owning the grab will receive pointer
and touch events for all their surfaces as normal (similar to an
"owner-events" grab in X11 parlance), while the top most grabbing popup
will always have keyboard focus.
This event asks the popup surface to configure itself given the
configuration. The configured state should not be applied immediately.
See xdg_surface.configure for details.
The x and y arguments represent the position the popup was placed at
given the xdg_positioner rule, relative to the upper left corner of the
window geometry of the parent surface.
For version 2 or older, the configure event for an xdg_popup is only
ever sent once for the initial configuration. Starting with version 3,
it may be sent again if the popup is setup with an xdg_positioner with
set_reactive requested, or in response to xdg_popup.reposition requests.
The popup_done event is sent out when a popup is dismissed by the
compositor. The client should destroy the xdg_popup object at this
point.
Reposition an already-mapped popup. The popup will be placed given the
details in the passed xdg_positioner object, and a
xdg_popup.repositioned followed by xdg_popup.configure and
xdg_surface.configure will be emitted in response. Any parameters set
by the previous positioner will be discarded.
The passed token will be sent in the corresponding
xdg_popup.repositioned event. The new popup position will not take
effect until the corresponding configure event is acknowledged by the
client. See xdg_popup.repositioned for details. The token itself is
opaque, and has no other special meaning.
If multiple reposition requests are sent, the compositor may skip all
but the last one.
If the popup is repositioned in response to a configure event for its
parent, the client should send an xdg_positioner.set_parent_configure
and possibly an xdg_positioner.set_parent_size request to allow the
compositor to properly constrain the popup.
If the popup is repositioned together with a parent that is being
resized, but not in response to a configure event, the client should
send an xdg_positioner.set_parent_size request.
The repositioned event is sent as part of a popup configuration
sequence, together with xdg_popup.configure and lastly
xdg_surface.configure to notify the completion of a reposition request.
The repositioned event is to notify about the completion of a
xdg_popup.reposition request. The token argument is the token passed
in the xdg_popup.reposition request.
Immediately after this event is emitted, xdg_popup.configure and
xdg_surface.configure will be sent with the updated size and position,
as well as a new configure serial.
The client should optionally update the content of the popup, but must
acknowledge the new popup configuration for the new position to take
effect. See xdg_surface.ack_configure for details.
waypipe-v0.8.6/src/ 0000775 0000000 0000000 00000000000 14414260423 0014160 5 ustar 00root root 0000000 0000000 waypipe-v0.8.6/src/bench.c 0000664 0000000 0000000 00000030622 14414260423 0015406 0 ustar 00root root 0000000 0000000 /*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "shadow.h"
#include "util.h"
#include
#include
#include
#include
#include
#include
struct compression_range {
enum compression_mode mode;
int min_val;
int max_val;
const char *desc;
};
static const struct compression_range comp_ranges[] = {
{COMP_NONE, 0, 0, "none"},
#ifdef HAS_LZ4
{COMP_LZ4, -10, 16, "lz4"},
#endif
#ifdef HAS_ZSTD
{COMP_ZSTD, -10, 22, "zstd"},
#endif
};
static void *create_text_like_image(size_t size)
{
uint8_t *data = malloc(size);
if (!data) {
return NULL;
}
for (size_t i = 0; i < size; i++) {
size_t step = i / 203 - i / 501;
bool s = step % 2 == 0;
data[i] = (uint8_t)(s ? ((step >> 1) & 0x2) + 0xfe : 0x00);
}
// int f = open("1.rgb", O_RDONLY);
// read(f, data, size);
// close(f);
return data;
}
static void *create_video_like_image(size_t size)
{
uint8_t *data = malloc(size);
if (!data) {
return NULL;
}
for (size_t i = 0; i < size; i++) {
/* primary sequence, with runs, but avoiding obvious repetition
* then add fine grain, a main source of complexity in real
* images
*/
uint32_t noise = (uint32_t)rand() % 2;
data[i] = (uint8_t)(i + i / 101 + i / 33 + noise);
}
// int f = open("0.rgb", O_RDONLY);
// read(f, data, size);
// close(f);
return data;
}
/** Create a shuffled variation of the original image. */
static void perturb(void *data, size_t size)
{
uint8_t *bytes = (uint8_t *)data;
for (int i = 0; i < 50; i++) {
// TODO: avoid redundant motion, and make this very fast
size_t low = (size_t)rand() % size;
size_t high = (size_t)rand() % size;
if (low >= high) {
continue;
}
for (size_t k = 0; k < (high - low) / 2; k++) {
uint8_t tmp = bytes[low + k];
bytes[low + k] = bytes[high - k];
bytes[high - k] = tmp;
}
}
}
struct bench_result {
const struct compression_range *rng;
int level;
float comp_time, dcomp_time;
};
static int float_compare(const void *a, const void *b)
{
float va = *(const float *)a;
float vb = *(const float *)b;
if (va < vb)
return -1;
if (va > vb)
return 1;
return 0;
}
static int compare_bench_result(const void *a, const void *b)
{
const struct bench_result *va = (const struct bench_result *)a;
const struct bench_result *vb = (const struct bench_result *)b;
if (va->comp_time < vb->comp_time)
return -1;
if (va->comp_time > vb->comp_time)
return 1;
return 0;
}
struct diff_comp_results {
/* Compressed packet size, in bytes */
float packet_size;
/* Time to construct compressed diff, in seconds */
float diffcomp_time;
/* Diff size / buffer size */
float diff_frac;
/* Compressed size / original size */
float comp_frac;
};
static int compare_timespec(const struct timespec *a, const struct timespec *b)
{
if (a->tv_sec != b->tv_sec)
return a->tv_sec < b->tv_sec ? -1 : 1;
if (a->tv_nsec != b->tv_nsec)
return a->tv_nsec < b->tv_nsec ? -1 : 1;
return 0;
}
/* requires delta >= 0 */
static struct timespec timespec_add(struct timespec base, int64_t delta_ns)
{
struct timespec ret;
ret.tv_sec = base.tv_sec + delta_ns / 1000000000LL;
ret.tv_nsec = base.tv_nsec + delta_ns % 1000000000LL;
if (ret.tv_nsec > 1000000000LL) {
ret.tv_nsec -= 1000000000LL;
ret.tv_sec++;
}
return ret;
}
static int64_t timespec_sub(struct timespec a, struct timespec b)
{
return (a.tv_sec - b.tv_sec) * 1000000000LL + (a.tv_nsec - b.tv_nsec);
}
#define NSAMPLES 5
static struct bench_result run_sub_bench(bool first,
const struct compression_range *rng, int level,
float bandwidth_mBps, int n_worker_threads, unsigned int seed,
bool text_like, size_t test_size, void *image)
{
/* Reset seed, so that all random image
* perturbations are consistent between runs */
srand(seed);
/* Setup a shadow structure */
struct thread_pool pool;
setup_thread_pool(&pool, rng->mode, level, n_worker_threads);
if (first) {
printf("Running compression level benchmarks, assuming bandwidth=%g MB/s, with %d threads\n",
bandwidth_mBps, pool.nthreads);
}
struct fd_translation_map map;
setup_translation_map(&map, false);
struct wmsg_open_file file_msg;
file_msg.remote_id = 0;
file_msg.file_size = (uint32_t)test_size;
file_msg.size_and_type = transfer_header(
sizeof(struct wmsg_open_file), WMSG_OPEN_FILE);
struct render_data render;
memset(&render, 0, sizeof(render));
render.disabled = true;
render.drm_fd = 1;
render.av_disabled = true;
struct bytebuf msg = {.size = sizeof(struct wmsg_open_file),
.data = (char *)&file_msg};
(void)apply_update(&map, &pool, &render, WMSG_OPEN_FILE, 0, &msg);
struct shadow_fd *sfd = get_shadow_for_rid(&map, 0);
int iter = 0;
float samples[NSAMPLES];
float diff_frac[NSAMPLES], comp_frac[NSAMPLES];
for (; !shutdown_flag && iter < NSAMPLES; iter++) {
/* Reset image state */
memcpy(sfd->mem_local, image, test_size);
memcpy(sfd->mem_mirror, image, test_size);
perturb(sfd->mem_local, test_size);
sfd->is_dirty = true;
damage_everything(&sfd->damage);
/* Create transfer queue */
struct transfer_queue transfer_data;
memset(&transfer_data, 0, sizeof(struct transfer_queue));
pthread_mutex_init(&transfer_data.async_recv_queue.lock, NULL);
struct timespec t0, t1;
clock_gettime(CLOCK_REALTIME, &t0);
collect_update(&pool, sfd, &transfer_data, false);
start_parallel_work(&pool, &transfer_data.async_recv_queue);
/* A restricted main loop, in which transfer blocks are
* instantaneously consumed when previous blocks have been
* 'sent' */
struct timespec next_write_time = {.tv_sec = 0, .tv_nsec = 0};
size_t total_wire_size = 0;
size_t net_diff_size = 0;
while (1) {
uint8_t flush[64];
(void)read(pool.selfpipe_r, flush, sizeof(flush));
/* Run tasks on main thread, just like the main loop */
bool done = false;
struct task_data task;
bool has_task = request_work_task(&pool, &task, &done);
if (has_task) {
run_task(&task, &pool.threads[0]);
pthread_mutex_lock(&pool.work_mutex);
pool.tasks_in_progress--;
pthread_mutex_unlock(&pool.work_mutex);
}
struct timespec cur_time;
clock_gettime(CLOCK_REALTIME, &cur_time);
if (compare_timespec(&next_write_time, &cur_time) < 0) {
transfer_load_async(&transfer_data);
if (transfer_data.start < transfer_data.end) {
struct iovec v =
transfer_data.vecs
[transfer_data.start++];
float delay_s = (float)v.iov_len /
(bandwidth_mBps * 1e6f);
total_wire_size += v.iov_len;
/* Only one message type will be
* produced for diffs */
struct wmsg_buffer_diff *header =
v.iov_base;
net_diff_size += (size_t)(header->diff_size +
header->ntrailing);
/* Advance timer for next receipt */
int64_t delay_ns = (int64_t)(delay_s *
1e9f);
next_write_time = timespec_add(
cur_time, delay_ns);
}
} else {
/* Very short delay, for poll loop */
bool tasks_remaining = false;
pthread_mutex_lock(&pool.work_mutex);
tasks_remaining = pool.stack_count > 0;
pthread_mutex_unlock(&pool.work_mutex);
struct timespec delay_time;
delay_time.tv_sec = 0;
delay_time.tv_nsec = 10000;
if (!tasks_remaining) {
int64_t nsecs_left = timespec_sub(
next_write_time,
cur_time);
if (nsecs_left > 1000000000LL) {
nsecs_left = 1000000000LL;
}
if (nsecs_left > delay_time.tv_nsec) {
delay_time.tv_nsec = nsecs_left;
}
}
nanosleep(&delay_time, NULL);
}
bool all_sent = false;
all_sent = transfer_data.start == transfer_data.end;
if (done && all_sent) {
break;
}
}
finish_update(sfd);
cleanup_transfer_queue(&transfer_data);
clock_gettime(CLOCK_REALTIME, &t1);
struct diff_comp_results r;
r.packet_size = (float)total_wire_size;
r.diffcomp_time = 1.0f * (float)(t1.tv_sec - t0.tv_sec) +
1e-9f * (float)(t1.tv_nsec - t0.tv_nsec);
r.comp_frac = r.packet_size / (float)net_diff_size;
r.diff_frac = (float)net_diff_size / (float)test_size;
samples[iter] = r.diffcomp_time;
diff_frac[iter] = r.diff_frac;
comp_frac[iter] = r.comp_frac;
}
/* Cleanup sfd and helper structures */
cleanup_thread_pool(&pool);
cleanup_translation_map(&map);
qsort(samples, (size_t)iter, sizeof(float), float_compare);
qsort(diff_frac, (size_t)iter, sizeof(float), float_compare);
qsort(comp_frac, (size_t)iter, sizeof(float), float_compare);
/* Using order statistics, because moment statistics a) require
* libm; b) don't work well with outliers. */
float median = samples[iter / 2];
float hiqr = (samples[(iter * 3) / 4] - samples[iter / 4]) / 2;
float dmedian = diff_frac[iter / 2];
float dhiqr = (diff_frac[(iter * 3) / 4] - diff_frac[iter / 4]) / 2;
float cmedian = comp_frac[iter / 2];
float chiqr = (comp_frac[(iter * 3) / 4] - comp_frac[iter / 4]) / 2;
struct bench_result res;
res.rng = rng;
res.level = level;
printf("%s, %s=%d: transfer %f+/-%f sec, diff %f+/-%f, comp %f+/-%f\n",
text_like ? "txt" : "img", rng->desc, level, median,
hiqr, dmedian, dhiqr, cmedian, chiqr);
res.comp_time = median;
res.dcomp_time = hiqr;
return res;
}
int run_bench(float bandwidth_mBps, uint32_t test_size, int n_worker_threads)
{
/* 4MB test image - 1024x1024x4. Any smaller, and unrealistic caching
* speedups may occur */
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
srand((unsigned int)tp.tv_nsec);
void *text_image = create_text_like_image(test_size);
void *vid_image = create_video_like_image(test_size);
if (!text_image || !vid_image) {
free(text_image);
free(vid_image);
wp_error("Failed to allocate test images");
return EXIT_FAILURE;
}
/* Q: store an array of all the modes -> outputs */
// Then sort that array
int ntests = 0;
for (size_t c = 0; c < sizeof(comp_ranges) / sizeof(comp_ranges[0]);
c++) {
ntests += comp_ranges[c].max_val - comp_ranges[c].min_val + 1;
}
/* For the content, the mode is generally consistent */
struct bench_result *tresults =
calloc((size_t)ntests, sizeof(struct bench_result));
struct bench_result *iresults =
calloc((size_t)ntests, sizeof(struct bench_result));
int ntres = 0, nires = 0;
for (int k = 0; k < 2; k++) {
bool text_like = k == 0;
int j = 0;
for (size_t c = 0;
!shutdown_flag &&
c < sizeof(comp_ranges) / sizeof(comp_ranges[0]);
c++) {
for (int lvl = comp_ranges[c].min_val;
!shutdown_flag &&
lvl <= comp_ranges[c].max_val;
lvl++) {
struct bench_result res = run_sub_bench(j == 0,
&comp_ranges[c], lvl,
bandwidth_mBps,
n_worker_threads,
(unsigned int)tp.tv_nsec,
text_like, test_size,
text_like ? text_image
: vid_image);
if (text_like) {
tresults[j++] = res;
ntres++;
} else {
iresults[j++] = res;
nires++;
}
}
}
}
for (int k = 0; k < 2; k++) {
bool text_like = k == 0;
struct bench_result *results = text_like ? tresults : iresults;
int nr = text_like ? ntres : nires;
if (nr <= 0) {
continue;
}
/* Print best recommendation */
qsort(results, (size_t)nr, sizeof(struct bench_result),
compare_bench_result);
struct bench_result best = results[0];
printf("%s, best compression level: \"%s=%d\", with %f+/-%f sec for sample transfer\n",
text_like ? "Text heavy image"
: "Photo-like image",
best.rng->desc, best.level, best.comp_time,
best.dcomp_time);
}
free(tresults);
free(iresults);
free(vid_image);
free(text_image);
return EXIT_SUCCESS;
}
waypipe-v0.8.6/src/client.c 0000664 0000000 0000000 00000055002 14414260423 0015604 0 ustar 00root root 0000000 0000000 /*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "main.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static inline uint32_t conntoken_version(uint32_t header)
{
return header >> 16;
}
static int check_conn_header(uint32_t header, const struct main_config *config,
char *err, size_t err_size)
{
if ((header >> 16) != WAYPIPE_PROTOCOL_VERSION) {
const char *endian_warning = "";
if ((header & CONN_FIXED_BIT) == 0 &&
(header & CONN_UNSET_BIT) != 0) {
endian_warning =
" It is also possible that server endianness does not match client";
}
snprintf(err, err_size,
"Waypipe client is rejecting connection header %08" PRIx32
"; as Waypipe server (application-side) protocol version (%u) is incompatible with Waypipe client protocol version (%u, from waypipe %s). Check that both sides have compatible versions of Waypipe.%s",
header, conntoken_version(header),
WAYPIPE_PROTOCOL_VERSION, WAYPIPE_VERSION,
endian_warning);
return -1;
}
/* Skip the following checks if config is null
* (i.e., called from reconnection loop) */
if (!config) {
return 0;
}
/* For now, reject mismatches in compression format and video coding
* setting, and print an error. Adopting whatever the server asks for
* is a minor security issue -- e.g., video handling is a good target
* for exploits, and compression can cost CPU time, especially if the
* initial connection mechanism were to be expanded to allow setting
* compression level. */
if ((header & CONN_COMPRESSION_MASK) == CONN_ZSTD_COMPRESSION) {
if (config->compression != COMP_ZSTD) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the compression=ZSTD the Waypipe server expected",
compression_mode_to_str(
config->compression));
return -1;
}
} else if ((header & CONN_COMPRESSION_MASK) == CONN_LZ4_COMPRESSION) {
if (config->compression != COMP_LZ4) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the compression=LZ4 the Waypipe server expected",
compression_mode_to_str(
config->compression));
return -1;
}
} else if ((header & CONN_COMPRESSION_MASK) == CONN_NO_COMPRESSION) {
if (config->compression != COMP_NONE) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the compression=NONE the Waypipe server expected",
compression_mode_to_str(
config->compression));
return -1;
}
} else if ((header & CONN_COMPRESSION_MASK) != 0) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client is configured for compression=%s, not the unidentified compression type the Waypipe server expected",
compression_mode_to_str(config->compression));
return -1;
}
if ((header & CONN_VIDEO_MASK) == CONN_VP9_VIDEO) {
if (!config->video_if_possible) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client was not run with video encoding enabled, unlike Waypipe server");
return -1;
}
if (config->video_fmt != VIDEO_VP9) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client was not configured for the VP9 video coding format requested by the Waypipe server");
return -1;
}
} else if ((header & CONN_VIDEO_MASK) == CONN_H264_VIDEO) {
if (!config->video_if_possible) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client was not run with video encoding enabled, unlike Waypipe server");
return -1;
}
if (config->video_fmt != VIDEO_H264) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client was not configured for the VP9 video coding format requested by the Waypipe server");
return -1;
}
} else if ((header & CONN_VIDEO_MASK) == CONN_NO_VIDEO) {
if (config->video_if_possible) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client has video encoding enabled, but Waypipe server does not");
return -1;
}
} else if ((header & CONN_VIDEO_MASK) != 0) {
snprintf(err, err_size,
"Waypipe client is rejecting connection, Waypipe client was not configured for the unidentified video coding format requested by the Waypipe server");
return -1;
}
return 0;
}
static void apply_conn_header(uint32_t header, struct main_config *config)
{
if (header & CONN_NO_DMABUF_SUPPORT) {
if (config) {
config->no_gpu = true;
}
}
// todo: consider allowing to disable video encoding
}
static void write_rejection_message(int channel_fd, char *msg)
{
char buf[512];
size_t len = print_wrapped_error(buf, sizeof(buf), msg);
if (!len) {
wp_error("Failed to print wrapped error for message of length %zu, not enough space",
strlen(msg));
return;
}
ssize_t written = write(channel_fd, buf, len);
if (written != (ssize_t)len) {
wp_error("Failed to send rejection message, only %d bytes of %d written",
(int)written, (int)len);
}
}
static inline bool key_match(
const uint32_t key1[static 3], const uint32_t key2[static 3])
{
return key1[0] == key2[0] && key1[1] == key2[1] && key1[2] == key2[2];
}
static int get_inherited_socket(const char *wayland_socket)
{
uint32_t val;
if (parse_uint32(wayland_socket, &val) == -1 || ((int)val) < 0) {
wp_error("Failed to parse \"%s\" (value of WAYLAND_SOCKET) as a nonnegative integer, exiting",
wayland_socket);
return -1;
}
int fd = (int)val;
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1 && errno == EBADF) {
wp_error("The file descriptor WAYLAND_SOCKET=%d was invalid, exiting",
fd);
return -1;
}
return fd;
}
static int get_display_path(char *path, size_t max_len)
{
const char *display = getenv("WAYLAND_DISPLAY");
if (!display) {
wp_error("WAYLAND_DISPLAY is not set, exiting");
return -1;
}
if (display[0] != '/') {
const char *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
if (!xdg_runtime_dir) {
wp_error("XDG_RUNTIME_DIR is not set, exiting");
return -1;
}
if (multi_strcat(path, max_len, xdg_runtime_dir, "/", display,
NULL) == 0) {
wp_error("full WAYLAND_DISPLAY path '%s' is longer than %z bytes, exiting",
display, max_len);
return -1;
}
} else {
if (strlen(display) + 1 >= max_len) {
wp_error("WAYLAND_DISPLAY='%s' is longer than %zu bytes, exiting",
display, max_len);
return -1;
}
strcpy(path, display);
}
return 0;
}
static int run_single_client_reconnector(
int channelsock, int linkfd, struct connection_token conn_id)
{
int retcode = EXIT_SUCCESS;
while (!shutdown_flag) {
struct pollfd pf[2];
pf[0].fd = channelsock;
pf[0].events = POLLIN;
pf[0].revents = 0;
pf[1].fd = linkfd;
pf[1].events = 0;
pf[1].revents = 0;
int r = poll(pf, 2, -1);
if (r == -1 && errno == EINTR) {
continue;
} else if (r == -1) {
retcode = EXIT_FAILURE;
break;
} else if (r == 0) {
// Nothing to read
continue;
}
if (pf[1].revents & POLLHUP) {
/* Hang up, main thread has closed its link */
break;
}
if (!(pf[0].revents & POLLIN)) {
continue;
}
int newclient = accept(channelsock, NULL, NULL);
if (newclient == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// The wakeup may have been spurious
continue;
}
wp_error("Connection failure: %s", strerror(errno));
retcode = EXIT_FAILURE;
break;
}
wp_debug("Reconnection to oneshot client");
struct connection_token new_conn;
memset(&new_conn, 0, sizeof(new_conn));
if (read(newclient, &new_conn.header,
sizeof(new_conn.header)) !=
sizeof(new_conn.header)) {
wp_error("Failed to get connection id header");
goto done;
}
if (check_conn_header(new_conn.header, NULL, NULL, 0) < 0) {
goto done;
}
if (read(newclient, &new_conn.key, sizeof(new_conn.key)) !=
sizeof(new_conn.key)) {
wp_error("Failed to get connection id key");
goto done;
}
if (!key_match(new_conn.key, conn_id.key)) {
wp_error("Connection attempt with unmatched key");
goto done;
}
bool update = new_conn.header & CONN_RECONNECTABLE_BIT;
if (!update) {
wp_error("Connection token is missing update flag");
goto done;
}
if (send_one_fd(linkfd, newclient) == -1) {
wp_error("Failed to get connection id");
retcode = EXIT_FAILURE;
checked_close(newclient);
break;
}
done:
checked_close(newclient);
}
checked_close(channelsock);
checked_close(linkfd);
return retcode;
}
static int run_single_client(int channelsock, pid_t *eol_pid,
const struct main_config *config, int disp_fd)
{
/* To support reconnection attempts, this mode creates a child
* reconnection watcher process, linked via socketpair */
int retcode = EXIT_SUCCESS;
int chanclient = -1;
struct connection_token conn_id;
memset(&conn_id, 0, sizeof(conn_id));
while (!shutdown_flag) {
int status = -1;
if (wait_for_pid_and_clean(eol_pid, &status, WNOHANG, NULL)) {
eol_pid = 0; // < in case eol_pid is recycled
wp_debug("Child (ssh) died, exiting");
// Copy the exit code
retcode = WEXITSTATUS(status);
break;
}
struct pollfd cs;
cs.fd = channelsock;
cs.events = POLLIN;
cs.revents = 0;
int r = poll(&cs, 1, -1);
if (r == -1) {
if (errno == EINTR) {
// If SIGCHLD, we will check the child.
// If SIGINT, the loop ends
continue;
}
retcode = EXIT_FAILURE;
break;
} else if (r == 0) {
// Nothing to read
continue;
}
chanclient = accept(channelsock, NULL, NULL);
if (chanclient == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// The wakeup may have been spurious
continue;
}
wp_error("Connection failure: %s", strerror(errno));
retcode = EXIT_FAILURE;
break;
}
char err_msg[512];
wp_debug("New connection to client");
if (read(chanclient, &conn_id.header, sizeof(conn_id.header)) !=
sizeof(conn_id.header)) {
wp_error("Failed to get connection id header");
goto fail_cc;
}
if (check_conn_header(conn_id.header, config, err_msg,
sizeof(err_msg)) < 0) {
wp_error("%s", err_msg);
write_rejection_message(chanclient, err_msg);
goto fail_cc;
}
if (read(chanclient, &conn_id.key, sizeof(conn_id.key)) !=
sizeof(conn_id.key)) {
wp_error("Failed to get connection id key");
goto fail_cc;
}
break;
fail_cc:
retcode = EXIT_FAILURE;
checked_close(chanclient);
chanclient = -1;
break;
}
if (retcode == EXIT_FAILURE || shutdown_flag || chanclient == -1) {
checked_close(channelsock);
checked_close(disp_fd);
return retcode;
}
if (conn_id.header & CONN_UPDATE_BIT) {
wp_error("Initial connection token had update flag set");
checked_close(channelsock);
checked_close(disp_fd);
return retcode;
}
/* Fork a reconnection handler, only if the connection is
* reconnectable/has a nonzero id */
int linkfds[2] = {-1, -1};
if (conn_id.header & CONN_RECONNECTABLE_BIT) {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, linkfds) == -1) {
wp_error("Failed to create socketpair: %s",
strerror(errno));
checked_close(chanclient);
return EXIT_FAILURE;
}
pid_t reco_pid = fork();
if (reco_pid == -1) {
wp_error("Fork failure: %s", strerror(errno));
checked_close(chanclient);
return EXIT_FAILURE;
} else if (reco_pid == 0) {
if (linkfds[0] != -1) {
checked_close(linkfds[0]);
}
checked_close(chanclient);
checked_close(disp_fd);
int rc = run_single_client_reconnector(
channelsock, linkfds[1], conn_id);
exit(rc);
}
checked_close(linkfds[1]);
}
checked_close(channelsock);
struct main_config mod_config = *config;
apply_conn_header(conn_id.header, &mod_config);
return main_interface_loop(
chanclient, disp_fd, linkfds[0], &mod_config, true);
}
void send_new_connection_fd(
struct conn_map *connmap, uint32_t key[static 3], int new_fd)
{
for (int i = 0; i < connmap->count; i++) {
if (key_match(connmap->data[i].token.key, key)) {
if (send_one_fd(connmap->data[i].linkfd, new_fd) ==
-1) {
wp_error("Failed to send new connection fd to subprocess: %s",
strerror(errno));
}
break;
}
}
}
static void handle_new_client_connection(int cwd_fd, struct pollfd *other_fds,
int n_other_fds, int chanclient, struct conn_map *connmap,
const struct main_config *config,
const struct socket_path disp_path,
const struct connection_token *conn_id)
{
bool reconnectable = conn_id->header & CONN_RECONNECTABLE_BIT;
if (reconnectable && buf_ensure_size(connmap->count + 1,
sizeof(struct conn_addr),
&connmap->size,
(void **)&connmap->data) == -1) {
wp_error("Failed to allocate space to track connection");
goto fail_cc;
}
int linkfds[2] = {-1, -1};
if (reconnectable) {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, linkfds) == -1) {
wp_error("Failed to create socketpair: %s",
strerror(errno));
goto fail_cc;
}
}
pid_t npid = fork();
if (npid == 0) {
// Run forked process, with the only shared
// state being the new channel socket
for (int i = 0; i < n_other_fds; i++) {
if (other_fds[i].fd != chanclient) {
checked_close(other_fds[i].fd);
}
}
if (reconnectable) {
checked_close(linkfds[0]);
}
for (int i = 0; i < connmap->count; i++) {
checked_close(connmap->data[i].linkfd);
}
int display_fd = -1;
if (connect_to_socket(cwd_fd, disp_path, NULL, &display_fd) ==
-1) {
exit(EXIT_FAILURE);
}
checked_close(cwd_fd);
struct main_config mod_config = *config;
apply_conn_header(conn_id->header, &mod_config);
int rc = main_interface_loop(chanclient, display_fd, linkfds[1],
&mod_config, true);
check_unclosed_fds();
exit(rc);
} else if (npid == -1) {
wp_error("Fork failure: %s", strerror(errno));
goto fail_ps;
}
// Remove connection from this process
if (reconnectable) {
checked_close(linkfds[1]);
connmap->data[connmap->count++] =
(struct conn_addr){.linkfd = linkfds[0],
.token = *conn_id,
.pid = npid};
}
return;
fail_ps:
checked_close(linkfds[0]);
fail_cc:
checked_close(chanclient);
return;
}
#define NUM_INCOMPLETE_CONNECTIONS 63
static void drop_incoming_connection(struct pollfd *fds,
struct connection_token *tokens, uint8_t *bytes_read, int index,
int incomplete)
{
checked_close(fds[index].fd);
if (index != incomplete - 1) {
size_t shift = (size_t)(incomplete - 1 - index);
memmove(fds + index, fds + index + 1,
sizeof(struct pollfd) * shift);
memmove(tokens + index, tokens + index + 1,
sizeof(struct connection_token) * shift);
memmove(bytes_read + index, bytes_read + index + 1,
sizeof(uint8_t) * shift);
}
memset(&fds[incomplete - 1], 0, sizeof(struct pollfd));
memset(&tokens[incomplete - 1], 0, sizeof(struct connection_token));
bytes_read[incomplete - 1] = 0;
}
static int run_multi_client(int cwd_fd, int channelsock, pid_t *eol_pid,
const struct main_config *config,
const struct socket_path disp_path)
{
struct conn_map connmap = {.data = NULL, .count = 0, .size = 0};
/* Keep track of the main socket, and all connections which have not
* yet fully provided their connection token. If we run out of space,
* the oldest incomplete connection gets dropped */
struct pollfd fds[NUM_INCOMPLETE_CONNECTIONS + 1];
struct connection_token tokens[NUM_INCOMPLETE_CONNECTIONS];
uint8_t bytes_read[NUM_INCOMPLETE_CONNECTIONS];
int incomplete = 0;
memset(fds, 0, sizeof(fds));
memset(tokens, 0, sizeof(tokens));
memset(bytes_read, 0, sizeof(bytes_read));
fds[0].fd = channelsock;
fds[0].events = POLLIN;
fds[0].revents = 0;
int retcode = EXIT_SUCCESS;
while (!shutdown_flag) {
int status = -1;
if (wait_for_pid_and_clean(
eol_pid, &status, WNOHANG, &connmap)) {
wp_debug("Child (ssh) died, exiting");
// Copy the exit code
retcode = WEXITSTATUS(status);
break;
}
int r = poll(fds, 1 + (nfds_t)incomplete, -1);
if (r == -1) {
if (errno == EINTR) {
// If SIGCHLD, we will check the child.
// If SIGINT, the loop ends
continue;
}
retcode = EXIT_FAILURE;
break;
} else if (r == 0) {
// Nothing to read
continue;
}
for (int i = 0; i < incomplete; i++) {
if (!(fds[i + 1].revents & POLLIN)) {
continue;
}
int cur_fd = fds[i + 1].fd;
char *dest = ((char *)&tokens[i]) + bytes_read[i];
ssize_t s = read(cur_fd, dest, 16 - bytes_read[i]);
if (s == -1) {
wp_error("Failed to read from connection: %s",
strerror(errno));
drop_incoming_connection(fds + 1, tokens,
bytes_read, i, incomplete);
incomplete--;
continue;
} else if (s == 0) {
/* connection closed */
wp_error("Connection closed early");
drop_incoming_connection(fds + 1, tokens,
bytes_read, i, incomplete);
incomplete--;
continue;
}
bytes_read[i] += (uint8_t)s;
if (bytes_read[i] - (uint8_t)s < 4 &&
bytes_read[i] >= 4) {
char err_msg[512];
/* Validate connection token header */
if (check_conn_header(tokens[i].header, config,
err_msg,
sizeof(err_msg)) < 0) {
wp_error("%s", err_msg);
write_rejection_message(
cur_fd, err_msg);
drop_incoming_connection(fds + 1,
tokens, bytes_read, i,
incomplete);
incomplete--;
continue;
}
}
if (bytes_read[i] < 16) {
continue;
}
/* Validate connection token key */
if (tokens[i].header & CONN_UPDATE_BIT) {
send_new_connection_fd(&connmap, tokens[i].key,
cur_fd);
drop_incoming_connection(fds + 1, tokens,
bytes_read, i, incomplete);
incomplete--;
continue;
}
/* Failures here are logged, but should not
* affect this process' ability to e.g. handle
* reconnections. */
handle_new_client_connection(cwd_fd, fds,
1 + incomplete, cur_fd, &connmap,
config, disp_path, &tokens[i]);
drop_incoming_connection(fds + 1, tokens, bytes_read, i,
incomplete);
incomplete--;
}
/* Process new connections second, to give incomplete
* connections a chance to clear first */
if (fds[0].revents & POLLIN) {
int chanclient = accept(channelsock, NULL, NULL);
if (chanclient == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// The wakeup may have been spurious
continue;
}
// should errors like econnaborted exit?
wp_error("Connection failure: %s",
strerror(errno));
retcode = EXIT_FAILURE;
break;
}
wp_debug("New connection to client");
if (set_nonblocking(chanclient) == -1) {
wp_error("Error making new connection nonblocking: %s",
strerror(errno));
checked_close(chanclient);
continue;
}
if (incomplete == NUM_INCOMPLETE_CONNECTIONS) {
wp_error("Dropping oldest incomplete connection (out of %d)",
NUM_INCOMPLETE_CONNECTIONS);
drop_incoming_connection(fds + 1, tokens,
bytes_read, 0, incomplete);
incomplete--;
}
fds[1 + incomplete].fd = chanclient;
fds[1 + incomplete].events = POLLIN;
fds[1 + incomplete].revents = 0;
memset(&tokens[incomplete], 0,
sizeof(struct connection_token));
bytes_read[incomplete] = 0;
incomplete++;
}
}
for (int i = 0; i < incomplete; i++) {
checked_close(fds[i + 1].fd);
}
for (int i = 0; i < connmap.count; i++) {
checked_close(connmap.data[i].linkfd);
}
free(connmap.data);
checked_close(channelsock);
return retcode;
}
int run_client(int cwd_fd, const char *sock_folder_name, int sock_folder_fd,
const char *sock_filename, const struct main_config *config,
bool oneshot, const char *wayland_socket, pid_t eol_pid,
int channelsock)
{
wp_debug("I'm a client listening on '%s' / '%s'", sock_folder_name,
sock_filename);
wp_debug("version: %s", WAYPIPE_VERSION);
/* Connect to Wayland display. We don't use the wayland-client
* function here, because its errors aren't immediately useful,
* and older Wayland versions have edge cases */
int dispfd = -1;
struct sockaddr_un display_filename = {0};
char display_folder[256] = {0};
if (wayland_socket) {
dispfd = get_inherited_socket(wayland_socket);
if (dispfd == -1) {
goto fail;
}
/* This socket is inherited and meant to be closed by Waypipe */
if (dispfd >= 0 && dispfd < 256) {
inherited_fds[dispfd / 64] &= ~(1uLL << (dispfd % 64));
}
} else {
if (get_display_path(display_folder, sizeof(display_folder)) ==
-1) {
goto fail;
}
if (split_socket_path(display_folder, &display_filename) ==
-1) {
goto fail;
}
}
struct socket_path display_path = {
.folder = display_folder,
.filename = &display_filename,
};
if (oneshot) {
if (!wayland_socket) {
connect_to_socket(cwd_fd, display_path, NULL, &dispfd);
}
} else {
int test_conn = -1;
if (connect_to_socket(cwd_fd, display_path, NULL, &test_conn) ==
-1) {
goto fail;
}
checked_close(test_conn);
}
wp_debug("A wayland compositor is available. Proceeding.");
/* These handlers close the channelsock and dispfd */
int retcode;
if (oneshot) {
retcode = run_single_client(
channelsock, &eol_pid, config, dispfd);
} else {
retcode = run_multi_client(cwd_fd, channelsock, &eol_pid,
config, display_path);
}
unlink_at_folder(cwd_fd, sock_folder_fd, sock_folder_name,
sock_filename);
int cleanup_type = shutdown_flag ? WNOHANG : 0;
int status = -1;
// Don't return until all child processes complete
if (wait_for_pid_and_clean(&eol_pid, &status, cleanup_type, NULL)) {
retcode = WEXITSTATUS(status);
}
return retcode;
fail:
close(channelsock);
if (eol_pid) {
waitpid(eol_pid, NULL, 0);
}
unlink_at_folder(cwd_fd, sock_folder_fd, sock_folder_name,
sock_filename);
return EXIT_FAILURE;
}
waypipe-v0.8.6/src/dmabuf.c 0000664 0000000 0000000 00000026554 14414260423 0015576 0 ustar 00root root 0000000 0000000 /*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "dmabuf.h"
#include "util.h"
#ifndef HAS_DMABUF
int init_render_data(struct render_data *data)
{
data->disabled = true;
(void)data;
return -1;
}
void cleanup_render_data(struct render_data *data) { (void)data; }
struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size,
const struct dmabuf_slice_data *info)
{
(void)rd;
(void)fd;
(void)size;
(void)info;
return NULL;
}
int get_unique_dmabuf_handle(
struct render_data *rd, int fd, struct gbm_bo **temporary_bo)
{
(void)rd;
(void)fd;
(void)temporary_bo;
return -1;
}
struct gbm_bo *make_dmabuf(
struct render_data *rd, const struct dmabuf_slice_data *info)
{
(void)rd;
(void)info;
return NULL;
}
int export_dmabuf(struct gbm_bo *bo)
{
(void)bo;
return -1;
}
void destroy_dmabuf(struct gbm_bo *bo) { (void)bo; }
void *map_dmabuf(struct gbm_bo *bo, bool write, void **map_handle,
uint32_t *exp_stride)
{
(void)bo;
(void)write;
(void)map_handle;
(void)exp_stride;
return NULL;
}
int unmap_dmabuf(struct gbm_bo *bo, void *map_handle)
{
(void)bo;
(void)map_handle;
return 0;
}
uint32_t dmabuf_get_simple_format_for_plane(uint32_t format, int plane)
{
(void)format;
(void)plane;
return 0;
}
uint32_t dmabuf_get_stride(struct gbm_bo *bo)
{
(void)bo;
return 0;
}
#else /* HAS_DMABUF */
#include
#include
#include
#include
#include
#include
#include
#include
int init_render_data(struct render_data *data)
{
/* render node support can be disabled either by choice
* or when a previous version fails */
if (data->disabled) {
return -1;
}
if (data->drm_fd != -1) {
// Silent return, idempotent
return 0;
}
const char *card = data->drm_node_path ? data->drm_node_path
: "/dev/dri/renderD128";
int drm_fd = open(card, O_RDWR | O_CLOEXEC | O_NOCTTY);
if (drm_fd == -1) {
wp_error("Failed to open drm fd for %s: %s", card,
strerror(errno));
data->disabled = true;
return -1;
}
struct gbm_device *dev = gbm_create_device(drm_fd);
if (!dev) {
data->disabled = true;
checked_close(drm_fd);
wp_error("Failed to create gbm device from drm_fd");
return -1;
}
data->drm_fd = drm_fd;
data->dev = dev;
/* Set the path to the card used for protocol handlers to see */
data->drm_node_path = card;
/* Assume true initially, fall back to old buffer creation path
* if the newer path errors out */
data->supports_modifiers = true;
return 0;
}
void cleanup_render_data(struct render_data *data)
{
if (data->drm_fd != -1) {
gbm_device_destroy(data->dev);
checked_close(data->drm_fd);
data->dev = NULL;
data->drm_fd = -1;
}
}
static bool dmabuf_info_valid(const struct dmabuf_slice_data *info)
{
if (info->height > (1u << 24) || info->width > (1u << 24) ||
info->num_planes > 4 || info->num_planes == 0) {
wp_error("Invalid DMABUF slice data: height " PRIu32
" width " PRIu32 " num_planes " PRIu32,
info->height, info->width, info->num_planes);
return false;
}
return true;
}
struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size,
const struct dmabuf_slice_data *info)
{
struct gbm_bo *bo;
if (!dmabuf_info_valid(info)) {
return NULL;
}
/* Multiplanar formats are all rather badly supported by
* drivers/libgbm/libdrm/compositors/applications/everything. */
struct gbm_import_fd_modifier_data data;
// Select all plane metadata associated to planes linked
// to this fd
data.modifier = info->modifier;
data.num_fds = 0;
uint32_t simple_format = 0;
for (int i = 0; i < info->num_planes; i++) {
if (info->using_planes[i]) {
data.fds[data.num_fds] = fd;
data.strides[data.num_fds] = (int)info->strides[i];
data.offsets[data.num_fds] = (int)info->offsets[i];
data.num_fds++;
if (!simple_format) {
simple_format = dmabuf_get_simple_format_for_plane(
info->format, i);
}
}
}
if (!simple_format) {
simple_format = info->format;
}
data.width = info->width;
data.height = info->height;
data.format = simple_format;
bo = gbm_bo_import(rd->dev, GBM_BO_IMPORT_FD_MODIFIER, &data,
GBM_BO_USE_RENDERING);
if (!bo) {
wp_error("Failed to import dmabuf (format %x, modifier %" PRIx64
") to gbm bo: %s",
info->format, info->modifier, strerror(errno));
return NULL;
}
/* todo: find out how to correctly map multiplanar formats */
*size = gbm_bo_get_stride(bo) * gbm_bo_get_height(bo);
return bo;
}
int get_unique_dmabuf_handle(
struct render_data *rd, int fd, struct gbm_bo **temporary_bo)
{
struct gbm_import_fd_data data;
data.fd = fd;
data.width = 1;
data.stride = 1;
data.height = 1;
data.format = GBM_FORMAT_R8;
*temporary_bo = gbm_bo_import(
rd->dev, GBM_BO_IMPORT_FD, &data, GBM_BO_USE_RENDERING);
if (!*temporary_bo) {
return -1;
}
// This effectively reduces to DRM_IOCTL_PRIME_FD_TO_HANDLE. Is the
// runtime dependency worth it?
int handle = gbm_bo_get_handle(*temporary_bo).s32;
return handle;
}
struct gbm_bo *make_dmabuf(
struct render_data *rd, const struct dmabuf_slice_data *info)
{
struct gbm_bo *bo;
if (!dmabuf_info_valid(info)) {
return NULL;
}
retry:
if (!rd->supports_modifiers ||
info->modifier == DRM_FORMAT_MOD_INVALID) {
uint32_t simple_format = dmabuf_get_simple_format_for_plane(
info->format, 0);
/* If the modifier is nonzero, assume that the backend
* preferred modifier matches it. With this old API, there
* really isn't any way to do this better */
bo = gbm_bo_create(rd->dev, info->width, info->height,
simple_format,
GBM_BO_USE_RENDERING |
(info->modifier ? 0
: GBM_BO_USE_LINEAR));
if (!bo) {
wp_error("Failed to make dmabuf (old path): %s",
strerror(errno));
return NULL;
}
uint64_t mod = gbm_bo_get_modifier(bo);
if (info->modifier != DRM_FORMAT_MOD_INVALID &&
mod != DRM_FORMAT_MOD_INVALID &&
mod != info->modifier) {
wp_error("DMABUF with format %08x, autoselected modifier %" PRIx64
" does not match desired %" PRIx64
", expect a crash",
simple_format, mod, info->modifier);
}
} else {
uint64_t modifiers[2] = {info->modifier, GBM_BO_USE_RENDERING};
uint32_t simple_format = dmabuf_get_simple_format_for_plane(
info->format, 0);
/* Whether just size and modifiers suffice to replicate
* a surface is driver dependent, and requires actual testing
* with the hardware.
*
* i915 DRM ioctls cover size, swizzling, tiling state, only.
* amdgpu, size + allocation domain/caching/align flags
* etnaviv, size + caching flags
* tegra, vc4: size + tiling + flags
* radeon: size + tiling + flags, including pitch
*
* Note that gbm doesn't have a specific api for creating
* buffers with minimal information, or even just getting
* the size of the buffer contents.
*/
bo = gbm_bo_create_with_modifiers(rd->dev, info->width,
info->height, simple_format, modifiers, 2);
if (!bo && errno == ENOSYS) {
wp_debug("Creating a DMABUF with modifiers explicitly set is not supported; retrying");
rd->supports_modifiers = false;
goto retry;
}
if (!bo) {
wp_error("Failed to make dmabuf (with format %x, modifier %" PRIx64
"): %s",
simple_format, info->modifier,
strerror(errno));
return NULL;
}
}
return bo;
}
int export_dmabuf(struct gbm_bo *bo)
{
int fd = gbm_bo_get_fd(bo);
if (fd == -1) {
wp_error("Failed to export dmabuf: %s", strerror(errno));
}
return fd;
}
void destroy_dmabuf(struct gbm_bo *bo)
{
if (bo) {
gbm_bo_destroy(bo);
}
}
void *map_dmabuf(struct gbm_bo *bo, bool write, void **map_handle,
uint32_t *exp_stride)
{
if (!bo) {
wp_error("Tried to map null gbm_bo");
return NULL;
}
/* With i965, the map handle MUST initially point to a NULL pointer;
* otherwise the handler silently exits, sometimes with misleading errno
* :-(
*/
*map_handle = NULL;
uint32_t stride;
uint32_t width = gbm_bo_get_width(bo);
uint32_t height = gbm_bo_get_height(bo);
/* As of writing, with amdgpu, GBM_BO_TRANSFER_WRITE invalidates
* regions not written to during the mapping, while iris preserves
* the original buffer contents. GBM documentation does not say which
* WRITE behavior is correct. What the individual drivers do may change
* in the future. Specifying READ_WRITE preserves the old contents with
* both drivers. */
uint32_t flags = write ? GBM_BO_TRANSFER_READ_WRITE
: GBM_BO_TRANSFER_READ;
void *data = gbm_bo_map(
bo, 0, 0, width, height, flags, &stride, map_handle);
if (!data) {
// errno is useless here
wp_error("Failed to map dmabuf");
return NULL;
}
*exp_stride = stride;
return data;
}
int unmap_dmabuf(struct gbm_bo *bo, void *map_handle)
{
gbm_bo_unmap(bo, map_handle);
return 0;
}
// TODO: support DRM formats, like DRM_FORMAT_RGB888_A8 and
// DRM_FORMAT_ARGB16161616F, defined in drm_fourcc.h.
struct multiplanar_info {
uint32_t format;
struct {
int subsample_w;
int subsample_h;
int cpp;
} planes[3];
};
static const struct multiplanar_info plane_table[] = {
{GBM_FORMAT_NV12, {{1, 1, 1}, {2, 2, 2}}},
{GBM_FORMAT_NV21, {{1, 1, 1}, {2, 2, 2}}},
{GBM_FORMAT_NV16, {{1, 1, 1}, {2, 1, 2}}},
{GBM_FORMAT_NV61, {{1, 1, 1}, {2, 1, 2}}},
{GBM_FORMAT_YUV410, {{1, 1, 1}, {4, 4, 1}, {4, 4, 1}}},
{GBM_FORMAT_YVU410, {{1, 1, 1}, {4, 4, 1}, {4, 4, 1}}},
{GBM_FORMAT_YUV411, {{1, 1, 1}, {4, 1, 1}, {4, 1, 1}}},
{GBM_FORMAT_YVU411, {{1, 1, 1}, {4, 1, 1}, {4, 1, 1}}},
{GBM_FORMAT_YUV420, {{1, 1, 1}, {2, 2, 1}, {2, 2, 1}}},
{GBM_FORMAT_YVU420, {{1, 1, 1}, {2, 2, 1}, {2, 2, 1}}},
{GBM_FORMAT_YUV422, {{1, 1, 1}, {2, 1, 1}, {2, 1, 1}}},
{GBM_FORMAT_YVU422, {{1, 1, 1}, {2, 1, 1}, {2, 1, 1}}},
{GBM_FORMAT_YUV444, {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}}},
{GBM_FORMAT_YVU444, {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}}}, {0}};
uint32_t dmabuf_get_simple_format_for_plane(uint32_t format, int plane)
{
const uint32_t by_cpp[] = {0, GBM_FORMAT_R8, GBM_FORMAT_GR88,
GBM_FORMAT_RGB888, GBM_BO_FORMAT_ARGB8888};
for (int i = 0; plane_table[i].format; i++) {
if (plane_table[i].format == format) {
int cpp = plane_table[i].planes[plane].cpp;
return by_cpp[cpp];
}
}
if (format == GBM_FORMAT_YUYV || format == GBM_FORMAT_YVYU ||
format == GBM_FORMAT_UYVY ||
format == GBM_FORMAT_VYUY ||
format == GBM_FORMAT_AYUV) {
return by_cpp[4];
}
return format;
}
uint32_t dmabuf_get_stride(struct gbm_bo *bo) { return gbm_bo_get_stride(bo); }
#endif /* HAS_DMABUF */
waypipe-v0.8.6/src/dmabuf.h 0000664 0000000 0000000 00000007312 14414260423 0015572 0 ustar 00root root 0000000 0000000 /*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef WAYPIPE_DMABUF_H
#define WAYPIPE_DMABUF_H
#include
#include
#include
#include
typedef void *VADisplay;
typedef unsigned int VAGenericID;
typedef VAGenericID VAConfigID;
struct render_data {
bool disabled;
int drm_fd;
const char *drm_node_path;
struct gbm_device *dev;
bool supports_modifiers;
/* video hardware context */
bool av_disabled;
int av_bpf;
int av_video_fmt;
struct AVBufferRef *av_hwdevice_ref;
struct AVBufferRef *av_drmdevice_ref;
VADisplay av_vadisplay;
VAConfigID av_copy_config;
};
/** Additional information to help serialize a dmabuf */
struct dmabuf_slice_data {
/* This information partially duplicates that of a gbm_bo. However, for
* instance with weston, it is possible for the compositor to handle
* multibuffer multiplanar images, even though a driver may only support
* multiplanar images derived from a single underlying dmabuf. */
uint32_t width;
uint32_t height;
uint32_t format;
int32_t num_planes;
uint32_t offsets[4];
uint32_t strides[4];
uint64_t modifier;
// to which planes is the matching dmabuf assigned?
uint8_t using_planes[4];
char pad[4];
};
static_assert(sizeof(struct dmabuf_slice_data) == 64, "size check");
int init_render_data(struct render_data *);
void cleanup_render_data(struct render_data *);
struct gbm_bo *make_dmabuf(
struct render_data *rd, const struct dmabuf_slice_data *info);
int export_dmabuf(struct gbm_bo *bo);
/** Import DMABUF to a GBM buffer object. */
struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size,
const struct dmabuf_slice_data *info);
void destroy_dmabuf(struct gbm_bo *bo);
/** Map a DMABUF for reading or for writing */
void *map_dmabuf(struct gbm_bo *bo, bool write, void **map_handle,
uint32_t *exp_stride);
int unmap_dmabuf(struct gbm_bo *bo, void *map_handle);
/** The handle values are unique among the set of currently active buffer
* objects. To compare a set of buffer objects, produce handles in a batch, and
* then free the temporary buffer objects in a batch */
int get_unique_dmabuf_handle(
struct render_data *rd, int fd, struct gbm_bo **temporary_bo);
uint32_t dmabuf_get_simple_format_for_plane(uint32_t format, int plane);
uint32_t dmabuf_get_stride(struct gbm_bo *bo);
/** Returns the number of bytes per pixel for WL or DRM format 'format', if the
* format is an RGBA-type single plane format. For YUV-type or planar formats,
* returns -1. */
int get_shm_bytes_per_pixel(uint32_t format);
#ifndef DRM_FORMAT_MOD_INVALID
#define DRM_FORMAT_MOD_INVALID 0x00ffffffffffffffULL
#endif
#endif // WAYPIPE_DMABUF_H
waypipe-v0.8.6/src/handlers.c 0000664 0000000 0000000 00000164331 14414260423 0016134 0 ustar 00root root 0000000 0000000 /*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "main.h"
#include "parsing.h"
#include "shadow.h"
#include
#include
#include
#include
#include
#include
#include
struct obj_wl_shm_pool {
struct wp_object base;
struct shadow_fd *owned_buffer;
};
enum buffer_type { BUF_SHM, BUF_DMA };
// This should be a safe limit for the maximum number of dmabuf planes
#define MAX_DMABUF_PLANES 8
struct obj_wl_buffer {
struct wp_object base;
enum buffer_type type;
struct shadow_fd *shm_buffer;
int32_t shm_offset;
int32_t shm_width;
int32_t shm_height;
int32_t shm_stride;
uint32_t shm_format;
int dmabuf_nplanes;
int32_t dmabuf_width;
int32_t dmabuf_height;
uint32_t dmabuf_format;
uint32_t dmabuf_flags;
struct shadow_fd *dmabuf_buffers[MAX_DMABUF_PLANES];
uint32_t dmabuf_offsets[MAX_DMABUF_PLANES];
uint32_t dmabuf_strides[MAX_DMABUF_PLANES];
uint64_t dmabuf_modifiers[MAX_DMABUF_PLANES];
uint64_t unique_id;
};
struct damage_record {
int x, y, width, height;
bool buffer_coordinates;
};
struct damage_list {
struct damage_record *list;
int len;
int size;
};
#define SURFACE_DAMAGE_BACKLOG 7
struct obj_wl_surface {
struct wp_object base;
/* The zeroth list is the "current" one, 1st was damage provided at last
* commit, etc. */
struct damage_list damage_lists[SURFACE_DAMAGE_BACKLOG];
/* Unique buffer identifiers to which the above damage lists apply */
uint64_t attached_buffer_uids[SURFACE_DAMAGE_BACKLOG];
uint32_t attached_buffer_id; /* protocol object id */
int32_t scale;
int32_t transform;
};
struct obj_wlr_screencopy_frame {
struct wp_object base;
/* Link to a wp_buffer instead of its underlying data,
* because if the buffer object is destroyed early, then
* we do not want to accidentally write over a section of a shm_pool
* which is now used for transport in the reverse direction.
*/
uint32_t buffer_id;
};
struct obj_wp_presentation {
struct wp_object base;
// reference clock - given clock
int64_t clock_delta_nsec;
int clock_id;
};
struct obj_wp_presentation_feedback {
struct wp_object base;
int64_t clock_delta_nsec;
};
struct obj_zwp_linux_dmabuf_params {
struct wp_object base;
struct shadow_fd *sfds;
// These variables are set by 'params.create', and passed on in
// params.created
int32_t create_width;
int32_t create_height;
uint32_t create_format;
uint32_t create_flags;
struct {
int fd;
struct shadow_fd *buffer;
uint32_t offset;
uint32_t stride;
uint64_t modifier;
} add[MAX_DMABUF_PLANES];
int nplanes;
};
struct format_table_entry {
uint32_t format;
uint32_t padding;
uint64_t modifier;
};
struct dmabuf_tranche {
uint32_t flags;
uint16_t *tranche;
size_t tranche_size;
};
struct obj_zwp_linux_dmabuf_feedback {
struct wp_object base;
struct format_table_entry *table;
size_t table_len;
dev_t main_device;
/* the tranche being edited until tranche_done is called */
dev_t current_device;
/* the tranche being edited until tranche_done is called */
struct dmabuf_tranche current;
/* list of all tranches */
struct dmabuf_tranche *tranches;
size_t tranche_count;
};
struct obj_wlr_export_dmabuf_frame {
struct wp_object base;
uint32_t width;
uint32_t height;
uint32_t format;
uint64_t modifier;
// At the moment, no message reordering support, for lack of a client
// to test it with
struct {
struct shadow_fd *buffer;
uint32_t offset;
uint32_t stride;
uint64_t modifier;
} objects[MAX_DMABUF_PLANES];
uint32_t nobjects;
};
/* List of interfaces which may be advertised as globals */
static const struct wp_interface *const global_interfaces[] = {
&intf_gtk_primary_selection_device_manager,
&intf_wl_compositor,
&intf_wl_data_device_manager,
&intf_wl_drm,
&intf_wl_output,
&intf_wl_seat,
&intf_wl_shm,
&intf_wl_subcompositor,
&intf_wp_presentation,
&intf_xdg_wm_base,
&intf_zwlr_data_control_manager_v1,
&intf_zwlr_export_dmabuf_manager_v1,
&intf_zwlr_gamma_control_manager_v1,
&intf_zwlr_screencopy_manager_v1,
&intf_zwp_input_method_manager_v2,
&intf_zwp_linux_dmabuf_v1,
&intf_zwp_primary_selection_device_manager_v1,
&intf_zwp_virtual_keyboard_manager_v1,
};
/* List of interfaces which are never advertised as globals */
static const struct wp_interface *const non_global_interfaces[] = {
&intf_gtk_primary_selection_offer,
&intf_gtk_primary_selection_source,
&intf_wl_buffer,
&intf_wl_data_offer,
&intf_wl_data_source,
&intf_wl_display,
&intf_wl_keyboard,
&intf_wl_registry,
&intf_wl_shm_pool,
&intf_wl_surface,
&intf_wp_presentation_feedback,
&intf_zwlr_data_control_offer_v1,
&intf_zwlr_data_control_source_v1,
&intf_zwlr_export_dmabuf_frame_v1,
&intf_zwlr_gamma_control_v1,
&intf_zwlr_screencopy_frame_v1,
&intf_zwp_linux_buffer_params_v1,
&intf_zwp_primary_selection_offer_v1,
&intf_zwp_primary_selection_source_v1,
};
void destroy_wp_object(struct wp_object *object)
{
if (object->type == &intf_wl_shm_pool) {
struct obj_wl_shm_pool *r = (struct obj_wl_shm_pool *)object;
if (r->owned_buffer) {
shadow_decref_protocol(r->owned_buffer);
}
} else if (object->type == &intf_wl_buffer) {
struct obj_wl_buffer *r = (struct obj_wl_buffer *)object;
for (int i = 0; i < MAX_DMABUF_PLANES; i++) {
if (r->dmabuf_buffers[i]) {
shadow_decref_protocol(r->dmabuf_buffers[i]);
}
}
if (r->shm_buffer) {
shadow_decref_protocol(r->shm_buffer);
}
} else if (object->type == &intf_wl_surface) {
struct obj_wl_surface *r = (struct obj_wl_surface *)object;
for (int i = 0; i < SURFACE_DAMAGE_BACKLOG; i++) {
free(r->damage_lists[i].list);
}
} else if (object->type == &intf_zwlr_screencopy_frame_v1) {
struct obj_wlr_screencopy_frame *r =
(struct obj_wlr_screencopy_frame *)object;
(void)r;
} else if (object->type == &intf_wp_presentation) {
} else if (object->type == &intf_wp_presentation_feedback) {
} else if (object->type == &intf_zwp_linux_buffer_params_v1) {
struct obj_zwp_linux_dmabuf_params *r =
(struct obj_zwp_linux_dmabuf_params *)object;
for (int i = 0; i < MAX_DMABUF_PLANES; i++) {
if (r->add[i].buffer) {
shadow_decref_protocol(r->add[i].buffer);
}
// Sometimes multiple entries point to the same buffer
if (r->add[i].fd != -1) {
checked_close(r->add[i].fd);
for (int k = 0; k < MAX_DMABUF_PLANES; k++) {
if (r->add[i].fd == r->add[k].fd) {
r->add[k].fd = -1;
}
}
}
}
} else if (object->type == &intf_zwlr_export_dmabuf_frame_v1) {
struct obj_wlr_export_dmabuf_frame *r =
(struct obj_wlr_export_dmabuf_frame *)object;
for (int i = 0; i < MAX_DMABUF_PLANES; i++) {
if (r->objects[i].buffer) {
shadow_decref_protocol(r->objects[i].buffer);
}
}
} else if (object->type == &intf_zwp_linux_dmabuf_feedback_v1) {
struct obj_zwp_linux_dmabuf_feedback *r =
(struct obj_zwp_linux_dmabuf_feedback *)object;
free(r->table);
if (r->tranche_count > 0) {
for (size_t i = 0; i < r->tranche_count; i++) {
free(r->tranches[i].tranche);
}
free(r->tranches);
}
}
free(object);
}
struct wp_object *create_wp_object(uint32_t id, const struct wp_interface *type)
{
/* Note: if custom types are ever implemented for globals, they would
* need special replacement logic when the type is set */
size_t sz;
if (type == &intf_wl_shm_pool) {
sz = sizeof(struct obj_wl_shm_pool);
} else if (type == &intf_wl_buffer) {
sz = sizeof(struct obj_wl_buffer);
} else if (type == &intf_wl_surface) {
sz = sizeof(struct obj_wl_surface);
} else if (type == &intf_zwlr_screencopy_frame_v1) {
sz = sizeof(struct obj_wlr_screencopy_frame);
} else if (type == &intf_wp_presentation) {
sz = sizeof(struct obj_wp_presentation);
} else if (type == &intf_wp_presentation_feedback) {
sz = sizeof(struct obj_wp_presentation_feedback);
} else if (type == &intf_zwp_linux_buffer_params_v1) {
sz = sizeof(struct obj_zwp_linux_dmabuf_params);
} else if (type == &intf_zwlr_export_dmabuf_frame_v1) {
sz = sizeof(struct obj_wlr_export_dmabuf_frame);
} else if (type == &intf_zwp_linux_dmabuf_feedback_v1) {
sz = sizeof(struct obj_zwp_linux_dmabuf_feedback);
} else {
sz = sizeof(struct wp_object);
}
struct wp_object *new_obj = calloc(1, sz);
if (!new_obj) {
wp_error("Failed to allocate new wp_object id=%d type=%s", id,
type->name);
return NULL;
}
new_obj->obj_id = id;
new_obj->type = type;
new_obj->is_zombie = false;
if (type == &intf_zwp_linux_buffer_params_v1) {
struct obj_zwp_linux_dmabuf_params *params =
(struct obj_zwp_linux_dmabuf_params *)new_obj;
for (int i = 0; i < MAX_DMABUF_PLANES; i++) {
params->add[i].fd = -1;
}
} else if (type == &intf_wl_surface) {
((struct obj_wl_surface *)new_obj)->scale = 1;
}
return new_obj;
}
void do_wl_display_evt_error(struct context *ctx, struct wp_object *object_id,
uint32_t code, const char *message)
{
const char *type_name =
object_id ? (object_id->type ? object_id->type->name
: "")
: "";
wp_error("Display sent fatal error message %s, code %u: %s", type_name,
code, message ? message : "");
(void)ctx;
}
void do_wl_display_evt_delete_id(struct context *ctx, uint32_t id)
{
struct wp_object *obj = tracker_get(ctx->tracker, id);
/* ensure this isn't miscalled to have wl_display delete itself */
if (obj && obj != ctx->obj) {
tracker_remove(ctx->tracker, obj);
destroy_wp_object(obj);
}
}
void do_wl_display_req_get_registry(
struct context *ctx, struct wp_object *registry)
{
(void)ctx;
(void)registry;
}
void do_wl_display_req_sync(struct context *ctx, struct wp_object *callback)
{
(void)ctx;
(void)callback;
}
void do_wl_registry_evt_global(struct context *ctx, uint32_t name,
const char *interface, uint32_t version)
{
if (!interface) {
wp_debug("Interface name provided via wl_registry::global was NULL");
return;
}
bool requires_rnode = false;
requires_rnode |= !strcmp(interface, "wl_drm");
requires_rnode |= !strcmp(interface, "zwp_linux_dmabuf_v1");
requires_rnode |= !strcmp(interface, "zwlr_export_dmabuf_manager_v1");
if (requires_rnode) {
if (init_render_data(&ctx->g->render) == -1) {
/* A gpu connection supported by waypipe is required on
* both sides, since data transfers may occur in both
* directions, and
* modifying textures may require driver support */
wp_debug("Discarding protocol advertisement for %s, render node support disabled",
interface);
ctx->drop_this_msg = true;
return;
}
}
if (!strcmp(interface, "zwp_linux_dmabuf_v1")) {
/* Higher versions will very likely require new Waypipe code to
* support, so limit this to what Waypipe supports */
if (ctx->message[2 + 1 + 1 + 5] >
ZWP_LINUX_DMABUF_V1_INTERFACE_VERSION) {
ctx->message[2 + 1 + 1 + 5] =
ZWP_LINUX_DMABUF_V1_INTERFACE_VERSION;
}
}
if (!strcmp(interface, "wl_shm")) {
/* Higher versions will very likely require new Waypipe code to
* support, so limit this to what Waypipe supports */
if (ctx->message[2 + 1 + 1 + 2] > WL_SHM_INTERFACE_VERSION) {
ctx->message[2 + 1 + 1 + 2] = WL_SHM_INTERFACE_VERSION;
}
}
bool unsupported = false;
// requires novel fd translation, not yet supported
unsupported |= !strcmp(
interface, "zwp_linux_explicit_synchronization_v1");
if (unsupported) {
wp_debug("Hiding %s advertisement, unsupported", interface);
ctx->drop_this_msg = true;
}
(void)name;
(void)version;
}
void do_wl_registry_evt_global_remove(struct context *ctx, uint32_t name)
{
(void)ctx;
(void)name;
}
void do_wl_registry_req_bind(struct context *ctx, uint32_t name,
const char *interface, uint32_t version, struct wp_object *id)
{
if (!interface) {
wp_debug("Interface name provided to wl_registry::bind was NULL");
return;
}
/* The object has already been created, but its type is NULL */
struct wp_object *the_object = id;
uint32_t obj_id = the_object->obj_id;
for (size_t i = 0; i < sizeof(non_global_interfaces) /
sizeof(non_global_interfaces[0]);
i++) {
if (!strcmp(interface, non_global_interfaces[i]->name)) {
wp_error("Interface %s does not support binding globals",
non_global_interfaces[i]->name);
/* exit search, discard unbound object */
goto fail;
}
}
for (size_t i = 0; i < sizeof(global_interfaces) /
sizeof(global_interfaces[0]);
i++) {
if (!strcmp(interface, global_interfaces[i]->name)) {
// Set the object type
the_object->type = global_interfaces[i];
if (global_interfaces[i] == &intf_wp_presentation) {
struct wp_object *new_object = create_wp_object(
obj_id, &intf_wp_presentation);
if (!new_object) {
return;
}
tracker_replace_existing(
ctx->tracker, new_object);
free(the_object);
}
return;
}
}
fail:
wp_debug("Unhandled protocol %s name=%d id=%d (v%d)", interface, name,
the_object->obj_id, version);
tracker_remove(ctx->tracker, the_object);
free(the_object);
(void)name;
(void)version;
}
void do_wl_buffer_evt_release(struct context *ctx) { (void)ctx; }
int get_shm_bytes_per_pixel(uint32_t format)
{
switch (format) {
case 0x34325241: /* DRM_FORMAT_ARGB8888 */
case 0x34325258: /* DRM_FORMAT_XRGB8888 */
case WL_SHM_FORMAT_ARGB8888:
case WL_SHM_FORMAT_XRGB8888:
return 4;
case WL_SHM_FORMAT_C8:
case WL_SHM_FORMAT_RGB332:
case WL_SHM_FORMAT_BGR233:
return 1;
case WL_SHM_FORMAT_XRGB4444:
case WL_SHM_FORMAT_XBGR4444:
case WL_SHM_FORMAT_RGBX4444:
case WL_SHM_FORMAT_BGRX4444:
case WL_SHM_FORMAT_ARGB4444:
case WL_SHM_FORMAT_ABGR4444:
case WL_SHM_FORMAT_RGBA4444:
case WL_SHM_FORMAT_BGRA4444:
case WL_SHM_FORMAT_XRGB1555:
case WL_SHM_FORMAT_XBGR1555:
case WL_SHM_FORMAT_RGBX5551:
case WL_SHM_FORMAT_BGRX5551:
case WL_SHM_FORMAT_ARGB1555:
case WL_SHM_FORMAT_ABGR1555:
case WL_SHM_FORMAT_RGBA5551:
case WL_SHM_FORMAT_BGRA5551:
case WL_SHM_FORMAT_RGB565:
case WL_SHM_FORMAT_BGR565:
return 2;
case WL_SHM_FORMAT_RGB888:
case WL_SHM_FORMAT_BGR888:
return 3;
case WL_SHM_FORMAT_XBGR8888:
case WL_SHM_FORMAT_RGBX8888:
case WL_SHM_FORMAT_BGRX8888:
case WL_SHM_FORMAT_ABGR8888:
case WL_SHM_FORMAT_RGBA8888:
case WL_SHM_FORMAT_BGRA8888:
case WL_SHM_FORMAT_XRGB2101010:
case WL_SHM_FORMAT_XBGR2101010:
case WL_SHM_FORMAT_RGBX1010102:
case WL_SHM_FORMAT_BGRX1010102:
case WL_SHM_FORMAT_ARGB2101010:
case WL_SHM_FORMAT_ABGR2101010:
case WL_SHM_FORMAT_RGBA1010102:
case WL_SHM_FORMAT_BGRA1010102:
return 4;
case WL_SHM_FORMAT_YUYV:
case WL_SHM_FORMAT_YVYU:
case WL_SHM_FORMAT_UYVY:
case WL_SHM_FORMAT_VYUY:
case WL_SHM_FORMAT_AYUV:
case WL_SHM_FORMAT_NV12:
case WL_SHM_FORMAT_NV21:
case WL_SHM_FORMAT_NV16:
case WL_SHM_FORMAT_NV61:
case WL_SHM_FORMAT_YUV410:
case WL_SHM_FORMAT_YVU410:
case WL_SHM_FORMAT_YUV411:
case WL_SHM_FORMAT_YVU411:
case WL_SHM_FORMAT_YUV420:
case WL_SHM_FORMAT_YVU420:
case WL_SHM_FORMAT_YUV422:
case WL_SHM_FORMAT_YVU422:
case WL_SHM_FORMAT_YUV444:
case WL_SHM_FORMAT_YVU444:
goto planar;
case WL_SHM_FORMAT_R8:
return 1;
case WL_SHM_FORMAT_R16:
case WL_SHM_FORMAT_RG88:
case WL_SHM_FORMAT_GR88:
return 2;
case WL_SHM_FORMAT_RG1616:
case WL_SHM_FORMAT_GR1616:
return 4;
case WL_SHM_FORMAT_XRGB16161616F:
case WL_SHM_FORMAT_XBGR16161616F:
case WL_SHM_FORMAT_ARGB16161616F:
case WL_SHM_FORMAT_ABGR16161616F:
case WL_SHM_FORMAT_AXBXGXRX106106106106:
return 8;
case WL_SHM_FORMAT_XYUV8888:
case WL_SHM_FORMAT_VUY888:
case WL_SHM_FORMAT_VUY101010:
case WL_SHM_FORMAT_Y210:
case WL_SHM_FORMAT_Y212:
case WL_SHM_FORMAT_Y216:
case WL_SHM_FORMAT_Y410:
case WL_SHM_FORMAT_Y412:
case WL_SHM_FORMAT_Y416:
case WL_SHM_FORMAT_XVYU2101010:
case WL_SHM_FORMAT_XVYU12_16161616:
case WL_SHM_FORMAT_XVYU16161616:
case WL_SHM_FORMAT_Y0L0:
case WL_SHM_FORMAT_X0L0:
case WL_SHM_FORMAT_Y0L2:
case WL_SHM_FORMAT_X0L2:
case WL_SHM_FORMAT_YUV420_8BIT:
case WL_SHM_FORMAT_YUV420_10BIT:
case WL_SHM_FORMAT_XRGB8888_A8:
case WL_SHM_FORMAT_XBGR8888_A8:
case WL_SHM_FORMAT_RGBX8888_A8:
case WL_SHM_FORMAT_BGRX8888_A8:
case WL_SHM_FORMAT_RGB888_A8:
case WL_SHM_FORMAT_BGR888_A8:
case WL_SHM_FORMAT_RGB565_A8:
case WL_SHM_FORMAT_BGR565_A8:
case WL_SHM_FORMAT_NV24:
case WL_SHM_FORMAT_NV42:
case WL_SHM_FORMAT_P210:
case WL_SHM_FORMAT_P010:
case WL_SHM_FORMAT_P012:
case WL_SHM_FORMAT_P016:
case WL_SHM_FORMAT_NV15:
case WL_SHM_FORMAT_Q410:
case WL_SHM_FORMAT_Q401:
goto planar;
case WL_SHM_FORMAT_XRGB16161616:
case WL_SHM_FORMAT_XBGR16161616:
case WL_SHM_FORMAT_ARGB16161616:
case WL_SHM_FORMAT_ABGR16161616:
return 8;
// todo: adjust API to handle bit packed formats
case WL_SHM_FORMAT_C1:
case WL_SHM_FORMAT_C2:
case WL_SHM_FORMAT_C4:
case WL_SHM_FORMAT_D1:
case WL_SHM_FORMAT_D2:
case WL_SHM_FORMAT_D4:
goto planar;
case WL_SHM_FORMAT_D8:
return 1;
case WL_SHM_FORMAT_R1:
case WL_SHM_FORMAT_R2:
case WL_SHM_FORMAT_R4:
goto planar;
case WL_SHM_FORMAT_R10:
case WL_SHM_FORMAT_R12:
return 2;
case WL_SHM_FORMAT_AVUY8888:
case WL_SHM_FORMAT_XVUY8888:
return 4;
case WL_SHM_FORMAT_P030:
goto planar;
default:
wp_error("Unidentified WL_SHM format %x", format);
return -1;
}
planar:
return -1;
}
static void compute_damage_coordinates(int *xlow, int *xhigh, int *ylow,
int *yhigh, const struct damage_record *rec, int buf_width,
int buf_height, int transform, int scale)
{
if (rec->buffer_coordinates) {
*xlow = rec->x;
*xhigh = rec->x + rec->width;
*ylow = rec->y;
*yhigh = rec->y + rec->height;
} else {
int xl = rec->x * scale;
int yl = rec->y * scale;
int xh = (rec->width + rec->x) * scale;
int yh = (rec->y + rec->height) * scale;
/* Each of the eight transformations corresponds to a
* unique set of reflections: X<->Y | Xflip | Yflip */
uint32_t magic = 0x74125630;
/* idx 76543210
* xyech = 10101010
* xflip = 11000110
* yflip = 10011100
*/
bool xyexch = magic & (1u << (4 * transform));
bool xflip = magic & (1u << (4 * transform + 1));
bool yflip = magic & (1u << (4 * transform + 2));
int ew = xyexch ? buf_height : buf_width;
int eh = xyexch ? buf_width : buf_height;
if (xflip) {
int tmp = ew - xh;
xh = ew - xl;
xl = tmp;
}
if (yflip) {
int tmp = eh - yh;
yh = eh - yl;
yl = tmp;
}
if (xyexch) {
*xlow = yl;
*xhigh = yh;
*ylow = xl;
*yhigh = xh;
} else {
*xlow = xl;
*xhigh = xh;
*ylow = yl;
*yhigh = yh;
}
}
}
void do_wl_surface_req_attach(struct context *ctx, struct wp_object *buffer,
int32_t x, int32_t y)
{
(void)x;
(void)y;
struct wp_object *bufobj = (struct wp_object *)buffer;
if (!bufobj) {
/* A null buffer can legitimately be send to remove
* surface contents, presumably with shell-defined
* semantics */
wp_debug("Buffer to be attached is null");
return;
}
if (bufobj->type != &intf_wl_buffer) {
wp_error("Buffer to be attached has the wrong type");
return;
}
struct obj_wl_surface *surface = (struct obj_wl_surface *)ctx->obj;
surface->attached_buffer_id = bufobj->obj_id;
}
static void rotate_damage_lists(struct obj_wl_surface *surface)
{
free(surface->damage_lists[SURFACE_DAMAGE_BACKLOG - 1].list);
memmove(surface->damage_lists + 1, surface->damage_lists,
(SURFACE_DAMAGE_BACKLOG - 1) *
sizeof(struct damage_list));
memset(surface->damage_lists, 0, sizeof(struct damage_list));
memmove(surface->attached_buffer_uids + 1,
surface->attached_buffer_uids,
(SURFACE_DAMAGE_BACKLOG - 1) * sizeof(uint64_t));
surface->attached_buffer_uids[0] = 0;
}
void do_wl_surface_req_commit(struct context *ctx)
{
struct obj_wl_surface *surface = (struct obj_wl_surface *)ctx->obj;
if (!surface->attached_buffer_id) {
/* The wl_surface.commit operation applies all "pending
* state", much of which we don't care about. Typically,
* when a wl_surface is first created, it is soon
* committed to atomically update state variables. An
* attached wl_buffer is not required.
*/
return;
}
if (ctx->on_display_side) {
/* commit signifies a client-side update only */
return;
}
struct wp_object *obj =
tracker_get(ctx->tracker, surface->attached_buffer_id);
if (!obj) {
wp_error("Attached buffer no longer exists");
return;
}
if (obj->type != &intf_wl_buffer) {
wp_error("Buffer to commit has the wrong type, and may have been recycled");
return;
}
struct obj_wl_buffer *buf = (struct obj_wl_buffer *)obj;
surface->attached_buffer_uids[0] = buf->unique_id;
if (buf->type == BUF_DMA) {
rotate_damage_lists(surface);
for (int i = 0; i < buf->dmabuf_nplanes; i++) {
struct shadow_fd *sfd = buf->dmabuf_buffers[i];
if (!sfd) {
wp_error("dmabuf surface buffer is missing plane %d",
i);
continue;
}
if (!(sfd->type == FDC_DMABUF ||
sfd->type == FDC_DMAVID_IR)) {
wp_error("fd associated with dmabuf surface is not a dmabuf");
continue;
}
// detailed damage tracking is not yet supported
sfd->is_dirty = true;
damage_everything(&sfd->damage);
}
return;
} else if (buf->type != BUF_SHM) {
wp_error("wp_buffer is backed neither by DMA nor SHM, not yet supported");
return;
}
struct shadow_fd *sfd = buf->shm_buffer;
if (!sfd) {
wp_error("wp_buffer to be committed has no fd");
return;
}
if (sfd->type != FDC_FILE) {
wp_error("fd associated with surface is not file-like");
return;
}
sfd->is_dirty = true;
int bpp = get_shm_bytes_per_pixel(buf->shm_format);
if (bpp == -1) {
wp_error("Encountered unknown/planar/subsampled wl_shm format %x; marking entire buffer",
buf->shm_format);
goto backup;
}
if (surface->scale <= 0) {
wp_error("Invalid buffer scale during commit (%d), assuming everything damaged",
surface->scale);
goto backup;
}
if (surface->transform < 0 || surface->transform >= 8) {
wp_error("Invalid buffer transform during commit (%d), assuming everything damaged",
surface->transform);
goto backup;
}
/* The damage specified as of wl_surface commit indicates which region
* of the surface has changed between the last commit and the current
* one. However, the last time the attached buffer was used may have
* been several commits ago, so we need to replay all the damage up
* to the current point. */
int age = -1;
int n_damaged_rects = surface->damage_lists[0].len;
for (int j = 1; j < SURFACE_DAMAGE_BACKLOG; j++) {
if (surface->attached_buffer_uids[0] ==
surface->attached_buffer_uids[j]) {
age = j;
break;
}
n_damaged_rects += surface->damage_lists[j].len;
}
if (age == -1) {
/* cannot find last time buffer+surface combo was used */
goto backup;
}
struct ext_interval *damage_array = malloc(
sizeof(struct ext_interval) * (size_t)n_damaged_rects);
if (!damage_array) {
wp_error("Failed to allocate damage array");
goto backup;
}
int i = 0;
// Translate damage stack into damage records for the fd buffer
for (int k = 0; k < age; k++) {
const struct damage_list *frame_damage =
&surface->damage_lists[k];
for (int j = 0; j < frame_damage->len; j++) {
int xlow, xhigh, ylow, yhigh;
compute_damage_coordinates(&xlow, &xhigh, &ylow, &yhigh,
&frame_damage->list[j], buf->shm_width,
buf->shm_height, surface->transform,
surface->scale);
/* Clip the damage rectangle to the containing
* buffer. */
xlow = clamp(xlow, 0, buf->shm_width);
xhigh = clamp(xhigh, 0, buf->shm_width);
ylow = clamp(ylow, 0, buf->shm_height);
yhigh = clamp(yhigh, 0, buf->shm_height);
damage_array[i].start = buf->shm_offset +
buf->shm_stride * ylow +
bpp * xlow;
damage_array[i].rep = yhigh - ylow;
damage_array[i].stride = buf->shm_stride;
damage_array[i].width = bpp * (xhigh - xlow);
i++;
}
}
merge_damage_records(&sfd->damage, i, damage_array,
ctx->g->threads.diff_alignment_bits);
free(damage_array);
rotate_damage_lists(surface);
backup:
if (1) {
/* damage the entire buffer (but no other part of the shm_pool)
*/
struct ext_interval full_surface_damage;
full_surface_damage.start = buf->shm_offset;
full_surface_damage.rep = 1;
full_surface_damage.stride = 0;
full_surface_damage.width = buf->shm_stride * buf->shm_height;
merge_damage_records(&sfd->damage, 1, &full_surface_damage,
ctx->g->threads.diff_alignment_bits);
}
rotate_damage_lists(surface);
return;
}
static void append_damage_record(struct obj_wl_surface *surface, int32_t x,
int32_t y, int32_t width, int32_t height,
bool in_buffer_coordinates)
{
struct damage_list *current = &surface->damage_lists[0];
if (buf_ensure_size(current->len + 1, sizeof(struct damage_record),
¤t->size, (void **)¤t->list) == -1) {
wp_error("Failed to allocate space for damage list, dropping damage record");
return;
}
// A rectangle of the buffer was damaged, hence backing buffers
// may be updated.
struct damage_record *damage = ¤t->list[current->len++];
damage->buffer_coordinates = in_buffer_coordinates;
damage->x = x;
damage->y = y;
damage->width = width;
damage->height = height;
}
void do_wl_surface_req_damage(struct context *ctx, int32_t x, int32_t y,
int32_t width, int32_t height)
{
if (ctx->on_display_side) {
// The display side does not need to track the damage
return;
}
append_damage_record((struct obj_wl_surface *)ctx->obj, x, y, width,
height, false);
}
void do_wl_surface_req_damage_buffer(struct context *ctx, int32_t x, int32_t y,
int32_t width, int32_t height)
{
if (ctx->on_display_side) {
// The display side does not need to track the damage
return;
}
append_damage_record((struct obj_wl_surface *)ctx->obj, x, y, width,
height, true);
}
void do_wl_surface_req_set_buffer_transform(
struct context *ctx, int32_t transform)
{
struct obj_wl_surface *surface = (struct obj_wl_surface *)ctx->obj;
surface->transform = transform;
}
void do_wl_surface_req_set_buffer_scale(struct context *ctx, int32_t scale)
{
struct obj_wl_surface *surface = (struct obj_wl_surface *)ctx->obj;
surface->scale = scale;
}
void do_wl_keyboard_evt_keymap(
struct context *ctx, uint32_t format, int fd, uint32_t size)
{
size_t fdsz = 0;
enum fdcat fdtype = get_fd_type(fd, &fdsz);
if (fdtype == FDC_UNKNOWN) {
fdtype = FDC_FILE;
fdsz = (size_t)size;
}
if (fdtype != FDC_FILE || fdsz != size) {
wp_error("keymap candidate fd %d was not file-like (type=%s), and with size=%zu did not match %u",
fd, fdcat_to_str(fdtype), fdsz, size);
return;
}
struct shadow_fd *sfd = translate_fd(&ctx->g->map, &ctx->g->render, fd,
FDC_FILE, fdsz, NULL, false);
if (!sfd) {
wp_error("Failed to create shadow for keymap fd=%d", fd);
return;
}
/* The keyboard file descriptor is never changed after being sent.
* Mark the shadow structure as owned by the protocol, so it can be
* automatically deleted as soon as the fd has been transferred. */
sfd->has_owner = true;
(void)format;
}
void do_wl_shm_req_create_pool(
struct context *ctx, struct wp_object *id, int fd, int32_t size)
{
struct obj_wl_shm_pool *the_shm_pool = (struct obj_wl_shm_pool *)id;
if (size <= 0) {
wp_error("Ignoring attempt to create a wl_shm_pool with size %d",
size);
}
size_t fdsz = 0;
enum fdcat fdtype = get_fd_type(fd, &fdsz);
if (fdtype == FDC_UNKNOWN) {
fdtype = FDC_FILE;
fdsz = (size_t)size;
}
/* It may be valid for the file descriptor size to be larger
* than the immediately advertised size, since the call to
* wl_shm.create_pool may be followed by wl_shm_pool.resize,
* which then increases the size
*/
if (fdtype != FDC_FILE || (int32_t)fdsz < size) {
wp_error("File type or size mismatch for fd %d with claimed: %s %s | %zu %u",
fd, fdcat_to_str(fdtype),
fdcat_to_str(FDC_FILE), fdsz, size);
return;
}
struct shadow_fd *sfd = translate_fd(&ctx->g->map, &ctx->g->render, fd,
FDC_FILE, fdsz, NULL, false);
if (!sfd) {
return;
}
the_shm_pool->owned_buffer = shadow_incref_protocol(sfd);
/* We only send shm_pool updates when the buffers created from the
* pool are used. Some applications make the pool >> actual buffers,
* so this can reduce communication by a lot*/
reset_damage(&sfd->damage);
}
void do_wl_shm_pool_req_resize(struct context *ctx, int32_t size)
{
struct obj_wl_shm_pool *the_shm_pool =
(struct obj_wl_shm_pool *)ctx->obj;
if (!the_shm_pool->owned_buffer) {
wp_error("Pool to be resized owns no buffer");
return;
}
if ((int32_t)the_shm_pool->owned_buffer->buffer_size >= size) {
// The underlying buffer was already resized by the time
// this protocol message was received
return;
}
/* The display side will be updated already via buffer update msg */
if (!ctx->on_display_side) {
extend_shm_shadow(&ctx->g->threads, the_shm_pool->owned_buffer,
(size_t)size);
}
}
void do_wl_shm_pool_req_create_buffer(struct context *ctx, struct wp_object *id,
int32_t offset, int32_t width, int32_t height, int32_t stride,
uint32_t format)
{
struct obj_wl_shm_pool *the_shm_pool =
(struct obj_wl_shm_pool *)ctx->obj;
struct obj_wl_buffer *the_buffer = (struct obj_wl_buffer *)id;
if (!the_buffer) {
wp_error("No buffer available");
return;
}
struct shadow_fd *sfd = the_shm_pool->owned_buffer;
if (!sfd) {
wp_error("Creating a wl_buffer from a pool that does not own an fd");
return;
}
the_buffer->type = BUF_SHM;
the_buffer->shm_buffer =
shadow_incref_protocol(the_shm_pool->owned_buffer);
the_buffer->shm_offset = offset;
the_buffer->shm_width = width;
the_buffer->shm_height = height;
the_buffer->shm_stride = stride;
the_buffer->shm_format = format;
the_buffer->unique_id = ctx->g->tracker.buffer_seqno++;
}
void do_zwlr_screencopy_frame_v1_evt_ready(struct context *ctx,
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec)
{
struct obj_wlr_screencopy_frame *frame =
(struct obj_wlr_screencopy_frame *)ctx->obj;
if (!frame->buffer_id) {
wp_error("frame has no copy target");
return;
}
struct wp_object *obj = (struct wp_object *)tracker_get(
ctx->tracker, frame->buffer_id);
if (!obj) {
wp_error("frame copy target no longer exists");
return;
}
if (obj->type != &intf_wl_buffer) {
wp_error("frame copy target is not a wl_buffer");
return;
}
struct obj_wl_buffer *buffer = (struct obj_wl_buffer *)obj;
struct shadow_fd *sfd = buffer->shm_buffer;
if (!sfd) {
wp_error("frame copy target does not own any buffers");
return;
}
if (sfd->type != FDC_FILE) {
wp_error("frame copy target buffer file descriptor (RID=%d) was not file-like (type=%d)",
sfd->remote_id, sfd->type);
return;
}
if (buffer->type != BUF_SHM) {
wp_error("screencopy not yet supported for non-shm-backed buffers");
return;
}
if (!ctx->on_display_side) {
// The display side performs the update
return;
}
sfd->is_dirty = true;
/* The protocol guarantees that the buffer attributes match
* those of the written frame */
const struct ext_interval interval = {.start = buffer->shm_offset,
.width = buffer->shm_height * buffer->shm_stride,
.stride = 0,
.rep = 1};
merge_damage_records(&sfd->damage, 1, &interval,
ctx->g->threads.diff_alignment_bits);
(void)tv_sec_lo;
(void)tv_sec_hi;
(void)tv_nsec;
}
void do_zwlr_screencopy_frame_v1_req_copy(
struct context *ctx, struct wp_object *buffer)
{
struct obj_wlr_screencopy_frame *frame =
(struct obj_wlr_screencopy_frame *)ctx->obj;
struct wp_object *buf = (struct wp_object *)buffer;
if (buf->type != &intf_wl_buffer) {
wp_error("frame copy destination is not a wl_buffer");
return;
}
frame->buffer_id = buf->obj_id;
}
static int64_t timespec_diff(struct timespec val, struct timespec sub)
{
// Overflows only with 68 year error, insignificant
return (val.tv_sec - sub.tv_sec) * 1000000000LL +
(val.tv_nsec - sub.tv_nsec);
}
void do_wp_presentation_evt_clock_id(struct context *ctx, uint32_t clk_id)
{
struct obj_wp_presentation *pres =
(struct obj_wp_presentation *)ctx->obj;
pres->clock_id = (int)clk_id;
int reference_clock = CLOCK_REALTIME;
if (pres->clock_id == reference_clock) {
pres->clock_delta_nsec = 0;
} else {
/* Estimate the difference in baseline between clocks.
* (TODO: Is there a syscall for this?) do median of 3?
*/
struct timespec t0, t1, t2;
clock_gettime(pres->clock_id, &t0);
clock_gettime(reference_clock, &t1);
clock_gettime(pres->clock_id, &t2);
int64_t diff1m0 = timespec_diff(t1, t0);
int64_t diff2m1 = timespec_diff(t2, t1);
pres->clock_delta_nsec = (diff1m0 - diff2m1) / 2;
}
}
void do_wp_presentation_req_feedback(struct context *ctx,
struct wp_object *surface, struct wp_object *callback)
{
struct obj_wp_presentation *pres =
(struct obj_wp_presentation *)ctx->obj;
struct obj_wp_presentation_feedback *feedback =
(struct obj_wp_presentation_feedback *)callback;
(void)surface;
feedback->clock_delta_nsec = pres->clock_delta_nsec;
}
void do_wp_presentation_feedback_evt_presented(struct context *ctx,
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec,
uint32_t refresh, uint32_t seq_hi, uint32_t seq_lo,
uint32_t flags)
{
struct obj_wp_presentation_feedback *feedback =
(struct obj_wp_presentation_feedback *)ctx->obj;
(void)refresh;
(void)seq_hi;
(void)seq_lo;
(void)flags;
/* convert local to reference, on display side */
int dir = ctx->on_display_side ? 1 : -1;
uint64_t sec = tv_sec_lo + tv_sec_hi * 0x100000000uLL;
int64_t nsec = tv_nsec;
nsec += dir * feedback->clock_delta_nsec;
sec = (uint64_t)((int64_t)sec + nsec / 1000000000LL);
nsec = nsec % 1000000000L;
if (nsec < 0) {
nsec += 1000000000L;
sec--;
}
// Size not changed, no other edits required
ctx->message[2] = (uint32_t)(sec / 0x100000000uLL);
ctx->message[3] = (uint32_t)(sec % 0x100000000uLL);
ctx->message[4] = (uint32_t)nsec;
}
void do_wl_drm_evt_device(struct context *ctx, const char *name)
{
if (ctx->on_display_side) {
/* Replacing the (remote) DRM device path with a local
* render node path only is useful on the application
* side */
return;
}
if (!name) {
wp_debug("Device name provided via wl_drm::device was NULL");
return;
}
if (!ctx->g->render.drm_node_path) {
/* While the render node should have been initialized in
* wl_registry.global, setting this path, we still don't want
* to crash even if this gets called by accident */
wp_debug("wl_drm::device, local render node not set up");
return;
}
int path_len = (int)strlen(ctx->g->render.drm_node_path);
int message_bytes = 8 + 4 + 4 * ((path_len + 1 + 3) / 4);
if (message_bytes > ctx->message_available_space) {
wp_error("Not enough space to modify DRM device advertisement from '%s' to '%s'",
name, ctx->g->render.drm_node_path);
return;
}
ctx->message_length = message_bytes;
uint32_t *payload = ctx->message + 2;
memset(payload, 0, (size_t)message_bytes - 8);
payload[0] = (uint32_t)path_len + 1;
memcpy(ctx->message + 3, ctx->g->render.drm_node_path,
(size_t)path_len);
uint32_t meth = (ctx->message[1] << 16) >> 16;
ctx->message[1] = message_header_2((uint32_t)message_bytes, meth);
}
void do_wl_drm_req_create_prime_buffer(struct context *ctx,
struct wp_object *id, int name, int32_t width, int32_t height,
uint32_t format, int32_t offset0, int32_t stride0,
int32_t offset1, int32_t stride1, int32_t offset2,
int32_t stride2)
{
struct obj_wl_buffer *buf = (struct obj_wl_buffer *)id;
struct dmabuf_slice_data info = {
.num_planes = 1,
.width = (uint32_t)width,
.height = (uint32_t)height,
.modifier = DRM_FORMAT_MOD_INVALID,
.format = format,
.offsets = {(uint32_t)offset0, (uint32_t)offset1,
(uint32_t)offset2, 0},
.strides = {(uint32_t)stride0, (uint32_t)stride1,
(uint32_t)stride2, 0},
.using_planes = {true, false, false, false},
};
struct shadow_fd *sfd = translate_fd(&ctx->g->map, &ctx->g->render,
name, FDC_DMABUF, 0, &info, false);
if (!sfd) {
return;
}
buf->type = BUF_DMA;
buf->dmabuf_nplanes = 1;
buf->dmabuf_buffers[0] = shadow_incref_protocol(sfd);
buf->dmabuf_width = width;
buf->dmabuf_height = height;
buf->dmabuf_format = format;
// handling multiple offsets (?)
buf->dmabuf_offsets[0] = (uint32_t)offset0;
buf->dmabuf_strides[0] = (uint32_t)stride0;
buf->unique_id = ctx->g->tracker.buffer_seqno++;
if (ctx->on_display_side) {
/* the new dmabuf being created is not guaranteed to
* have the original offset/stride parameters, so reset
* them */
ctx->message[6] = 0;
ctx->message[7] = dmabuf_get_stride(sfd->dmabuf_bo);
}
}
static bool dmabuf_format_permitted(
struct context *ctx, uint32_t format, uint64_t modifier)
{
if (ctx->g->config->only_linear_dmabuf) {
/* MOD_INVALID is allowed because some drivers don't support
* LINEAR. Every modern GPU+driver should be able to handle
* LINEAR. Conditionally blocking INVALID (i.e, if LINEAR is an
* option) can break things when the application-side Waypipe
* instance does not support LINEAR. */
if (modifier != 0 && modifier != DRM_FORMAT_MOD_INVALID) {
return false;
}
}
/* Filter out formats which are not recognized, or multiplane */
if (get_shm_bytes_per_pixel(format) == -1) {
return false;
}
/* Blacklist intel modifiers which introduce a second color control
* surface; todo: add support for these, eventually */
if (modifier == (1uLL << 56 | 4) || modifier == (1uLL << 56 | 5) ||
modifier == (1uLL << 56 | 6) ||
modifier == (1uLL << 56 | 7) ||
modifier == (1uLL << 56 | 8)) {
return false;
}
return true;
}
void do_zwp_linux_dmabuf_v1_evt_modifier(struct context *ctx, uint32_t format,
uint32_t modifier_hi, uint32_t modifier_lo)
{
(void)format;
uint64_t modifier = modifier_hi * 0x100000000uLL + modifier_lo;
// Prevent all advertisements for dmabufs with modifiers
if (!dmabuf_format_permitted(ctx, format, modifier)) {
ctx->drop_this_msg = true;
}
}
void do_zwp_linux_dmabuf_v1_req_get_default_feedback(
struct context *ctx, struct wp_object *id)
{
// todo: use this to find the correct main device
(void)ctx;
(void)id;
}
void do_zwp_linux_dmabuf_v1_req_get_surface_feedback(struct context *ctx,
struct wp_object *id, struct wp_object *surface)
{
(void)ctx;
(void)id;
(void)surface;
}
void do_zwp_linux_buffer_params_v1_evt_created(
struct context *ctx, struct wp_object *buffer)
{
struct obj_zwp_linux_dmabuf_params *params =
(struct obj_zwp_linux_dmabuf_params *)ctx->obj;
struct obj_wl_buffer *buf = (struct obj_wl_buffer *)buffer;
buf->type = BUF_DMA;
buf->dmabuf_nplanes = params->nplanes;
for (int i = 0; i < params->nplanes; i++) {
if (!params->add[i].buffer) {
wp_error("dmabuf backed wl_buffer plane %d was missing",
i);
continue;
}
buf->dmabuf_buffers[i] =
shadow_incref_protocol(params->add[i].buffer);
buf->dmabuf_offsets[i] = params->add[i].offset;
buf->dmabuf_strides[i] = params->add[i].stride;
buf->dmabuf_modifiers[i] = params->add[i].modifier;
}
buf->dmabuf_flags = params->create_flags;
buf->dmabuf_width = params->create_width;
buf->dmabuf_height = params->create_height;
buf->dmabuf_format = params->create_format;
buf->unique_id = ctx->g->tracker.buffer_seqno++;
}
void do_zwp_linux_buffer_params_v1_req_add(struct context *ctx, int fd,
uint32_t plane_idx, uint32_t offset, uint32_t stride,
uint32_t modifier_hi, uint32_t modifier_lo)
{
struct obj_zwp_linux_dmabuf_params *params =
(struct obj_zwp_linux_dmabuf_params *)ctx->obj;
if (params->nplanes != (int)plane_idx) {
wp_error("Expected sequentially assigned plane fds: got new_idx=%d != %d=nplanes",
plane_idx, params->nplanes);
return;
}
if (params->nplanes >= MAX_DMABUF_PLANES) {
wp_error("Too many planes");
return;
}
params->nplanes++;
params->add[plane_idx].fd = fd;
params->add[plane_idx].offset = offset;
params->add[plane_idx].stride = stride;
params->add[plane_idx].modifier =
modifier_lo + modifier_hi * 0x100000000uLL;
// Only perform rearrangement on the client side, for now
if (true) {
ctx->drop_this_msg = true;
}
}
static uint32_t append_zwp_linux_buffer_params_v1_req_add(uint32_t *msg,
bool display_side, uint32_t obj_id, uint32_t plane_idx,
uint32_t offset, uint32_t stride, uint32_t modifier_hi,
uint32_t modifier_lo)
{
uint32_t msg_size = 2;
if (msg) {
msg[0] = obj_id;
msg[msg_size++] = plane_idx;
msg[msg_size++] = offset;
msg[msg_size++] = stride;
msg[msg_size++] = modifier_hi;
msg[msg_size++] = modifier_lo;
msg[1] = ((uint32_t)msg_size << 18) | 1;
/* Tag the message as having one file descriptor */
if (!display_side) {
msg[1] |= (uint32_t)(1 << 11);
}
} else {
msg_size += 5;
}
return msg_size;
}
void do_zwp_linux_buffer_params_v1_req_create(struct context *ctx,
int32_t width, int32_t height, uint32_t format, uint32_t flags)
{
struct obj_zwp_linux_dmabuf_params *params =
(struct obj_zwp_linux_dmabuf_params *)ctx->obj;
params->create_flags = flags;
params->create_width = width;
params->create_height = height;
params->create_format = format;
struct dmabuf_slice_data info = {.width = (uint32_t)width,
.height = (uint32_t)height,
.format = format,
.num_planes = params->nplanes,
.strides = {params->add[0].stride,
params->add[1].stride,
params->add[2].stride,
params->add[3].stride},
.offsets = {params->add[0].offset,
params->add[1].offset,
params->add[2].offset,
params->add[3].offset}};
bool all_same_fds = true;
for (int i = 1; i < params->nplanes; i++) {
if (params->add[i].fd != params->add[0].fd) {
all_same_fds = false;
}
}
for (int i = 0; i < params->nplanes; i++) {
memset(info.using_planes, 0, sizeof(info.using_planes));
for (int k = 0; k < min(params->nplanes, 4); k++) {
if (params->add[k].fd == params->add[i].fd) {
info.using_planes[k] = 1;
info.modifier = params->add[k].modifier;
}
}
enum fdcat res_type = FDC_DMABUF;
if (ctx->g->config->video_if_possible) {
// TODO: multibuffer support
if (all_same_fds && video_supports_dmabuf_format(format,
info.modifier)) {
res_type = ctx->on_display_side ? FDC_DMAVID_IW
: FDC_DMAVID_IR;
}
}
/* note: the``info` provided includes the incoming/as-if stride
* data. */
struct shadow_fd *sfd = translate_fd(&ctx->g->map,
&ctx->g->render, params->add[i].fd, res_type, 0,
&info, false);
if (!sfd) {
continue;
}
if (ctx->on_display_side) {
/* the new dmabuf being created is not guaranteed to
* have the original offset/stride parameters, so reset
* them */
params->add[i].offset = 0;
params->add[i].stride =
dmabuf_get_stride(sfd->dmabuf_bo);
}
/* increment for each extra time this fd will be sent */
if (sfd->has_owner) {
shadow_incref_transfer(sfd);
}
// Convert the stored fds to buffer pointers now.
params->add[i].buffer = shadow_incref_protocol(sfd);
}
if (true) {
// Update file descriptors
int nfds = params->nplanes;
if (nfds > ctx->fds->size - ctx->fds->zone_end) {
wp_error("Not enough space to reintroduce zwp_linux_buffer_params_v1.add message fds");
return;
}
int nmoved = (ctx->fds->zone_end - ctx->fds->zone_start);
memmove(ctx->fds->data + ctx->fds->zone_start + nfds,
ctx->fds->data + ctx->fds->zone_start,
(size_t)nmoved * sizeof(int));
for (int i = 0; i < params->nplanes; i++) {
ctx->fds->data[ctx->fds->zone_start + i] =
params->add[i].fd;
}
/* We inject `nfds` new file descriptors, and advance the zone
* of queued file descriptors forward, since the injected file
* descriptors will not be used by the parser, but will still
* be transported out. */
ctx->fds->zone_start += nfds;
ctx->fds->zone_end += nfds;
ctx->fds_changed = true;
// Update data
int net_length = ctx->message_length;
uint32_t extra = 0;
for (int i = 0; i < params->nplanes; i++) {
extra += append_zwp_linux_buffer_params_v1_req_add(NULL,
ctx->on_display_side,
params->base.obj_id, (uint32_t)i,
params->add[i].offset,
params->add[i].stride,
(uint32_t)(params->add[i].modifier >>
32),
(uint32_t)(params->add[i].modifier));
}
net_length += (int)(sizeof(uint32_t) * extra);
if (net_length > ctx->message_available_space) {
wp_error("Not enough space to reintroduce zwp_linux_buffer_params_v1.add message data");
return;
}
char *cmsg = (char *)ctx->message;
memmove(cmsg + net_length - ctx->message_length, cmsg,
(size_t)ctx->message_length);
size_t start = 0;
for (int i = 0; i < params->nplanes; i++) {
uint32_t step = append_zwp_linux_buffer_params_v1_req_add(
(uint32_t *)(cmsg + start),
ctx->on_display_side,
params->base.obj_id, (uint32_t)i,
params->add[i].offset,
params->add[i].stride,
(uint32_t)(params->add[i].modifier >>
32),
(uint32_t)(params->add[i].modifier));
start += step * sizeof(uint32_t);
}
wp_debug("Reintroducing add requests for zwp_linux_buffer_params_v1, going from %d to %d bytes",
ctx->message_length, net_length);
ctx->message_length = net_length;
}
// Avoid closing in destroy_wp_object
for (int i = 0; i < MAX_DMABUF_PLANES; i++) {
params->add[i].fd = -1;
}
}
void do_zwp_linux_buffer_params_v1_req_create_immed(struct context *ctx,
struct wp_object *buffer_id, int32_t width, int32_t height,
uint32_t format, uint32_t flags)
{
// There isn't really that much unnecessary copying. Note that
// 'create' may modify messages
do_zwp_linux_buffer_params_v1_req_create(
ctx, width, height, format, flags);
do_zwp_linux_buffer_params_v1_evt_created(ctx, buffer_id);
}
void do_zwp_linux_dmabuf_feedback_v1_evt_done(struct context *ctx)
{
struct obj_zwp_linux_dmabuf_feedback *obj =
(struct obj_zwp_linux_dmabuf_feedback *)ctx->obj;
int worst_case_space = 2;
for (size_t i = 0; i < obj->tranche_count; i++) {
for (size_t j = 0; j < obj->tranches[i].tranche_size; j++) {
uint16_t idx = obj->tranches[i].tranche[j];
if (idx > obj->table_len) {
wp_error("Tranche format index %u out of bounds [0,%zu)",
idx, obj->table_len);
return;
}
}
worst_case_space +=
2 + 3 + 3 + 3 + ((int)sizeof(dev_t) + 3) / 4 +
((int)obj->tranches[i].tranche_size + 1) / 2;
}
if (ctx->message_available_space < worst_case_space * 4) {
wp_error("Not enough space to introduce all tranche fields");
return;
}
/* Inject messages for filtered tranche parameters here */
size_t m = 0;
for (size_t i = 0; i < obj->tranche_count; i++) {
bool empty = true;
for (size_t j = 0; j < obj->tranches[i].tranche_size; j++) {
uint16_t idx = obj->tranches[i].tranche[j];
if (dmabuf_format_permitted(ctx, obj->table[idx].format,
obj->table[idx].modifier)) {
empty = false;
break;
}
}
if (empty) {
/* discard tranche, has no entries */
continue;
}
size_t s;
s = 3 + ((sizeof(dev_t) + 3) / 4);
ctx->message[m] = obj->base.obj_id;
ctx->message[m + 1] = message_header_2(
4 * (uint32_t)s, 4); // tranche_target_device
ctx->message[m + 2] = sizeof(dev_t);
memcpy(&ctx->message[m + 3], &obj->main_device, sizeof(dev_t));
m += s;
s = 3;
ctx->message[m] = obj->base.obj_id;
ctx->message[m + 1] = message_header_2(
4 * (uint32_t)s, 6); // tranche_flags
ctx->message[m + 2] = obj->tranches[i].flags;
m += s;
size_t w = 0;
uint16_t *fmts = (uint16_t *)&ctx->message[m + 3];
for (size_t j = 0; j < obj->tranches[i].tranche_size; j++) {
uint16_t idx = obj->tranches[i].tranche[j];
if (dmabuf_format_permitted(ctx, obj->table[idx].format,
obj->table[idx].modifier)) {
fmts[w++] = idx;
}
}
s = 3 + ((w + 1) / 2);
ctx->message[m] = obj->base.obj_id;
ctx->message[m + 1] = message_header_2(
4 * (uint32_t)s, 5); // tranche_formats
ctx->message[m + 2] = (uint32_t)(2 * w);
m += s;
s = 2;
ctx->message[m] = obj->base.obj_id;
ctx->message[m + 1] = message_header_2(
4 * (uint32_t)s, 3); // tranche_done
m += s;
}
ctx->message[m] = obj->base.obj_id;
ctx->message[m + 1] = message_header_2(8, 0); // done
m += 2;
ctx->message_length = (int)(m * 4);
for (size_t i = 0; i < obj->tranche_count; i++) {
free(obj->tranches[i].tranche);
}
free(obj->tranches);
obj->tranches = NULL;
obj->tranche_count = 0;
}
void do_zwp_linux_dmabuf_feedback_v1_evt_format_table(
struct context *ctx, int fd, uint32_t size)
{
size_t fdsz = 0;
enum fdcat fdtype = get_fd_type(fd, &fdsz);
if (fdtype == FDC_UNKNOWN) {
fdtype = FDC_FILE;
}
if (fdtype != FDC_FILE || fdsz != size) {
wp_error("format tabl fd %d was not file-like (type=%s), and size=%zu did not match %u",
fd, fdcat_to_str(fdtype), fdsz, size);
return;
}
struct shadow_fd *sfd = translate_fd(&ctx->g->map, &ctx->g->render, fd,
FDC_FILE, size, NULL, false);
if (!sfd) {
return;
}
/* Mark the shadow structure as owned by the protocol, but do not
* increase the protocol refcount, so that as soon as it gets
* transferred it is destroyed */
sfd->has_owner = true;
struct obj_zwp_linux_dmabuf_feedback *obj =
(struct obj_zwp_linux_dmabuf_feedback *)ctx->obj;
free(obj->table);
obj->table_len = sfd->buffer_size / sizeof(struct format_table_entry);
obj->table = calloc(obj->table_len, sizeof(struct format_table_entry));
if (!obj->table) {
wp_error("failed to allocate copy of dmabuf feedback format table");
return;
}
memcpy(obj->table, sfd->mem_local,
obj->table_len * sizeof(struct format_table_entry));
}
void do_zwp_linux_dmabuf_feedback_v1_evt_main_device(struct context *ctx,
uint32_t device_count, const uint8_t *device_val)
{
struct obj_zwp_linux_dmabuf_feedback *obj =
(struct obj_zwp_linux_dmabuf_feedback *)ctx->obj;
if ((size_t)device_count != sizeof(dev_t)) {
wp_error("Invalid dev_t size %zu, should be %zu",
(size_t)device_count, sizeof(dev_t));
return;
}
if (ctx->on_display_side) {
memcpy(&obj->main_device, device_val, sizeof(dev_t));
} else {
// adopt the main device from the render fd being used
struct stat fsdata;
memset(&fsdata, 0, sizeof(fsdata));
int ret = fstat(ctx->g->render.drm_fd, &fsdata);
if (ret == -1) {
wp_error("Failed to get render device info");
return;
}
obj->main_device = fsdata.st_rdev;
}
/* todo: add support for changing render devices in waypipe */
}
void do_zwp_linux_dmabuf_feedback_v1_evt_tranche_done(struct context *ctx)
{
struct obj_zwp_linux_dmabuf_feedback *obj =
(struct obj_zwp_linux_dmabuf_feedback *)ctx->obj;
if (obj->main_device != obj->current_device && ctx->on_display_side) {
/* Filter out/ignore all tranches for anything but the main
* device. */
return;
}
void *next = realloc(obj->tranches,
(obj->tranche_count + 1) * sizeof(*obj->tranches));
if (!next) {
wp_error("Failed to resize tranche list");
return;
}
obj->tranches = next;
obj->tranches[obj->tranche_count] = obj->current;
obj->tranche_count++;
/* it is unclear whether flags/device get in a valid use of the
* protocol, but assuming they do not costs nothing. */
// todo: what about the tranche?
obj->current.tranche = NULL;
obj->current.tranche_size = 0;
/* discard message, will be resent later if needed */
ctx->drop_this_msg = true;
}
void do_zwp_linux_dmabuf_feedback_v1_evt_tranche_target_device(
struct context *ctx, uint32_t device_count,
const uint8_t *device_val)
{
struct obj_zwp_linux_dmabuf_feedback *obj =
(struct obj_zwp_linux_dmabuf_feedback *)ctx->obj;
if ((size_t)device_count != sizeof(dev_t)) {
wp_error("Invalid dev_t size %zu, should be %zu",
(size_t)device_count, sizeof(dev_t));
}
memcpy(&obj->current_device, device_val, sizeof(dev_t));
/* discard message, will be resent later if needed */
ctx->drop_this_msg = true;
}
void do_zwp_linux_dmabuf_feedback_v1_evt_tranche_formats(struct context *ctx,
uint32_t indices_count, const uint8_t *indices_val)
{
struct obj_zwp_linux_dmabuf_feedback *obj =
(struct obj_zwp_linux_dmabuf_feedback *)ctx->obj;
size_t num_indices = (size_t)indices_count / 2;
free(obj->current.tranche);
obj->current.tranche_size = num_indices;
obj->current.tranche = calloc(num_indices, sizeof(uint16_t));
if (!obj->current.tranche) {
wp_error("failed to allocate for tranche");
return;
}
// todo: translation to formats+modifiers should be performed
// immediately, in case format table changes between tranches
memcpy(obj->current.tranche, indices_val,
num_indices * sizeof(uint16_t));
/* discard message, will be resent later if needed */
ctx->drop_this_msg = true;
}
void do_zwp_linux_dmabuf_feedback_v1_evt_tranche_flags(
struct context *ctx, uint32_t flags)
{
struct obj_zwp_linux_dmabuf_feedback *obj =
(struct obj_zwp_linux_dmabuf_feedback *)ctx->obj;
obj->current.flags = flags;
/* discard message, will be resent later if needed */
ctx->drop_this_msg = true;
}
void do_zwlr_export_dmabuf_frame_v1_evt_frame(struct context *ctx,
uint32_t width, uint32_t height, uint32_t offset_x,
uint32_t offset_y, uint32_t buffer_flags, uint32_t flags,
uint32_t format, uint32_t mod_high, uint32_t mod_low,
uint32_t num_objects)
{
struct obj_wlr_export_dmabuf_frame *frame =
(struct obj_wlr_export_dmabuf_frame *)ctx->obj;
frame->width = width;
frame->height = height;
(void)offset_x;
(void)offset_y;
// the 'transient' flag could be cleared, technically
(void)flags;
(void)buffer_flags;
frame->format = format;
frame->modifier = mod_high * 0x100000000uLL + mod_low;
frame->nobjects = num_objects;
if (frame->nobjects > MAX_DMABUF_PLANES) {
wp_error("Too many (%u) frame objects required",
frame->nobjects);
frame->nobjects = MAX_DMABUF_PLANES;
}
}
void do_zwlr_export_dmabuf_frame_v1_evt_object(struct context *ctx,
uint32_t index, int fd, uint32_t size, uint32_t offset,
uint32_t stride, uint32_t plane_index)
{
struct obj_wlr_export_dmabuf_frame *frame =
(struct obj_wlr_export_dmabuf_frame *)ctx->obj;
if (index > frame->nobjects) {
wp_error("Cannot add frame object with index %u >= %u", index,
frame->nobjects);
return;
}
if (frame->objects[index].buffer) {
wp_error("Cannot add frame object with index %u, already used",
frame->nobjects);
return;
}
frame->objects[index].offset = offset;
frame->objects[index].stride = stride;
// for lack of a test program, we assume all dmabufs passed in
// here are distinct, and hence need no 'multiplane' adjustments
struct dmabuf_slice_data info = {.width = frame->width,
.height = frame->height,
.format = frame->format,
.num_planes = (int32_t)frame->nobjects,
.strides = {frame->objects[0].stride,
frame->objects[1].stride,
frame->objects[2].stride,
frame->objects[3].stride},
.offsets = {frame->objects[0].offset,
frame->objects[1].offset,
frame->objects[2].offset,
frame->objects[3].offset},
.using_planes = {false, false, false, false},
.modifier = frame->modifier};
info.using_planes[index] = true;
struct shadow_fd *sfd = translate_fd(&ctx->g->map, &ctx->g->render, fd,
FDC_DMABUF, 0, &info, false);
if (!sfd) {
return;
}
if (sfd->buffer_size < size) {
wp_error("Frame object %u has a dmabuf with less (%u) than the advertised (%u) size",
index, (uint32_t)sfd->buffer_size, size);
}
// Convert the stored fds to buffer pointers now.
frame->objects[index].buffer = shadow_incref_protocol(sfd);
// in practice, index+1?
(void)plane_index;
}
void do_zwlr_export_dmabuf_frame_v1_evt_ready(struct context *ctx,
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec)
{
struct obj_wlr_export_dmabuf_frame *frame =
(struct obj_wlr_export_dmabuf_frame *)ctx->obj;
if (!ctx->on_display_side) {
/* The client side does not update the buffer */
return;
}
(void)tv_sec_hi;
(void)tv_sec_lo;
(void)tv_nsec;
for (uint32_t i = 0; i < frame->nobjects; i++) {
struct shadow_fd *sfd = frame->objects[i].buffer;
if (sfd) {
sfd->is_dirty = true;
damage_everything(&sfd->damage);
}
}
}
static void translate_data_transfer_fd(struct context *context, int32_t fd)
{
/* treat the fd as a one-way pipe, even if it is e.g. a file or
* socketpair, with additional properties. The fd being sent
* around should be, according to the protocol, only written into and
* closed */
(void)translate_fd(&context->g->map, &context->g->render, fd, FDC_PIPE,
0, NULL, true);
}
void do_gtk_primary_selection_offer_req_receive(
struct context *ctx, const char *mime_type, int fd)
{
translate_data_transfer_fd(ctx, fd);
(void)mime_type;
}
void do_gtk_primary_selection_source_evt_send(
struct context *ctx, const char *mime_type, int fd)
{
translate_data_transfer_fd(ctx, fd);
(void)mime_type;
}
void do_zwp_primary_selection_offer_v1_req_receive(
struct context *ctx, const char *mime_type, int fd)
{
translate_data_transfer_fd(ctx, fd);
(void)mime_type;
}
void do_zwp_primary_selection_source_v1_evt_send(
struct context *ctx, const char *mime_type, int fd)
{
translate_data_transfer_fd(ctx, fd);
(void)mime_type;
}
void do_zwlr_data_control_offer_v1_req_receive(
struct context *ctx, const char *mime_type, int fd)
{
translate_data_transfer_fd(ctx, fd);
(void)mime_type;
}
void do_zwlr_data_control_source_v1_evt_send(
struct context *ctx, const char *mime_type, int fd)
{
translate_data_transfer_fd(ctx, fd);
(void)mime_type;
}
void do_wl_data_offer_req_receive(
struct context *ctx, const char *mime_type, int fd)
{
translate_data_transfer_fd(ctx, fd);
(void)mime_type;
}
void do_wl_data_source_evt_send(
struct context *ctx, const char *mime_type, int fd)
{
translate_data_transfer_fd(ctx, fd);
(void)mime_type;
}
void do_zwlr_gamma_control_v1_req_set_gamma(struct context *ctx, int fd)
{
size_t fdsz = 0;
enum fdcat fdtype = get_fd_type(fd, &fdsz);
if (fdtype == FDC_UNKNOWN) {
fdtype = FDC_FILE;
/* fdsz fallback? */
}
// TODO: use file size from earlier in the protocol, because some
// systems may send file-like objects not supporting fstat
if (fdtype != FDC_FILE) {
wp_error("gamma ramp fd %d was not file-like (type=%s)", fd,
fdcat_to_str(fdtype));
return;
}
struct shadow_fd *sfd = translate_fd(&ctx->g->map, &ctx->g->render, fd,
FDC_FILE, fdsz, NULL, false);
if (!sfd) {
return;
}
/* Mark the shadow structure as owned by the protocol, but do not
* increase the protocol refcount, so that as soon as it gets
* transferred it is destroyed */
sfd->has_owner = true;
}
const struct wp_interface *the_display_interface = &intf_wl_display;
waypipe-v0.8.6/src/interval.c 0000664 0000000 0000000 00000020757 14414260423 0016163 0 ustar 00root root 0000000 0000000 /*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "shadow.h"
#include
#include
struct merge_stack_elem {
int offset;
int count;
};
struct merge_stack {
struct interval *data;
int size;
int count;
};
static int stream_merge(int a_count, const struct interval *__restrict__ a_list,
int b_count, const struct interval *__restrict__ b_list,
struct interval *__restrict__ c_list, int margin)
{
int ia = 0, ib = 0, ic = 0;
int cursor = INT32_MIN;
(void)a_count;
(void)b_count;
/* the loop exit condition appears to be faster than checking
* ia= cursor + margin) {
c_list[ic++] = sel;
} else {
c_list[ic - 1].end = new_cursor;
}
cursor = new_cursor;
}
/* add end sentinel */
c_list[ic] = (struct interval){.start = INT32_MAX, .end = INT32_MAX};
return ic;
}
static int fix_merge_stack_property(int size, struct merge_stack_elem *stack,
struct merge_stack *base, struct merge_stack *temp,
int merge_margin, bool force_compact, int *absorbed)
{
while (size > 1) {
struct merge_stack_elem top = stack[size - 1];
struct merge_stack_elem nxt = stack[size - 2];
if (2 * top.count <= nxt.count && !force_compact) {
return size;
}
if (buf_ensure_size(top.count + nxt.count + 1,
sizeof(struct interval), &temp->size,
(void **)&temp->data) == -1) {
wp_error("Failed to resize a merge buffer, some damage intervals may be lost");
return size;
}
int xs = stream_merge(top.count, &base->data[top.offset],
nxt.count, &base->data[nxt.offset], temp->data,
merge_margin);
/* There are more complicated/multi-buffer alternatives with
* fewer memory copies, but this is already <20% of stream
* merge time */
memcpy(&base->data[nxt.offset], temp->data,
(size_t)(xs + 1) * sizeof(struct interval));
base->count = nxt.offset + xs + 1;
stack[size - 1] = (struct merge_stack_elem){
.offset = 0, .count = 0};
stack[size - 2] = (struct merge_stack_elem){
.offset = nxt.offset, .count = xs};
size--;
*absorbed += (top.count + nxt.count - xs);
}
return size;
}
static int unpack_ext_interval(struct interval *vec,
const struct ext_interval e, int alignment_bits)
{
int iw = 0;
int last_end = INT32_MIN;
for (int ir = 0; ir < e.rep; ir++) {
int start = e.start + ir * e.stride;
int end = start + e.width;
start = (start >> alignment_bits) << alignment_bits;
end = ((end + (1 << alignment_bits) - 1) >> alignment_bits)
<< alignment_bits;
if (start > last_end) {
vec[iw].start = start;
vec[iw].end = end;
last_end = end;
iw++;
} else {
vec[iw - 1].end = end;
last_end = end;
}
}
/* end sentinel */
vec[iw] = (struct interval){.start = INT32_MAX, .end = INT32_MAX};
return iw;
}
/* By writing a mergesort by hand, we can detect duplicates early.
*
* TODO: optimize output with run-length-encoded segments
* TODO: explicit time limiting/adaptive margin! */
void merge_mergesort(const int old_count, struct interval *old_list,
const int new_count, const struct ext_interval *const new_list,
int *dst_count, struct interval **dst_list, int merge_margin,
int alignment_bits)
{
/* Stack-based mergesort: the buffer at position `i+1`
* should be <= 1/2 times the size of the buffer at
* position `i`; buffers will be merged
* to maintain this invariant */
// TODO: improve memory management!
struct merge_stack_elem substack[32];
int substack_size = 0;
memset(substack, 0, sizeof(substack));
struct merge_stack base = {.data = NULL, .count = 0, .size = 0};
struct merge_stack temp = {.data = NULL, .count = 0, .size = 0};
if (old_count) {
/* seed the stack with the previous damage
* interval list,
* including trailing terminator */
base.data = old_list;
base.size = old_count + 1;
base.count = old_count + 1;
substack[substack_size++] = (struct merge_stack_elem){
.offset = 0, .count = old_count};
}
int src_count = 0, absorbed = 0;
for (int jn = 0; jn < new_count; jn++) {
struct ext_interval e = new_list[jn];
/* ignore invalid intervals -- also, if e.start
* is close to INT32_MIN, the stream merge
* breaks */
if (e.width <= 0 || e.rep <= 0 || e.start < 0) {
continue;
}
/* To limit CPU time, if it is very likely that
* an interval would be merged anyway, then
* replace it with its containing interval. */
int remaining = src_count - absorbed;
bool force_combine = (absorbed > 30000) ||
10 * remaining < src_count;
int64_t intv_end = e.start + e.stride * (int64_t)(e.rep - 1) +
e.width;
if (intv_end >= INT32_MAX) {
/* overflow protection */
e.width = INT32_MAX - 1 - e.start;
e.rep = 1;
}
/* Remove internal gaps are smaller than the
* margin and hence
* would need to be merged away anyway. */
if (e.width > e.stride - merge_margin || force_combine) {
e.width = e.stride * (e.rep - 1) + e.width;
e.rep = 1;
}
if (buf_ensure_size(base.count + e.rep + 1,
sizeof(struct interval), &base.size,
(void **)&base.data) == -1) {
wp_error("Failed to resize a merge buffer, some damage intervals may be lost");
continue;
}
struct interval *vec = &base.data[base.count];
int iw = unpack_ext_interval(vec, e, alignment_bits);
src_count += iw;
substack[substack_size] = (struct merge_stack_elem){
.offset = base.count, .count = iw};
substack_size++;
base.count += iw + 1;
/* merge down the stack as far as possible */
substack_size = fix_merge_stack_property(substack_size,
substack, &base, &temp, merge_margin, false,
&absorbed);
}
/* collapse the stack into a final interval */
fix_merge_stack_property(substack_size, substack, &base, &temp,
merge_margin, true, &absorbed);
free(temp.data);
*dst_list = base.data;
*dst_count = substack[0].count;
}
/* This value must be larger than 8, or diffs will explode */
#define MERGE_MARGIN 256
void merge_damage_records(struct damage *base, int nintervals,
const struct ext_interval *const new_list, int alignment_bits)
{
for (int i = 0; i < nintervals; i++) {
base->acc_damage_stat += new_list[i].width * new_list[i].rep;
base->acc_count++;
}
// Fast return if there is nothing to do
if (base->damage == DAMAGE_EVERYTHING || nintervals <= 0) {
return;
}
if (nintervals >= (1 << 30) || base->ndamage_intvs >= (1 << 30)) {
/* avoid overflow in merge routine; also would be cheaper to
* damage everything at this point; */
damage_everything(base);
return;
}
merge_mergesort(base->ndamage_intvs, base->damage, nintervals, new_list,
&base->ndamage_intvs, &base->damage, MERGE_MARGIN,
alignment_bits);
}
void reset_damage(struct damage *base)
{
if (base->damage != DAMAGE_EVERYTHING) {
free(base->damage);
}
base->damage = NULL;
base->ndamage_intvs = 0;
base->acc_damage_stat = 0;
base->acc_count = 0;
}
void damage_everything(struct damage *base)
{
if (base->damage != DAMAGE_EVERYTHING) {
free(base->damage);
}
base->damage = DAMAGE_EVERYTHING;
base->ndamage_intvs = 0;
}
waypipe-v0.8.6/src/interval.h 0000664 0000000 0000000 00000006532 14414260423 0016163 0 ustar 00root root 0000000 0000000 /*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef WAYPIPE_INTERVAL_H
#define WAYPIPE_INTERVAL_H
#include
/** A slight modification of the standard 'damage' rectangle
* formulation, written to be agnostic of whatever buffers
* underlie the system.
*
* [start,start+width),[start+stride,start+stride+width),
* ... [start+(rep-1)*stride,start+(rep-1)*stride+width) */
struct ext_interval {
int32_t start;
/** Subinterval width */
int32_t width;
/** Number of distinct subinterval start positions. For a single
* interval, this is one. */
int32_t rep;
/** Spacing between start positions, should be > width, unless
* the is only one subinterval, in which case the value shouldn't
* matter and is conventionally set to 0. */
int32_t stride;
};
/** [start, end). (This is better than {start,width}, since width computations
* are rare and trivial, while merging code branches frequently off of
* endpoints) */
struct interval {
int32_t start;
int32_t end;
};
#define DAMAGE_EVERYTHING ((struct interval *)-1)
/** Interval-based damage tracking. If damage is NULL, there is
* no recorded damage. If damage is DAMAGE_EVERYTHING, the entire
* region should be updated. If ndamage_intvs > 0, then
* damage points to an array of struct interval objects. */
struct damage {
struct interval *damage;
int ndamage_intvs;
int64_t acc_damage_stat;
int acc_count;
};
/** Given an array of extended intervals, update the base damage structure
* so that it contains a reasonably small disjoint set of extended intervals
* which contains the old base set and the new set. Before merging, all
* interval boundaries will be rounded to the next multiple of
* `1 << alignment_bits`. */
void merge_damage_records(struct damage *base, int nintervals,
const struct ext_interval *const new_list, int alignment_bits);
/** Set damage to empty */
void reset_damage(struct damage *base);
/** Expand damage to cover everything */
void damage_everything(struct damage *base);
/* internal merge driver, made visible for testing */
void merge_mergesort(const int old_count, struct interval *old_list,
const int new_count, const struct ext_interval *const new_list,
int *dst_count, struct interval **dst_list, int merge_margin,
int alignment_bits);
#endif // WAYPIPE_INTERVAL_H
waypipe-v0.8.6/src/kernel.c 0000664 0000000 0000000 00000021650 14414260423 0015610 0 ustar 00root root 0000000 0000000 /*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "kernel.h"
#include "interval.h"
#include "util.h"
#include
#include
#include
#include
#include
static size_t run_interval_diff_C(const int diff_window_size,
const void *__restrict__ imod, void *__restrict__ ibase,
uint32_t *__restrict__ idiff, size_t i, const size_t i_end)
{
const uint64_t *__restrict__ mod = imod;
uint64_t *__restrict__ base = ibase;
uint64_t *__restrict__ diff = (uint64_t *__restrict__)idiff;
/* we paper over gaps of a given window size, to avoid fine
* grained context switches */
const size_t i_start = i;
size_t dc = 0;
uint64_t changed_val = i < i_end ? mod[i] : 0;
uint64_t base_val = i < i_end ? base[i] : 0;
i++;
// Alternating scanners, ending with a mispredict each.
bool clear_exit = false;
while (i < i_end) {
while (changed_val == base_val && i < i_end) {
changed_val = mod[i];
base_val = base[i];
i++;
}
if (i == i_end) {
/* it's possible that the last value actually;
* see exit block */
clear_exit = true;
break;
}
uint32_t *ctrl_blocks = (uint32_t *)&diff[dc++];
ctrl_blocks[0] = (uint32_t)((i - 1) * 2);
diff[dc++] = changed_val;
base[i - 1] = changed_val;
// changed_val != base_val, difference occurs at early
// index
size_t nskip = 0;
// we could only sentinel this assuming a tiny window
// size
while (i < i_end && nskip <= (size_t)diff_window_size / 2) {
base_val = base[i];
changed_val = mod[i];
base[i] = changed_val;
i++;
diff[dc++] = changed_val;
nskip++;
nskip *= (base_val == changed_val);
}
dc -= nskip;
ctrl_blocks[1] = (uint32_t)((i - nskip) * 2);
/* our sentinel, at worst, causes overcopy by one. this
* is fine
*/
}
/* If only the last block changed */
if ((clear_exit || i_start + 1 == i_end) && changed_val != base_val) {
uint32_t *ctrl_blocks = (uint32_t *)&diff[dc++];
ctrl_blocks[0] = (uint32_t)(i_end - 1) * 2;
ctrl_blocks[1] = (uint32_t)i_end * 2;
diff[dc++] = changed_val;
base[i_end - 1] = changed_val;
}
return dc * 2;
}
#ifdef HAVE_AVX512F
static bool avx512f_available(void)
{
return __builtin_cpu_supports("avx512f");
}
size_t run_interval_diff_avx512f(const int diff_window_size,
const void *__restrict__ imod, void *__restrict__ ibase,
uint32_t *__restrict__ idiff, size_t i, const size_t i_end);
#endif
#ifdef HAVE_AVX2
static bool avx2_available(void) { return __builtin_cpu_supports("avx2"); }
size_t run_interval_diff_avx2(const int diff_window_size,
const void *__restrict__ imod, void *__restrict__ ibase,
uint32_t *__restrict__ idiff, size_t i, const size_t i_end);
#endif
#ifdef HAVE_NEON
bool neon_available(void); // in platform.c
size_t run_interval_diff_neon(const int diff_window_size,
const void *__restrict__ imod, void *__restrict__ ibase,
uint32_t *__restrict__ idiff, size_t i, const size_t i_end);
#endif
#ifdef HAVE_SSE3
static bool sse3_available(void) { return __builtin_cpu_supports("sse3"); }
size_t run_interval_diff_sse3(const int diff_window_size,
const void *__restrict__ imod, void *__restrict__ ibase,
uint32_t *__restrict__ idiff, size_t i, const size_t i_end);
#endif
interval_diff_fn_t get_diff_function(enum diff_type type, int *alignment_bits)
{
#ifdef HAVE_AVX512F
if ((type == DIFF_FASTEST || type == DIFF_AVX512F) &&
avx512f_available()) {
*alignment_bits = 6;
return run_interval_diff_avx512f;
}
#endif
#ifdef HAVE_AVX2
if ((type == DIFF_FASTEST || type == DIFF_AVX2) && avx2_available()) {
*alignment_bits = 6;
return run_interval_diff_avx2;
}
#endif
#ifdef HAVE_NEON
if ((type == DIFF_FASTEST || type == DIFF_NEON) && neon_available()) {
*alignment_bits = 4;
return run_interval_diff_neon;
}
#endif
#ifdef HAVE_SSE3
if ((type == DIFF_FASTEST || type == DIFF_SSE3) && sse3_available()) {
*alignment_bits = 5;
return run_interval_diff_sse3;
}
#endif
if ((type == DIFF_FASTEST || type == DIFF_C)) {
*alignment_bits = 3;
return run_interval_diff_C;
}
*alignment_bits = 0;
return NULL;
}
/** Construct the main portion of a diff. The provided arguments should
* be validated beforehand. All intervals, as well as the base/changed data
* pointers, should be aligned to the alignment size associated with the
* interval diff function */
size_t construct_diff_core(interval_diff_fn_t idiff_fn, int alignment_bits,
const struct interval *__restrict__ damaged_intervals,
int n_intervals, void *__restrict__ base,
const void *__restrict__ changed, void *__restrict__ diff)
{
uint32_t *diff_blocks = (uint32_t *)diff;
size_t cursor = 0;
for (int i = 0; i < n_intervals; i++) {
struct interval e = damaged_intervals[i];
size_t bend = (size_t)e.end >> alignment_bits;
size_t bstart = (size_t)e.start >> alignment_bits;
cursor += (*idiff_fn)(24, changed, base, diff_blocks + cursor,
bstart, bend);
}
return cursor * sizeof(uint32_t);
}
size_t construct_diff_trailing(size_t size, int alignment_bits,
char *__restrict__ base, const char *__restrict__ changed,
char *__restrict__ diff)
{
size_t alignment = 1u << alignment_bits;
size_t ntrailing = size % alignment;
size_t offset = size - ntrailing;
bool tail_change = false;
if (ntrailing > 0) {
for (size_t i = 0; i < ntrailing; i++) {
tail_change |= base[offset + i] != changed[offset + i];
}
}
if (tail_change) {
for (size_t i = 0; i < ntrailing; i++) {
diff[i] = changed[offset + i];
base[offset + i] = changed[offset + i];
}
return ntrailing;
}
return 0;
}
void apply_diff(size_t size, char *__restrict__ target1,
char *__restrict__ target2, size_t diffsize, size_t ntrailing,
const char *__restrict__ diff)
{
size_t nblocks = size / sizeof(uint32_t);
size_t ndiffblocks = diffsize / sizeof(uint32_t);
uint32_t *__restrict__ t1_blocks = (uint32_t *)target1;
uint32_t *__restrict__ t2_blocks = (uint32_t *)target2;
uint32_t *__restrict__ diff_blocks = (uint32_t *)diff;
for (size_t i = 0; i < ndiffblocks;) {
size_t nfrom = (size_t)diff_blocks[i];
size_t nto = (size_t)diff_blocks[i + 1];
size_t span = nto - nfrom;
if (nto > nblocks || nfrom >= nto ||
i + (nto - nfrom) >= ndiffblocks) {
wp_error("Invalid copy range [%zu,%zu) > %zu=nblocks or [%zu,%zu) > %zu=ndiffblocks",
nfrom, nto, nblocks, i + 1,
i + 1 + span, ndiffblocks);
return;
}
memcpy(t1_blocks + nfrom, diff_blocks + i + 2,
sizeof(uint32_t) * span);
memcpy(t2_blocks + nfrom, diff_blocks + i + 2,
sizeof(uint32_t) * span);
i += span + 2;
}
if (ntrailing > 0) {
size_t offset = size - ntrailing;
for (size_t i = 0; i < ntrailing; i++) {
target1[offset + i] = diff[diffsize + i];
target2[offset + i] = diff[diffsize + i];
}
}
}
void stride_shifted_copy(char *dest, const char *src, size_t src_start,
size_t copy_length, size_t row_length, size_t src_stride,
size_t dst_stride)
{
size_t src_end = src_start + copy_length;
size_t lrow = src_start / src_stride;
size_t trow = src_end / src_stride;
/* special case: inside a segment */
if (lrow == trow) {
size_t cstart = src_start - lrow * src_stride;
if (cstart < row_length) {
size_t cend = src_end - trow * src_stride;
cend = cend > row_length ? row_length : cend;
memcpy(dest + dst_stride * lrow + cstart,
src + src_start, cend - cstart);
}
return;
}
/* leading segment */
if (src_start > lrow * src_stride) {
size_t igap = src_start - lrow * src_stride;
if (igap < row_length) {
memcpy(dest + dst_stride * lrow + igap, src + src_start,
row_length - igap);
}
}
/* main body */
size_t srow = (src_start + src_stride - 1) / src_stride;
for (size_t i = srow; i < trow; i++) {
memcpy(dest + dst_stride * i, src + src_stride * i, row_length);
}
/* trailing segment */
if (src_end > trow * src_stride) {
size_t local = src_end - trow * src_stride;
local = local > row_length ? row_length : local;
memcpy(dest + dst_stride * trow, src + src_end - local, local);
}
}
waypipe-v0.8.6/src/kernel.h 0000664 0000000 0000000 00000006405 14414260423 0015616 0 ustar 00root root 0000000 0000000 /*
* Copyright © 2019 Manuel Stoeckl
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef WAYPIPE_KERNEL_H
#define WAYPIPE_KERNEL_H
#include
#include
struct interval;
typedef size_t (*interval_diff_fn_t)(const int diff_window_size,
const void *__restrict__ imod, void *__restrict__ ibase,
uint32_t *__restrict__ diff, size_t i, const size_t i_end);
enum diff_type {
DIFF_FASTEST,
DIFF_AVX512F,
DIFF_AVX2,
DIFF_SSE3,
DIFF_NEON,
DIFF_C,
};
/** Returns a function pointer to a diff construction kernel, and indicates
* the alignment of the data which is to be passed in */
interval_diff_fn_t get_diff_function(enum diff_type type, int *alignment_bits);
/** Given intervals aligned to 1<