mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2026-02-03 21:50:31 +01:00
Compare commits
277 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8994dfe17 | ||
|
|
180c9f2890 | ||
|
|
57064f6846 | ||
|
|
330b54d389 | ||
|
|
84c23989e9 | ||
|
|
65c0b39c3e | ||
|
|
9446918556 | ||
|
|
5f9e181073 | ||
|
|
08d7d5918f | ||
|
|
d5198d0e53 | ||
|
|
756f74ec73 | ||
|
|
55381623f2 | ||
|
|
73ec8dee9f | ||
|
|
fe47a0a1f7 | ||
|
|
26e671f192 | ||
|
|
2996a66b37 | ||
|
|
d55da9466c | ||
|
|
3f48d11958 | ||
|
|
2821282dc9 | ||
|
|
acff519ac3 | ||
|
|
39a222868f | ||
|
|
7141566924 | ||
|
|
a646a4c19b | ||
|
|
2671079754 | ||
|
|
4f11112be0 | ||
|
|
19b6453540 | ||
|
|
cae398c132 | ||
|
|
6c50e2f8a0 | ||
|
|
7667d1fcd8 | ||
|
|
5e57b1ed5f | ||
|
|
42b9a89371 | ||
|
|
8d86ada12d | ||
|
|
1010cdff3c | ||
|
|
525d55a532 | ||
|
|
70c3348dfe | ||
|
|
4d999849ee | ||
|
|
f3f69e3a96 | ||
|
|
3b98946b9a | ||
|
|
7347aeacd2 | ||
|
|
1faaacda6e | ||
|
|
e3e143ea42 | ||
|
|
500ad0510f | ||
|
|
45c526bcc9 | ||
|
|
297f95efac | ||
|
|
0831303a70 | ||
|
|
0488b4b4d0 | ||
|
|
55335b9030 | ||
|
|
12237f19ea | ||
|
|
6194880aa9 | ||
|
|
5567524ecc | ||
|
|
883a60d4e6 | ||
|
|
6aa4dc0c7e | ||
|
|
ac9b92bbae | ||
|
|
70cfc6eed2 | ||
|
|
54e71e6dd5 | ||
|
|
1b11d10ff2 | ||
|
|
edc8ea045a | ||
|
|
8cd2b01bfa | ||
|
|
98e445ebdb | ||
|
|
dbeff9a90d | ||
|
|
efdc58e094 | ||
|
|
50ff529a76 | ||
|
|
6c5b486306 | ||
|
|
ee27dd5c92 | ||
|
|
851f935fe1 | ||
|
|
2556ad38c6 | ||
|
|
b2484c00d0 | ||
|
|
daba46a2ae | ||
|
|
daf0b24665 | ||
|
|
247a3d49d7 | ||
|
|
e147d4311f | ||
|
|
9e0413cbc7 | ||
|
|
962863cbc4 | ||
|
|
b0deafc641 | ||
|
|
fa21d765a1 | ||
|
|
eda5b90760 | ||
|
|
5d6d8e6590 | ||
|
|
d25ceaff98 | ||
|
|
76845ecdfe | ||
|
|
fb85496aa3 | ||
|
|
6068f3b14c | ||
|
|
6bbb3c5979 | ||
|
|
1152c038ee | ||
|
|
564f14a739 | ||
|
|
73e0e8d339 | ||
|
|
971013429d | ||
|
|
e40796402b | ||
|
|
a278c7b371 | ||
|
|
2cc5e56e28 | ||
|
|
95eb3c4bb3 | ||
|
|
91e0b8961b | ||
|
|
3c88c0d3b2 | ||
|
|
9c35a57cfe | ||
|
|
b1c1c5d579 | ||
|
|
739ea0f357 | ||
|
|
561cfd009e | ||
|
|
cdec01dacd | ||
|
|
d4b60a7d0d | ||
|
|
504afdea4a | ||
|
|
f2811418dd | ||
|
|
beb1de6292 | ||
|
|
2acf42db9c | ||
|
|
9ee33b019f | ||
|
|
60bc264e75 | ||
|
|
96f20ae333 | ||
|
|
7619bfa9ad | ||
|
|
c169ac63b8 | ||
|
|
0cf12a889d | ||
|
|
62ae6f5edf | ||
|
|
b33317cda0 | ||
|
|
327dd4f8b1 | ||
|
|
076e8bc670 | ||
|
|
1f0cc83548 | ||
|
|
247b6acd3c | ||
|
|
b3a4243924 | ||
|
|
80bbcc67ed | ||
|
|
11aa10393b | ||
|
|
1cf06d0af6 | ||
|
|
a491580fc0 | ||
|
|
37ea216424 | ||
|
|
d593127c18 | ||
|
|
0770fec433 | ||
|
|
dbc06510a1 | ||
|
|
a5826424f1 | ||
|
|
96609f82a0 | ||
|
|
55fe9303fb | ||
|
|
005c4ac493 | ||
|
|
bbc1f8de1c | ||
|
|
e3091a1802 | ||
|
|
22c94fd916 | ||
|
|
ec031bc4bf | ||
|
|
bf03c56300 | ||
|
|
37cc857a52 | ||
|
|
6230e187fd | ||
|
|
997b7c0f37 | ||
|
|
cf4ab5e73f | ||
|
|
ebde54f3b3 | ||
|
|
9f82bbf344 | ||
|
|
e411b85a33 | ||
|
|
917b79f83e | ||
|
|
54dd4353df | ||
|
|
2f8872676d | ||
|
|
712e9513c6 | ||
|
|
798b0966de | ||
|
|
d6a8a5e94a | ||
|
|
362c4c392a | ||
|
|
438140feb2 | ||
|
|
0b2f46e1a2 | ||
|
|
a5dd5a5ee5 | ||
|
|
5331dc0bf2 | ||
|
|
b67cdba809 | ||
|
|
e8a844355f | ||
|
|
865d7152e0 | ||
|
|
3e2e43e352 | ||
|
|
a924888f0f | ||
|
|
6ea468c823 | ||
|
|
dc153c50ed | ||
|
|
c4ac084159 | ||
|
|
eb1d5a7e1a | ||
|
|
a424458f03 | ||
|
|
0f81114544 | ||
|
|
87705ef97c | ||
|
|
267716a760 | ||
|
|
4059820391 | ||
|
|
9ee399c8be | ||
|
|
da1fa204d5 | ||
|
|
23f056433d | ||
|
|
33b4a61995 | ||
|
|
54ca521d0a | ||
|
|
93efd3d14e | ||
|
|
08f1d41085 | ||
|
|
d29778658a | ||
|
|
6ce695db3b | ||
|
|
870123f54b | ||
|
|
eaed3883d8 | ||
|
|
97d66604fb | ||
|
|
55e5deedd8 | ||
|
|
4936316884 | ||
|
|
9697aa9c74 | ||
|
|
38132d6fc5 | ||
|
|
924341e174 | ||
|
|
4e634aa76c | ||
|
|
7999483a34 | ||
|
|
fae41aac08 | ||
|
|
34c83370be | ||
|
|
d31b5a1ccf | ||
|
|
44e7f2cbc4 | ||
|
|
35832d9fe1 | ||
|
|
f9c83ed9a6 | ||
|
|
57e7cd7972 | ||
|
|
05d79f6e58 | ||
|
|
f558eedae2 | ||
|
|
6758b9970d | ||
|
|
19c949a46b | ||
|
|
a3c801097d | ||
|
|
cdbd1d9eaf | ||
|
|
2df570b203 | ||
|
|
5dcdc696fb | ||
|
|
38244b8c66 | ||
|
|
3f768ecbfc | ||
|
|
5909717700 | ||
|
|
43b383ebdb | ||
|
|
b8884770f1 | ||
|
|
d4bf8840a4 | ||
|
|
fd2999ffdb | ||
|
|
cefef7b1a5 | ||
|
|
69e973e6b3 | ||
|
|
95d1107cea | ||
|
|
8f6c355805 | ||
|
|
381c6bea1f | ||
|
|
4929aeae61 | ||
|
|
82cdbc9129 | ||
|
|
0820e29bd5 | ||
|
|
d9d4630567 | ||
|
|
c179b3dac1 | ||
|
|
ac16ba77ff | ||
|
|
8713ef2d63 | ||
|
|
16c7b1570b | ||
|
|
931632effd | ||
|
|
f9c6ea30fc | ||
|
|
a223ce86da | ||
|
|
397ee6d79c | ||
|
|
90040096dd | ||
|
|
1bac0c28b4 | ||
|
|
abe85e051e | ||
|
|
f235052f81 | ||
|
|
8f911d5e41 | ||
|
|
8e95c7d8a5 | ||
|
|
4f2fd16186 | ||
|
|
7115e9c4c8 | ||
|
|
e03c047b5d | ||
|
|
76652350cc | ||
|
|
2fbd22984f | ||
|
|
b7ab63c386 | ||
|
|
7ddd70e9d8 | ||
|
|
36f1641125 | ||
|
|
661f7665d7 | ||
|
|
39e6c93c8b | ||
|
|
97a5538258 | ||
|
|
1f51c05897 | ||
|
|
b14405d3cd | ||
|
|
22079d62dc | ||
|
|
059d3e11b6 | ||
|
|
e0d60b062c | ||
|
|
befab06ea2 | ||
|
|
aa3c2eb763 | ||
|
|
6caf69a73c | ||
|
|
a9edea65ca | ||
|
|
2d6b11833b | ||
|
|
987997dc2c | ||
|
|
f0894aac67 | ||
|
|
4096d100ee | ||
|
|
b5f0899352 | ||
|
|
fc79982682 | ||
|
|
1cf9412990 | ||
|
|
95366ea131 | ||
|
|
168de89a70 | ||
|
|
080864d82a | ||
|
|
7d0536f344 | ||
|
|
ed1acbbd7c | ||
|
|
1175595acf | ||
|
|
cc053155a5 | ||
|
|
868a16df85 | ||
|
|
35ec414215 | ||
|
|
37e2bdebed | ||
|
|
3ef22c8ed0 | ||
|
|
3e908c03f4 | ||
|
|
1aedabe7c7 | ||
|
|
c99f4ffa2c | ||
|
|
f081e8e79f | ||
|
|
552f6dcbd0 | ||
|
|
1d8cd84c56 | ||
|
|
00226da59e | ||
|
|
3a9eb2d8b6 | ||
|
|
091948e9ef | ||
|
|
016072507e | ||
|
|
a75828aee0 |
122 changed files with 8288 additions and 1823 deletions
149
.gitlab-ci.yml
149
.gitlab-ci.yml
|
|
@ -6,7 +6,7 @@
|
|||
# #
|
||||
########################################
|
||||
|
||||
.templates_sha: &template_sha 9f0eb526291fe74651fe1430cbd2397f4c0a819b # see https://docs.gitlab.com/ee/ci/yaml/#includefile
|
||||
.templates_sha: &template_sha c6aeb16f86e32525fa630fb99c66c4f3e62fc3cb
|
||||
|
||||
include:
|
||||
- project: 'freedesktop/ci-templates'
|
||||
|
|
@ -24,8 +24,10 @@ stages:
|
|||
|
||||
workflow:
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||
- if: $CI_PIPELINE_SOURCE == 'push'
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
variables:
|
||||
###############################################################################
|
||||
|
|
@ -38,9 +40,9 @@ variables:
|
|||
# See the documentation here: #
|
||||
# https://wayland.freedesktop.org/libinput/doc/latest/building_libinput.html #
|
||||
###############################################################################
|
||||
FEDORA_PACKAGES: 'git diffutils gcc gcc-c++ pkgconf-pkg-config systemd-devel libxkbcommon-devel libxml2 doxygen python3-attrs python3-pytest python3-dbusmock python3-jinja2 python3-pip hugo libabigail '
|
||||
FEDORA_PACKAGES: 'git diffutils gcc gcc-c++ pkgconf-pkg-config systemd-devel libxkbcommon-devel libxml2 doxygen python3-pytest python3-dbusmock python3-jinja2 python3-pip python3-pyyaml golang libabigail '
|
||||
FEDORA_PIP_PACKAGES: 'meson ninja structlog strenum '
|
||||
DEBIAN_PACKAGES: 'git gcc g++ pkg-config libsystemd-dev libxkbcommon-dev libxml2 doxygen python3-attr python3-pytest python3-dbusmock python3-jinja2 python3-pip '
|
||||
DEBIAN_PACKAGES: 'git gcc g++ pkg-config libsystemd-dev libxkbcommon-dev libxml2 doxygen python3-pytest python3-dbusmock python3-jinja2 python3-pip python3-yaml '
|
||||
DEBIAN_PIP_PACKAGES: 'meson ninja structlog strenum '
|
||||
############################ end of package lists #############################
|
||||
|
||||
|
|
@ -48,14 +50,14 @@ variables:
|
|||
# changing these will force rebuilding the associated image
|
||||
# Note: these tags have no meaning and are not tied to a particular
|
||||
# libinput version
|
||||
FEDORA_TAG: '2023-05-18.3'
|
||||
DEBIAN_TAG: '2023-05-18.3'
|
||||
FEDORA_TAG: '2025-05-19.1'
|
||||
DEBIAN_TAG: '2025-05-19.1'
|
||||
|
||||
FDO_UPSTREAM_REPO: libinput/libei
|
||||
|
||||
MESON_BUILDDIR: "builddir"
|
||||
NINJA_ARGS: ''
|
||||
MESON_ARGS: ''
|
||||
MESON_ARGS: '-Dauto_features=enabled'
|
||||
MESON_TEST_ARGS: ''
|
||||
GIT_DEPTH: 1
|
||||
|
||||
|
|
@ -136,18 +138,18 @@ check-merge-request:
|
|||
junit: results.xml
|
||||
allow_failure: true
|
||||
|
||||
# Format anything python with python-black
|
||||
# Format anything python with ruff
|
||||
#
|
||||
python-black:
|
||||
python-ruff-format:
|
||||
extends:
|
||||
- .fdo.ci-fairy
|
||||
stage: prep
|
||||
before_script:
|
||||
- python3 -m venv venv
|
||||
- source venv/bin/activate
|
||||
- pip3 install black
|
||||
- pip3 install ruff
|
||||
script:
|
||||
- black --check --diff . proto/ei-scanner
|
||||
- ruff format --check --diff . proto/ei-scanner
|
||||
|
||||
# Lint with Ruff
|
||||
#
|
||||
|
|
@ -166,14 +168,14 @@ python-ruff:
|
|||
# Build distribution-specific images used by the jobs in the build stage
|
||||
#
|
||||
|
||||
fedora:38@container-prep:
|
||||
fedora:43@container-prep:
|
||||
extends:
|
||||
- .fdo.container-build@fedora
|
||||
- .policy
|
||||
stage: prep
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
FDO_DISTRIBUTION_VERSION: '38'
|
||||
FDO_DISTRIBUTION_VERSION: '43'
|
||||
FDO_DISTRIBUTION_PACKAGES: $FEDORA_PACKAGES
|
||||
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
|
||||
FDO_DISTRIBUTION_EXEC: 'pip install $FEDORA_PIP_PACKAGES'
|
||||
|
|
@ -224,12 +226,12 @@ debian:bullseye@container-prep:
|
|||
- .build@template
|
||||
variables:
|
||||
MESON_TEST_ARGS: '--no-suite=python'
|
||||
FDO_DISTRIBUTION_VERSION: '38'
|
||||
FDO_DISTRIBUTION_VERSION: '43'
|
||||
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
|
||||
needs:
|
||||
- "fedora:38@container-prep"
|
||||
- "fedora:43@container-prep"
|
||||
|
||||
default-build-release@fedora:38:
|
||||
default-build-release@fedora:43:
|
||||
stage: distro
|
||||
extends:
|
||||
- .fedora-build@template
|
||||
|
|
@ -237,21 +239,32 @@ default-build-release@fedora:38:
|
|||
MESON_ARGS: "-Dbuildtype=release"
|
||||
CFLAGS: "-Werror -Wno-error=vla-parameter" # munit triggers -Wvla-parameter
|
||||
|
||||
build-no-libxkcommon-nodeps@fedora:38:
|
||||
build-no-libxkcommon-nodeps@fedora:43:
|
||||
extends:
|
||||
- .fedora-build@template
|
||||
before_script:
|
||||
- dnf remove -y libxkcommon-devel
|
||||
|
||||
build-no-doxygen@fedora:38:
|
||||
build-no-doxygen@fedora:43:
|
||||
extends:
|
||||
- .fedora-build@template
|
||||
variables:
|
||||
MESON_ARGS: "-Ddocumentation=[]"
|
||||
before_script:
|
||||
- dnf remove -y doxygen hugo
|
||||
- dnf remove -y doxygen
|
||||
|
||||
valgrind@fedora:38:
|
||||
build-disable-features@fedora:43:
|
||||
extends:
|
||||
- .fedora-build@template
|
||||
parallel:
|
||||
matrix:
|
||||
- FEATURE: libei
|
||||
- FEATURE: libeis
|
||||
- FEATURE: liboeffis
|
||||
variables:
|
||||
MESON_ARGS: '-D${FEATURE}=disabled'
|
||||
|
||||
valgrind@fedora:43:
|
||||
extends:
|
||||
- .fedora-build@template
|
||||
variables:
|
||||
|
|
@ -259,40 +272,69 @@ valgrind@fedora:38:
|
|||
before_script:
|
||||
- dnf install -y valgrind
|
||||
|
||||
pytest@fedora:38:
|
||||
pytest@fedora:43:
|
||||
extends:
|
||||
- .fedora-build@template
|
||||
variables:
|
||||
MESON_TEST_ARGS: '--suite=python'
|
||||
|
||||
werror@fedora:38:
|
||||
werror@fedora:43:
|
||||
extends:
|
||||
- .fedora-build@template
|
||||
variables:
|
||||
MESON_ARGS: '-Dwerror=true'
|
||||
allow_failure: true
|
||||
|
||||
abicheck@fedora:38:
|
||||
abicheck@fedora:43:
|
||||
extends:
|
||||
- .fedora-build@template
|
||||
variables:
|
||||
GIT_STRATEGY: none # We need the upstream repo instead
|
||||
MESON_ARGS: '-Dwerror=true'
|
||||
script:
|
||||
before_script:
|
||||
- git clone --depth=1 https://gitlab.freedesktop.org/hadess/check-abi
|
||||
- |
|
||||
pushd check-abi
|
||||
meson _build
|
||||
meson setup _build
|
||||
meson compile -C _build
|
||||
meson install -C _build
|
||||
popd
|
||||
- git clone --depth=1 https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
|
||||
- cd libei
|
||||
- git fetch --tags
|
||||
- check-abi 0.99.1 HEAD
|
||||
- pip install attrs # required by libei 1.0.0
|
||||
script:
|
||||
- git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
|
||||
- git fetch --tags upstream$CI_JOB_ID
|
||||
- check-abi abe85e051e7029bfd2e7913ab980a9e0042b6d0d $CI_COMMIT_SHA
|
||||
only:
|
||||
- merge_requests
|
||||
|
||||
event-type-check@fedora:43:
|
||||
extends:
|
||||
- .fedora-build@template
|
||||
script:
|
||||
- .gitlab-ci/meson-build.sh --skip-test
|
||||
- .gitlab-ci/check-event-values.py
|
||||
variables:
|
||||
PKG_CONFIG_PATH: $MESON_BUILDDIR/meson-uninstalled
|
||||
only:
|
||||
- merge_requests
|
||||
|
||||
|
||||
.debian-build@template:
|
||||
extends:
|
||||
- .fdo.distribution-image@debian
|
||||
- .build@template
|
||||
variables:
|
||||
MESON_TEST_ARGS: '--no-suite=python'
|
||||
FDO_DISTRIBUTION_VERSION: 'bullseye'
|
||||
FDO_DISTRIBUTION_TAG: $DEBIAN_TAG
|
||||
needs:
|
||||
- "debian:bullseye@container-prep"
|
||||
|
||||
minimum-meson@debian:bullseye:
|
||||
extends:
|
||||
- .debian-build@template
|
||||
script:
|
||||
- pip uninstall -y meson
|
||||
- pip install "meson==0.57.0"
|
||||
- .gitlab-ci/meson-build.sh --run-test
|
||||
|
||||
|
||||
#################################################################
|
||||
# #
|
||||
|
|
@ -300,16 +342,38 @@ abicheck@fedora:38:
|
|||
# #
|
||||
#################################################################
|
||||
|
||||
fedora:38@default-build:
|
||||
fedora:43@default-build:
|
||||
stage: distro
|
||||
extends:
|
||||
- .build@template
|
||||
- .fdo.distribution-image@fedora
|
||||
variables:
|
||||
FDO_DISTRIBUTION_VERSION: '38'
|
||||
FDO_DISTRIBUTION_VERSION: '43'
|
||||
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
|
||||
needs:
|
||||
- "fedora:38@container-prep"
|
||||
- "fedora:43@container-prep"
|
||||
|
||||
fedora:43@doc-build:
|
||||
stage: distro
|
||||
extends:
|
||||
- .build@template
|
||||
- .fdo.distribution-image@fedora
|
||||
variables:
|
||||
FDO_DISTRIBUTION_VERSION: '43'
|
||||
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
|
||||
MESON_ARGS: "-Ddocumentation=protocol,api"
|
||||
script:
|
||||
# Staying up-to-date with the breakages between hugo and the relearn theme is annoying
|
||||
# so let's lock the version for our doc build
|
||||
- go install "github.com/gohugoio/hugo@v0.142"
|
||||
- export PATH="$HOME/go/bin:$PATH"
|
||||
- .gitlab-ci/meson-build.sh
|
||||
- rm -rf public/
|
||||
- mv "$MESON_BUILDDIR"/doc/protocol/ei/public/ public/
|
||||
- mv "$MESON_BUILDDIR"/doc/html/ public/api
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
|
||||
debian:bullseye@default-build:
|
||||
stage: distro
|
||||
|
|
@ -323,21 +387,22 @@ debian:bullseye@default-build:
|
|||
- "debian:bullseye@container-prep"
|
||||
|
||||
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
extends:
|
||||
- .build@template
|
||||
- .fdo.distribution-image@fedora
|
||||
variables:
|
||||
FDO_DISTRIBUTION_VERSION: '38'
|
||||
FDO_DISTRIBUTION_VERSION: '43'
|
||||
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
|
||||
MESON_ARGS: "-Ddocumentation=protocol,api"
|
||||
script:
|
||||
- .gitlab-ci/meson-build.sh
|
||||
- rm -rf public/
|
||||
- mv "$MESON_BUILDDIR"/doc/protocol/ei/public/ public/
|
||||
- mv "$MESON_BUILDDIR"/doc/html/ public/api
|
||||
|
||||
- echo "Nothing to do"
|
||||
dependencies:
|
||||
- "fedora:43@doc-build"
|
||||
needs:
|
||||
- "fedora:43@doc-build"
|
||||
only:
|
||||
refs:
|
||||
- main
|
||||
|
|
|
|||
65
.gitlab-ci/check-event-values.py
Executable file
65
.gitlab-ci/check-event-values.py
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Check that the EI_EVENT_FOO and EIS_EVENT_FOO enum values match (for those events that
|
||||
# exist in both libraries).
|
||||
|
||||
from typing import Iterator
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
template = """
|
||||
#include <libei.h>
|
||||
#include <libeis.h>
|
||||
|
||||
@@@
|
||||
|
||||
int main(void) {
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
assert_template = '_Static_assert(EIS_EVENT_{event} == EI_EVENT_{event}, "Mismatching event types for {event}");'
|
||||
|
||||
|
||||
def extract(header: Path, prefix: str) -> Iterator[str]:
|
||||
with open(header) as fd:
|
||||
for line in fd:
|
||||
match = re.match(rf"^\t{prefix}(\w+)", line)
|
||||
if match:
|
||||
yield match[1]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ei_events = extract(Path("src/libei.h"), prefix="EI_EVENT_")
|
||||
eis_events = extract(Path("src/libeis.h"), prefix="EIS_EVENT_")
|
||||
|
||||
common = set(ei_events) & set(eis_events)
|
||||
|
||||
print("Shared events that need identical values:")
|
||||
for e in common:
|
||||
print(f" EI_EVENT_{e}")
|
||||
|
||||
asserts = (assert_template.format(event=e) for e in common)
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".c", delete=False) as fd:
|
||||
fd.write(template.replace("@@@", "\n".join(asserts)).encode("utf-8"))
|
||||
fd.flush()
|
||||
pkgconfig = subprocess.run(
|
||||
["pkg-config", "--cflags", "--libs", "libei-1.0", "libeis-1.0"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
pkgconfig_args = pkgconfig.stdout.decode("utf-8").strip().split(" ")
|
||||
try:
|
||||
subprocess.run(
|
||||
["gcc", "-o", "event-type-check", *pkgconfig_args, fd.name],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
print("Success. Event types are identical")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Mismatching event types")
|
||||
print(e.stdout.decode("utf-8"))
|
||||
print(e.stderr.decode("utf-8"))
|
||||
raise SystemExit(1)
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
# #
|
||||
########################################
|
||||
|
||||
.templates_sha: &template_sha 9f0eb526291fe74651fe1430cbd2397f4c0a819b # see https://docs.gitlab.com/ee/ci/yaml/#includefile
|
||||
.templates_sha: &template_sha c6aeb16f86e32525fa630fb99c66c4f3e62fc3cb
|
||||
|
||||
include:
|
||||
- project: 'freedesktop/ci-templates'
|
||||
|
|
@ -29,8 +29,10 @@ stages:
|
|||
|
||||
workflow:
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||
- if: $CI_PIPELINE_SOURCE == 'push'
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
variables:
|
||||
###############################################################################
|
||||
|
|
@ -61,7 +63,7 @@ variables:
|
|||
|
||||
MESON_BUILDDIR: "builddir"
|
||||
NINJA_ARGS: ''
|
||||
MESON_ARGS: ''
|
||||
MESON_ARGS: '-Dauto_features=enabled'
|
||||
MESON_TEST_ARGS: ''
|
||||
GIT_DEPTH: 1
|
||||
|
||||
|
|
@ -142,18 +144,18 @@ check-merge-request:
|
|||
junit: results.xml
|
||||
allow_failure: true
|
||||
|
||||
# Format anything python with python-black
|
||||
# Format anything python with ruff
|
||||
#
|
||||
python-black:
|
||||
python-ruff-format:
|
||||
extends:
|
||||
- .fdo.ci-fairy
|
||||
stage: prep
|
||||
before_script:
|
||||
- python3 -m venv venv
|
||||
- source venv/bin/activate
|
||||
- pip3 install black
|
||||
- pip3 install ruff
|
||||
script:
|
||||
- black --check --diff . proto/ei-scanner
|
||||
- ruff format --check --diff . proto/ei-scanner
|
||||
|
||||
# Lint with Ruff
|
||||
#
|
||||
|
|
@ -220,7 +222,7 @@ python-ruff:
|
|||
{% set version = "{}".format(distro.versions|last()) %}
|
||||
.{{distro.name}}-build@template:
|
||||
extends:
|
||||
- .fdo.distribution-image@fedora
|
||||
- .fdo.distribution-image@{{distro.name}}
|
||||
- .build@template
|
||||
variables:
|
||||
MESON_TEST_ARGS: '--no-suite=python'
|
||||
|
|
@ -249,7 +251,18 @@ build-no-doxygen@{{distro.name}}:{{version}}:
|
|||
variables:
|
||||
MESON_ARGS: "-Ddocumentation=[]"
|
||||
before_script:
|
||||
- dnf remove -y doxygen hugo
|
||||
- dnf remove -y doxygen
|
||||
|
||||
build-disable-features@{{distro.name}}:{{version}}:
|
||||
extends:
|
||||
- .{{distro.name}}-build@template
|
||||
parallel:
|
||||
matrix:
|
||||
- FEATURE: libei
|
||||
- FEATURE: libeis
|
||||
- FEATURE: liboeffis
|
||||
variables:
|
||||
MESON_ARGS: '-D${FEATURE}=disabled'
|
||||
|
||||
valgrind@{{distro.name}}:{{version}}:
|
||||
extends:
|
||||
|
|
@ -275,24 +288,56 @@ werror@{{distro.name}}:{{version}}:
|
|||
abicheck@{{distro.name}}:{{version}}:
|
||||
extends:
|
||||
- .{{distro.name}}-build@template
|
||||
variables:
|
||||
GIT_STRATEGY: none # We need the upstream repo instead
|
||||
MESON_ARGS: '-Dwerror=true'
|
||||
script:
|
||||
before_script:
|
||||
- git clone --depth=1 https://gitlab.freedesktop.org/hadess/check-abi
|
||||
- |
|
||||
pushd check-abi
|
||||
meson _build
|
||||
meson setup _build
|
||||
meson compile -C _build
|
||||
meson install -C _build
|
||||
popd
|
||||
- git clone --depth=1 https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
|
||||
- cd libei
|
||||
- git fetch --tags
|
||||
- check-abi {{last_abi_break}} HEAD
|
||||
- pip install attrs # required by libei 1.0.0
|
||||
script:
|
||||
- git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
|
||||
- git fetch --tags upstream$CI_JOB_ID
|
||||
- check-abi {{last_abi_break}} $CI_COMMIT_SHA
|
||||
only:
|
||||
- merge_requests
|
||||
|
||||
event-type-check@{{distro.name}}:{{version}}:
|
||||
extends:
|
||||
- .{{distro.name}}-build@template
|
||||
script:
|
||||
- .gitlab-ci/meson-build.sh --skip-test
|
||||
- .gitlab-ci/check-event-values.py
|
||||
variables:
|
||||
PKG_CONFIG_PATH: $MESON_BUILDDIR/meson-uninstalled
|
||||
only:
|
||||
- merge_requests
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% for distro in distributions if distro.use_for_minimal_meson_test %}
|
||||
{% set version = "{}".format(distro.versions|last()) %}
|
||||
.{{distro.name}}-build@template:
|
||||
extends:
|
||||
- .fdo.distribution-image@{{distro.name}}
|
||||
- .build@template
|
||||
variables:
|
||||
MESON_TEST_ARGS: '--no-suite=python'
|
||||
FDO_DISTRIBUTION_VERSION: '{{version}}'
|
||||
FDO_DISTRIBUTION_TAG: ${{distro.name.upper()}}_TAG
|
||||
needs:
|
||||
- "{{distro.name}}:{{version}}@container-prep"
|
||||
|
||||
minimum-meson@{{distro.name}}:{{version}}:
|
||||
extends:
|
||||
- .{{distro.name}}-build@template
|
||||
script:
|
||||
- pip uninstall -y meson
|
||||
- pip install "meson=={{minimum_meson_version}}"
|
||||
- .gitlab-ci/meson-build.sh --run-test
|
||||
|
||||
{% endfor %}
|
||||
|
||||
#################################################################
|
||||
|
|
@ -320,6 +365,30 @@ abicheck@{{distro.name}}:{{version}}:
|
|||
needs:
|
||||
- "{{distro.name}}:{{version}}@container-prep"
|
||||
|
||||
{% if distro.name == pages.distro %}
|
||||
{{distro.name}}:{{version}}@doc-build:
|
||||
stage: distro
|
||||
extends:
|
||||
- .build@template
|
||||
- .fdo.distribution-image@{{distro.name}}
|
||||
variables:
|
||||
FDO_DISTRIBUTION_VERSION: '{{version}}'
|
||||
FDO_DISTRIBUTION_TAG: ${{distro.name.upper()}}_TAG
|
||||
MESON_ARGS: "-Ddocumentation=protocol,api"
|
||||
script:
|
||||
# Staying up-to-date with the breakages between hugo and the relearn theme is annoying
|
||||
# so let's lock the version for our doc build
|
||||
- go install "github.com/gohugoio/hugo@v0.142"
|
||||
- export PATH="$HOME/go/bin:$PATH"
|
||||
- .gitlab-ci/meson-build.sh
|
||||
- rm -rf public/
|
||||
- mv "$MESON_BUILDDIR"/doc/protocol/ei/public/ public/
|
||||
- mv "$MESON_BUILDDIR"/doc/html/ public/api
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
|
|
@ -333,11 +402,11 @@ pages:
|
|||
FDO_DISTRIBUTION_TAG: ${{pages.distro.upper()}}_TAG
|
||||
MESON_ARGS: "-Ddocumentation=protocol,api"
|
||||
script:
|
||||
- .gitlab-ci/meson-build.sh
|
||||
- rm -rf public/
|
||||
- mv "$MESON_BUILDDIR"/doc/protocol/ei/public/ public/
|
||||
- mv "$MESON_BUILDDIR"/doc/html/ public/api
|
||||
|
||||
- echo "Nothing to do"
|
||||
dependencies:
|
||||
- "{{pages.distro}}:{{pages.version}}@doc-build"
|
||||
needs:
|
||||
- "{{pages.distro}}:{{pages.version}}@doc-build"
|
||||
only:
|
||||
refs:
|
||||
- main
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@
|
|||
#
|
||||
|
||||
# We're happy to rebuild all containers when one changes.
|
||||
.default_tag: &default_tag '2023-05-18.3'
|
||||
.default_tag: &default_tag '2025-05-19.1'
|
||||
|
||||
last_abi_break: 0.99.1
|
||||
last_abi_break: abe85e051e7029bfd2e7913ab980a9e0042b6d0d
|
||||
minimum_meson_version: 0.57.0
|
||||
|
||||
distributions:
|
||||
- name: fedora
|
||||
tag: *default_tag
|
||||
versions:
|
||||
- '38' # update the pages job when bumping the version
|
||||
- '43' # update the pages job when bumping the version
|
||||
use_for_custom_build_tests: true
|
||||
packages:
|
||||
- git
|
||||
|
|
@ -23,12 +24,12 @@ distributions:
|
|||
- libxkbcommon-devel
|
||||
- libxml2
|
||||
- doxygen
|
||||
- python3-attrs
|
||||
- python3-pytest
|
||||
- python3-dbusmock
|
||||
- python3-jinja2
|
||||
- python3-pip
|
||||
- hugo # for documentation only
|
||||
- python3-pyyaml
|
||||
- golang # for documentation only
|
||||
- libabigail # for abidiff only
|
||||
pips:
|
||||
- meson
|
||||
|
|
@ -37,6 +38,7 @@ distributions:
|
|||
- strenum
|
||||
- name: debian
|
||||
tag: *default_tag
|
||||
use_for_minimal_meson_test: true
|
||||
versions:
|
||||
- 'bullseye'
|
||||
packages:
|
||||
|
|
@ -48,11 +50,11 @@ distributions:
|
|||
- libxkbcommon-dev
|
||||
- libxml2
|
||||
- doxygen
|
||||
- python3-attr
|
||||
- python3-pytest
|
||||
- python3-dbusmock
|
||||
- python3-jinja2
|
||||
- python3-pip
|
||||
- python3-yaml
|
||||
pips:
|
||||
- meson
|
||||
- ninja
|
||||
|
|
@ -61,4 +63,4 @@ distributions:
|
|||
|
||||
pages:
|
||||
distro: fedora
|
||||
version: 38
|
||||
version: 43
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# This script is sourced from here:
|
||||
# https://gitlab.freedesktop.org/whot/meson-helper
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
set -x
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.3.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- id: check-merge-conflict
|
||||
- id: check-symlinks
|
||||
- id: no-commit-to-branch
|
||||
args: ['--branch', 'main']
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: ['--check', '--diff', '.', 'proto/ei-scanner']
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.254
|
||||
rev: v0.9.4
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ['--ignore=E741,E501', '.', 'proto/ei-scanner']
|
||||
- id: ruff-format
|
||||
args: ['.', 'proto/ei-scanner']
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: ci-fairy-generate-template
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -319,19 +319,19 @@ like `xdotool`. It provides context and device negotiation between the
|
|||
server and the client - the latter must be able to adjust to limitations the
|
||||
server imposes.
|
||||
|
||||
The current implemtation of the protocol does not allow for a `libei` client
|
||||
to send all requests in bulk and exit. The decision on whether to accept a
|
||||
device is ultimately made by the caller implementation and
|
||||
non-deterministic. For **libei** to support a batch request, *someone* would
|
||||
have to wait. It cannot be the server as the exact requirements are unknown: do
|
||||
we pause processing on the client altogether? We may miss a disconnect
|
||||
event? Do we pause processing for one device only? But then we may be
|
||||
re-ordering input events and cause havoc.
|
||||
The current implementation of the protocol does not allow for a `libei` client
|
||||
to connect, send all requests in bulk and exit. The decision on which devices
|
||||
are available is made by the EIS implementation and that requires the `libei`
|
||||
client to wait until such devices are available. On a technical level the
|
||||
protocol is object-oriented and requests cannot be sent until the respective
|
||||
device object is available.
|
||||
|
||||
It could be `libei` itself to implement these event queues but this too can
|
||||
mess with the input order. And implementing an event queue is not hard, so
|
||||
this issue is punted to the caller instead. Xwayland in its current
|
||||
implementation already does this.
|
||||
The duration until devices become available is non-deterministic, and so is
|
||||
what types of devices are available to a client. For **libei** to support a
|
||||
connect-send-exit approach, it would still require the client process to stay
|
||||
alive until device negotiation is complete within libei and all events have
|
||||
been sent. And since the client process must stay alive, we might as well
|
||||
have the device negotiation handled in the caller.
|
||||
|
||||
### uinput vs libei
|
||||
|
||||
|
|
|
|||
|
|
@ -2,28 +2,30 @@ if 'api' not in get_option('documentation')
|
|||
subdir_done()
|
||||
endif
|
||||
|
||||
doxygen = find_program('doxygen', required : false)
|
||||
doxygen = find_program('doxygen', required: false)
|
||||
if not doxygen.found()
|
||||
error('Program "doxygen" not found or not executable. Try building with -Ddocumentation=false')
|
||||
error('Program "doxygen" not found or not executable. Try building with -Ddocumentation=false')
|
||||
endif
|
||||
|
||||
mainpage = vcs_tag(command : ['git', 'log', '-1', '--format=%h'],
|
||||
fallback : 'unknown',
|
||||
input : 'mainpage.dox',
|
||||
output : 'mainpage.dox',
|
||||
replace_string: '__GIT_VERSION__')
|
||||
mainpage = vcs_tag(command: ['git', 'log', '-1', '--format=%h'],
|
||||
fallback: 'unknown',
|
||||
input: 'mainpage.dox',
|
||||
output: 'mainpage.dox',
|
||||
replace_string: '__GIT_VERSION__',
|
||||
)
|
||||
|
||||
src_doxygen = files(
|
||||
# style files
|
||||
'doxygen-awesome.css',
|
||||
# style files
|
||||
'doxygen-awesome.css',
|
||||
) + [libei_headers, libeis_headers, liboeffis_headers]
|
||||
|
||||
doxyfiles = []
|
||||
foreach f : src_doxygen
|
||||
df = configure_file(input: f,
|
||||
output: '@PLAINNAME@',
|
||||
copy : true)
|
||||
doxyfiles += [ df ]
|
||||
foreach f: src_doxygen
|
||||
df = configure_file(input: f,
|
||||
output: '@PLAINNAME@',
|
||||
copy: true,
|
||||
)
|
||||
doxyfiles += [df]
|
||||
endforeach
|
||||
|
||||
doc_config = configuration_data()
|
||||
|
|
@ -31,14 +33,16 @@ doc_config.set('PACKAGE_NAME', meson.project_name())
|
|||
doc_config.set('PACKAGE_VERSION', meson.project_version())
|
||||
doc_config.set('builddir', meson.current_build_dir())
|
||||
|
||||
doxyfile = configure_file(input : 'libei.doxygen.in',
|
||||
output : 'libei.doxygen',
|
||||
configuration : doc_config)
|
||||
doxyfile = configure_file(input: 'libei.doxygen.in',
|
||||
output: 'libei.doxygen',
|
||||
configuration: doc_config,
|
||||
)
|
||||
|
||||
custom_target('doxygen',
|
||||
input : [ doxyfile, mainpage ] + src_doxygen + doxyfiles,
|
||||
output : [ 'doc' ],
|
||||
command : [ doxygen, doxyfile ],
|
||||
install : false,
|
||||
depends: [ mainpage ],
|
||||
build_by_default : true)
|
||||
input: [ doxyfile, mainpage ] + src_doxygen + doxyfiles,
|
||||
output: [ 'doc' ],
|
||||
command: [ doxygen, doxyfile ],
|
||||
install: false,
|
||||
depends: [ mainpage ],
|
||||
build_by_default: true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ server side, typically a Wayland compositor, is called the **"EIS Implementation
|
|||
This documentation details the protocol to communicate between the client side
|
||||
and the EIS implementation.
|
||||
|
||||
A typical Compositor setup using the `libei` and `libeis` [C libraries]({{< relref "libraries" >}}) looks like this:
|
||||
A typical Compositor setup using the `libei` and `libeis` [C libraries]({{% relref "libraries" %}}) looks like this:
|
||||
|
||||
{{< mermaid >}}
|
||||
flowchart LR;
|
||||
|
|
@ -69,7 +69,7 @@ If you are looking for easy-to-use C libraries instead, see:
|
|||
|
||||
- 🥚 [libei](https://libinput.pages.freedesktop.org/libei/api/group__libei.html) for the client side
|
||||
- 🍦 [libeis](https://libinput.pages.freedesktop.org/libei/api/group__libeis.html) for the EIS implementation side
|
||||
- 🚌 [liboeffis](https://libinput.pages.freedesktop.org/libei/api/group__oeffis.html) is an helper library for DBus communication with the
|
||||
- 🚌 [liboeffis](https://libinput.pages.freedesktop.org/libei/api/group__liboeffis.html) is an helper library for DBus communication with the
|
||||
XDG RemoteDesktop portal (`liboeffis`)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ weight: 5
|
|||
In the libei repo, we use the
|
||||
[ei-scanner](https://gitlab.freedesktop.org/libinput/libei/-/blob/main/proto/ei-scanner)
|
||||
to generate bindings for C and Python (for tests) as well the [interface
|
||||
documentation]({{< relref "interfaces/" >}}) in this documentation.
|
||||
documentation]({{% relref "interfaces/" %}}) in this documentation.
|
||||
|
||||
Note: these generated protocol bindings are **not** part of libei's API contract.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ weight: 4
|
|||
---
|
||||
|
||||
The initial connection is a two-step process:
|
||||
An [ei_handshake]({{< relref "interfaces/ei_handshake" >}}) object with the special ID 0 is guaranteed to
|
||||
An [ei_handshake]({{% relref "interfaces/ei_handshake" %}}) object with the special ID 0 is guaranteed to
|
||||
exists. The client must send the appropriate requests to set up
|
||||
its connection, followed by the `ei_handshake.finish` request. The EIS
|
||||
implementation replies by creating the [ei_connection]({{< relref "interfaces/ei_connection" >}}) object with the
|
||||
implementation replies by creating the [ei_connection]({{% relref "interfaces/ei_connection" %}}) object with the
|
||||
client-requested version (or any lower version) that is the connection for the
|
||||
remainder of this client (see [version negotiation]({{< relref "doc/specification#version-negotiation" >}}).
|
||||
remainder of this client (see [version negotiation]({{% relref "doc/specification#version-negotiation" %}}).
|
||||
|
||||
Immediately after connecting, the EIS implementation must send the
|
||||
`ei_handshake.handshake_version` event. The client replies with the
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ flowchart LR;
|
|||
{{< /mermaid >}}
|
||||
|
||||
Objects are identified by a unique object ID, assigned at creation of the object.
|
||||
The type of an object is defined by its [interface]({{< relref "interfaces" >}})
|
||||
The type of an object is defined by its [interface]({{% relref "interfaces" %}})
|
||||
and agreed on at object creation. Each object has exactly one interface, but
|
||||
there may be multiple objects with that interface. For example, a compositor
|
||||
may create multiple objects with the [`ei_device`]({{< relref "interfaces/ei_device" >}})
|
||||
may create multiple objects with the [`ei_device`]({{% relref "interfaces/ei_device" %}})
|
||||
interface.
|
||||
|
||||
All data on the protocol (e.g. object IDs) is private to that client's
|
||||
|
|
@ -57,7 +57,7 @@ content: |object-id |length |opcode |...
|
|||
Where:
|
||||
- `object-id` is one 64-bit unsigned integer that uniquely identifies
|
||||
the object sending the request/event. The `object-id`
|
||||
0 is reserved for the special [`ei_handshake`]({{< relref "interfaces/ei_handshake" >}}) object.
|
||||
0 is reserved for the special [`ei_handshake`]({{% relref "interfaces/ei_handshake" %}}) object.
|
||||
- `length` is a 32-bit integer that specifies the length of the message in
|
||||
bytes, including the 16 header bytes for `object-id`, `length` and `opcode`.
|
||||
- `opcode` is a 32-bit integer that specifies the event or request-specific
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ The current protocol specification is available [in XML format here](https://git
|
|||
|
||||
|
||||
In that protocol specification:
|
||||
- a request or event with the XML attribute `type="destructor"` marks the message as [destructor]({{< relref "#destructors" >}}).
|
||||
- a request or event with the XML attribute `type="destructor"` marks the message as [destructor]({{% relref "#destructors" %}}).
|
||||
- an argument with an XML attribute `enum` carries a value of the corresponding enum
|
||||
- an argument with an XML attribute `interface` attribute indicates that an object in the same message is
|
||||
of that interface type
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ All types are encoded in the EIS implementation's native byte order.
|
|||
| int64 | 64 | signed integer | `int64_t` | |
|
||||
| float | 32 | IEEE-754 float | `float` | |
|
||||
| fd | 0 | file descriptor | `int` | see [^1] |
|
||||
| string | 32 + N| length + string | `int`, `char[]` | see [String Encoding]({{< ref "#string-encoding" >}}) |
|
||||
| new_id | 64 | object id allocated by the caller | `uint64_t` | see [Object IDs]({{< ref "#object-ids" >}}) |
|
||||
| object_id | 64 | previously allocated object id | `uint64_t` | see [Object IDs]({{< ref "#object-ids" >}}) |
|
||||
| string | 32 + N| length + string | `int`, `char[]` | see [String Encoding]({{% ref "#string-encoding" %}}) |
|
||||
| new_id | 64 | object id allocated by the caller | `uint64_t` | see [Object IDs]({{% ref "#object-ids" %}}) |
|
||||
| object_id | 64 | previously allocated object id | `uint64_t` | see [Object IDs]({{% ref "#object-ids" %}}) |
|
||||
|
||||
|
||||
[^1]: zero bytes in the message itself, transmitted in the overhead
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ else
|
|||
git clone --depth=1 https://github.com/McShelby/hugo-theme-relearn "$SITEDIR/themes/hugo-theme-relearn"
|
||||
fi
|
||||
|
||||
cp "$TEMPLATEDIR/config.toml" "$SITEDIR/"
|
||||
cp "$TEMPLATEDIR/hugo.toml" "$SITEDIR/"
|
||||
|
||||
pushd "$TEMPLATEDIR" > /dev/null || exit 1
|
||||
find . -type f -name "*.md"
|
||||
|
|
|
|||
|
|
@ -19,3 +19,6 @@ name = "<i class='fas fa-bug'></i> Report a Bug"
|
|||
identifier = "bugs"
|
||||
url = "https://gitlab.freedesktop.org/libinput/libei/-/issues/"
|
||||
weight = 10
|
||||
|
||||
[params]
|
||||
mermaid = true
|
||||
|
|
@ -2,9 +2,9 @@ if 'protocol' not in get_option('documentation')
|
|||
subdir_done()
|
||||
endif
|
||||
|
||||
hugo = find_program('hugo', required : false)
|
||||
hugo = find_program('hugo', required: false)
|
||||
if not hugo.found()
|
||||
error('Program "hugo" not found or not executable. Try building with -Ddocumentation=false')
|
||||
error('Program "hugo" not found or not executable. Try building with -Ddocumentation=false')
|
||||
endif
|
||||
|
||||
src_script = files('generate-protocol-docs.sh')
|
||||
|
|
@ -12,14 +12,14 @@ src_script = files('generate-protocol-docs.sh')
|
|||
hugo_script = find_program(src_script)
|
||||
|
||||
src_hugo = files(
|
||||
'config.toml',
|
||||
'hugo.toml',
|
||||
'interface.md.tmpl',
|
||||
) + src_script
|
||||
|
||||
custom_target('hugo',
|
||||
input : src_hugo + [protocol_xml],
|
||||
output : [ 'doc' ],
|
||||
command : [ hugo_script, '--git-repo', meson.project_source_root(), '--output-dir', meson.current_build_dir() ],
|
||||
install : false,
|
||||
build_by_default : true,
|
||||
input: src_hugo + [protocol_xml],
|
||||
output: ['doc'],
|
||||
command: [hugo_script, '--git-repo', meson.project_source_root(), '--output-dir', meson.current_build_dir()],
|
||||
install: false,
|
||||
build_by_default: true,
|
||||
)
|
||||
|
|
|
|||
45
meson.build
45
meson.build
|
|
@ -1,8 +1,9 @@
|
|||
project('libei', 'c',
|
||||
version: '0.99.2',
|
||||
license: 'MIT',
|
||||
default_options: [ 'c_std=gnu99', 'warning_level=2' ],
|
||||
meson_version: '>= 0.60.0')
|
||||
version: '1.5.0',
|
||||
license: 'MIT',
|
||||
default_options: [ 'c_std=gnu11', 'warning_level=2' ],
|
||||
meson_version: '>= 0.57.0',
|
||||
)
|
||||
|
||||
libei_version = meson.project_version().split('.')
|
||||
libei_version_major = libei_version[0].to_int()
|
||||
|
|
@ -20,17 +21,13 @@ libei_api_dir = 'libei-@0@'.format(libei_api_version)
|
|||
|
||||
# We use the same soname across all our libraries and they track the project
|
||||
# version. If we have ABI incompatible changes, bump the project major version.
|
||||
if libei_version_major < 1
|
||||
soname = '1.0.0' # Remove after the 1.0 release
|
||||
else
|
||||
soname = meson.project_version()
|
||||
endif
|
||||
soname = meson.project_version()
|
||||
|
||||
pkgconfig = import('pkgconfig')
|
||||
fs = import('fs')
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
cflags =[
|
||||
cflags = [
|
||||
'-Wno-unused-parameter',
|
||||
'-Wmissing-prototypes',
|
||||
'-Wno-missing-field-initializers',
|
||||
|
|
@ -74,7 +71,16 @@ config_h.set('_GNU_SOURCE', '1')
|
|||
config_h.set_quoted('EI_VERSION', meson.project_version())
|
||||
config_h.set_quoted('EIS_VERSION', meson.project_version())
|
||||
if cc.has_function('memfd_create', prefix: '#define _GNU_SOURCE\n#include <sys/mman.h>')
|
||||
config_h.set10('HAVE_MEMFD_CREATE', true)
|
||||
config_h.set10('HAVE_MEMFD_CREATE', true)
|
||||
endif
|
||||
|
||||
code = '''
|
||||
#include <time.h>
|
||||
|
||||
void func() { auto foo = gmtime(NULL); foo->tm_sec = 0; }
|
||||
'''
|
||||
if cc.compiles(code, name: 'has C23 auto keyword')
|
||||
config_h.set('HAVE_C23_AUTO', '1')
|
||||
endif
|
||||
|
||||
dep_math = cc.find_library('m', required: false)
|
||||
|
|
@ -105,6 +111,11 @@ if not get_option('liboeffis').disabled()
|
|||
else
|
||||
dep_sdbus = dependency('', required: false)
|
||||
endif
|
||||
|
||||
build_oeffis = dep_sdbus.found()
|
||||
build_libeis = not get_option('libeis').disabled()
|
||||
build_libei = not get_option('libei').disabled()
|
||||
|
||||
config_h.set10('HAVE_LIBSYSTEMD', dep_sdbus.found() and dep_sdbus.name() == 'libsystemd')
|
||||
config_h.set10('HAVE_LIBELOGIND', dep_sdbus.found() and dep_sdbus.name() == 'libelogind')
|
||||
config_h.set10('HAVE_BASU', dep_sdbus.found() and dep_sdbus.name() == 'basu')
|
||||
|
|
@ -117,18 +128,14 @@ subdir('tools')
|
|||
subdir('test')
|
||||
subdir('doc')
|
||||
|
||||
black = find_program('black', required: false)
|
||||
if black.found()
|
||||
test('python-black', black,
|
||||
args: ['--check', meson.project_source_root(), meson.project_source_root() / 'proto' / 'ei-scanner'],
|
||||
suite: 'python',
|
||||
)
|
||||
endif
|
||||
|
||||
ruff = find_program('ruff', required: false)
|
||||
if ruff.found()
|
||||
test('python-ruff', ruff,
|
||||
args: ['check', '--ignore=E741,E501', meson.project_source_root(), meson.project_source_root() / 'proto' / 'ei-scanner'],
|
||||
suite: 'python',
|
||||
)
|
||||
test('python-ruff-format', ruff,
|
||||
args: ['format', meson.project_source_root(), meson.project_source_root() / 'proto' / 'ei-scanner'],
|
||||
suite: 'python',
|
||||
)
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -2,3 +2,5 @@ option('documentation', type: 'array', value: [], choices: ['api', 'protocol'],
|
|||
option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library')
|
||||
option('tests', type: 'feature', value: 'auto', description: 'Enable/disable tests')
|
||||
option('liboeffis', type: 'feature', value: 'auto', description: 'Build liboeffis.so')
|
||||
option('libeis', type: 'feature', value: 'auto', description: 'Build libeis.so')
|
||||
option('libei', type: 'feature', value: 'auto', description: 'Build libei.so')
|
||||
|
|
|
|||
283
proto/ei-scanner
283
proto/ei-scanner
|
|
@ -17,12 +17,12 @@ Opcodes for events and request are assigned in order as they
|
|||
appear in the XML file.
|
||||
"""
|
||||
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import argparse
|
||||
import attr
|
||||
import jinja2
|
||||
import jinja2.environment
|
||||
import os
|
||||
|
|
@ -55,23 +55,53 @@ def snake2camel(s: str) -> str:
|
|||
return s.replace("_", " ").title().replace(" ", "")
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class Description:
|
||||
summary: str = attr.ib(default="")
|
||||
text: str = attr.ib(default="")
|
||||
summary: str = ""
|
||||
text: str = ""
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class Argument:
|
||||
"""
|
||||
Argument to a request or a reply
|
||||
"""
|
||||
|
||||
name: str = attr.ib()
|
||||
protocol_type: str = attr.ib()
|
||||
summary: str = attr.ib()
|
||||
enum: Optional["Enum"] = attr.ib()
|
||||
interface: Optional["Interface"] = attr.ib()
|
||||
name: str
|
||||
protocol_type: str
|
||||
summary: str
|
||||
enum: Optional["Enum"]
|
||||
interface: Optional["Interface"]
|
||||
interface_arg: Optional["Argument"] = None
|
||||
"""
|
||||
For an argument with "interface_arg", this field points to the argument that
|
||||
contains the interface name.
|
||||
"""
|
||||
interface_arg_for: Optional["Argument"] = None
|
||||
"""
|
||||
For an argument referenced by another argument through "interface_name", this field
|
||||
points to the other argument that references this argument.
|
||||
"""
|
||||
version_arg: Optional["Argument"] = None
|
||||
"""
|
||||
For an argument with type "new_id", this field points to the argument that
|
||||
contains the version for this new object.
|
||||
"""
|
||||
version_arg_for: Optional["Argument"] = None
|
||||
"""
|
||||
For an argument referenced by another argument of type "new_id", this field
|
||||
points to the other argument that references this argument.
|
||||
"""
|
||||
allow_null: bool = False
|
||||
"""
|
||||
For an argument of type string, specify if the argument may be NULL.
|
||||
"""
|
||||
|
||||
def __post_init(self):
|
||||
if self.protocol_type is None or self.protocol_type not in PROTOCOL_TYPES:
|
||||
raise ValueError(f"Failed to parse protocol_type {self.protocol_type}")
|
||||
if self.interface is not None and self.signature not in ["n", "o"]:
|
||||
raise ValueError("Interface may only be set for object types")
|
||||
|
||||
@property
|
||||
def signature(self) -> str:
|
||||
|
|
@ -80,11 +110,6 @@ class Argument:
|
|||
"""
|
||||
return PROTOCOL_TYPES[self.protocol_type]
|
||||
|
||||
@interface.validator # type: ignore
|
||||
def _validate_interface(self, attribute, value):
|
||||
if value is not None and self.signature not in ["n", "o"]:
|
||||
raise ValueError("Interface may only be set for object types")
|
||||
|
||||
@property
|
||||
def as_c_arg(self) -> str:
|
||||
return f"{self.c_type} {self.name}"
|
||||
|
|
@ -103,12 +128,6 @@ class Argument:
|
|||
"new_id": "new_id_t",
|
||||
}[self.protocol_type]
|
||||
|
||||
@protocol_type.validator # type: ignore
|
||||
def _validate_protocol_type(self, attribute, value):
|
||||
assert (
|
||||
value is not None and value in PROTOCOL_TYPES
|
||||
), f"Failed to parse protocol_type {value}"
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
|
|
@ -117,6 +136,7 @@ class Argument:
|
|||
summary: str = "",
|
||||
enum: Optional["Enum"] = None,
|
||||
interface: Optional["Interface"] = None,
|
||||
allow_null: bool = False,
|
||||
) -> "Argument":
|
||||
return cls(
|
||||
name=name,
|
||||
|
|
@ -124,29 +144,29 @@ class Argument:
|
|||
summary=summary,
|
||||
enum=enum,
|
||||
interface=interface,
|
||||
allow_null=allow_null,
|
||||
)
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class Message:
|
||||
"""
|
||||
Parent class for a wire message (Request or Event).
|
||||
"""
|
||||
|
||||
name: str = attr.ib()
|
||||
since: int = attr.ib()
|
||||
opcode: int = attr.ib()
|
||||
interface: "Interface" = attr.ib()
|
||||
description: Optional[Description] = attr.ib(default=None)
|
||||
is_destructor: bool = attr.ib(default=False)
|
||||
context_type: Optional[str] = attr.ib(default=None)
|
||||
name: str
|
||||
since: int
|
||||
opcode: int
|
||||
interface: "Interface"
|
||||
description: Optional[Description] = None
|
||||
is_destructor: bool = False
|
||||
context_type: Optional[str] = None
|
||||
|
||||
arguments: List[Argument] = attr.ib(init=False, factory=list)
|
||||
arguments: List[Argument] = field(init=False, default_factory=list)
|
||||
|
||||
@context_type.validator # type: ignore
|
||||
def _context_type_validate(self, attr, value):
|
||||
if value not in [None, "sender", "receiver"]:
|
||||
raise ValueError(f"Invalid context type {value}")
|
||||
def __post_init(self):
|
||||
if self.context_type not in [None, "sender", "receiver"]:
|
||||
raise ValueError(f"Invalid context type {self.context_type}")
|
||||
|
||||
def add_argument(self, arg: Argument) -> None:
|
||||
if arg.name in [a.name for a in self.arguments]:
|
||||
|
|
@ -165,8 +185,14 @@ class Message:
|
|||
def camel_name(self) -> str:
|
||||
return snake2camel(self.name)
|
||||
|
||||
def find_argument(self, name: str) -> Optional[Argument]:
|
||||
for a in self.arguments:
|
||||
if a.name == name:
|
||||
return a
|
||||
return None
|
||||
|
||||
@attr.s
|
||||
|
||||
@dataclass
|
||||
class Request(Message):
|
||||
@classmethod
|
||||
def create(
|
||||
|
|
@ -193,7 +219,7 @@ class Request(Message):
|
|||
return f"{self.interface.name}_request_{self.name}"
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class Event(Message):
|
||||
@classmethod
|
||||
def create(
|
||||
|
|
@ -220,17 +246,17 @@ class Event(Message):
|
|||
return f"{self.interface.name}_event_{self.name}"
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class Entry:
|
||||
"""
|
||||
An enum entry
|
||||
"""
|
||||
|
||||
name: str = attr.ib()
|
||||
value: int = attr.ib()
|
||||
enum: "Enum" = attr.ib()
|
||||
summary: str = attr.ib()
|
||||
since: int = attr.ib()
|
||||
name: str
|
||||
value: int
|
||||
enum: "Enum"
|
||||
summary: str
|
||||
since: int
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
|
|
@ -246,15 +272,15 @@ class Entry:
|
|||
return f"{self.enum.fqdn}_{self.name}"
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class Enum:
|
||||
name: str = attr.ib()
|
||||
since: int = attr.ib()
|
||||
interface: "Interface" = attr.ib()
|
||||
is_bitfield: bool = attr.ib(default=False)
|
||||
description: Optional[Description] = attr.ib(default=None)
|
||||
name: str
|
||||
since: int
|
||||
interface: "Interface"
|
||||
is_bitfield: bool = False
|
||||
description: Optional[Description] = None
|
||||
|
||||
entries: List[Entry] = attr.ib(init=False, factory=list)
|
||||
entries: List[Entry] = field(init=False, default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
|
|
@ -297,16 +323,20 @@ class Enum:
|
|||
return snake2camel(self.name)
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class Interface:
|
||||
protocol_name: str = attr.ib() # name as in the XML, e.g. ei_pointer
|
||||
version: int = attr.ib()
|
||||
requests: List[Request] = attr.ib(init=False, factory=list)
|
||||
events: List[Event] = attr.ib(init=False, factory=list)
|
||||
enums: List[Enum] = attr.ib(init=False, factory=list)
|
||||
protocol_name: str # name as in the XML, e.g. ei_pointer
|
||||
version: int
|
||||
requests: List[Request] = field(init=False, default_factory=list)
|
||||
events: List[Event] = field(init=False, default_factory=list)
|
||||
enums: List[Enum] = field(init=False, default_factory=list)
|
||||
|
||||
mode: str = attr.ib(validator=attr.validators.in_(["ei", "eis", "brei"]))
|
||||
description: Optional[Description] = attr.ib(default=None)
|
||||
mode: str
|
||||
description: Optional[Description] = None
|
||||
|
||||
def __post_init(self):
|
||||
if self.mode not in ["ei", "eis", "brei"]:
|
||||
raise ValueError(f"Invalid mode {self.mode}")
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
@ -412,11 +442,11 @@ class Interface:
|
|||
return cls(protocol_name=protocol_name, version=version, mode=mode)
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class XmlError(Exception):
|
||||
line: int = attr.ib()
|
||||
column: int = attr.ib()
|
||||
message: str = attr.ib()
|
||||
line: int
|
||||
column: int
|
||||
message: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"line {self.line}:{self.column}: {self.message}"
|
||||
|
|
@ -426,29 +456,34 @@ class XmlError(Exception):
|
|||
return cls(line=location[0], column=location[1], message=message)
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class Copyright:
|
||||
text: str = attr.ib(default="")
|
||||
is_complete: bool = attr.ib(init=False, default=False)
|
||||
text: str = ""
|
||||
is_complete: bool = field(init=False, default=False)
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class Protocol:
|
||||
copyright: Optional[str] = attr.ib(default=None)
|
||||
interfaces: List[Interface] = attr.ib(factory=list)
|
||||
copyright: Optional[str] = None
|
||||
interfaces: List[Interface] = field(default_factory=list)
|
||||
|
||||
|
||||
@attr.s
|
||||
@dataclass
|
||||
class ProtocolParser(xml.sax.handler.ContentHandler):
|
||||
component: str = attr.ib()
|
||||
interfaces: List[Interface] = attr.ib(factory=list)
|
||||
copyright: Optional[Copyright] = attr.ib(init=False, default=None)
|
||||
component: str
|
||||
interfaces: List[Interface] = field(default_factory=list)
|
||||
copyright: Optional[Copyright] = field(init=False, default=None)
|
||||
|
||||
current_interface: Optional[Interface] = attr.ib(init=False, default=None)
|
||||
current_message: Optional[Union[Message, Enum]] = attr.ib(init=False, default=None)
|
||||
current_description: Optional[Description] = attr.ib(init=False, default=None)
|
||||
current_interface: Optional[Interface] = field(init=False, default=None)
|
||||
current_message: Optional[Union[Message, Enum]] = field(init=False, default=None)
|
||||
current_description: Optional[Description] = field(init=False, default=None)
|
||||
# A dict of arg name to interface_arg name mappings
|
||||
current_interface_arg_names: Dict[str, str] = field(
|
||||
init=False, default_factory=dict
|
||||
)
|
||||
current_new_id_arg: Optional[Argument] = field(init=False, default=None)
|
||||
|
||||
_run_counter: int = attr.ib(init=False, default=0, repr=False)
|
||||
_run_counter: int = field(init=False, default=0, repr=False)
|
||||
|
||||
@property
|
||||
def location(self) -> Tuple[int, int]:
|
||||
|
|
@ -485,7 +520,7 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
|
||||
try:
|
||||
name = attrs["name"]
|
||||
version = attrs["version"]
|
||||
version = int(attrs["version"])
|
||||
except KeyError as e:
|
||||
raise XmlError.create(
|
||||
f"Missing attribute {e} in element '{element}'",
|
||||
|
|
@ -524,12 +559,17 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
)
|
||||
try:
|
||||
name = attrs["name"]
|
||||
since = attrs["since"]
|
||||
since = int(attrs["since"])
|
||||
except KeyError as e:
|
||||
raise XmlError.create(
|
||||
f"Missing attribute {e} in element '{element}'",
|
||||
self.location,
|
||||
)
|
||||
if since > self.current_interface.version:
|
||||
raise XmlError.create(
|
||||
f"Invalid 'since' {since} for '{self.current_interface.name}.{name}'",
|
||||
self.location,
|
||||
)
|
||||
|
||||
try:
|
||||
is_bitfield = {
|
||||
|
|
@ -577,12 +617,18 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
|
||||
try:
|
||||
name = attrs["name"]
|
||||
since = attrs["since"]
|
||||
since = int(attrs["since"])
|
||||
except KeyError as e:
|
||||
raise XmlError.create(
|
||||
f"Missing attribute {e} in element '{element}'",
|
||||
self.location,
|
||||
)
|
||||
if since > self.current_interface.version:
|
||||
raise XmlError.create(
|
||||
f"Invalid 'since' {since} for '{self.current_interface.name}.{name}'",
|
||||
self.location,
|
||||
)
|
||||
|
||||
is_destructor = attrs.get("type", "") == "destructor"
|
||||
opcode = len(self.current_interface.requests)
|
||||
request = Request.create(
|
||||
|
|
@ -611,12 +657,17 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
)
|
||||
try:
|
||||
name = attrs["name"]
|
||||
since = attrs["since"]
|
||||
since = int(attrs["since"])
|
||||
except KeyError as e:
|
||||
raise XmlError.create(
|
||||
f"Missing attribute {e} in element '{element}'",
|
||||
self.location,
|
||||
)
|
||||
if since > self.current_interface.version:
|
||||
raise XmlError.create(
|
||||
f"Invalid 'since' {since} for '{self.current_interface.name}.{name}'",
|
||||
self.location,
|
||||
)
|
||||
|
||||
is_destructor = attrs.get("type", "") == "destructor"
|
||||
opcode = len(self.current_interface.events)
|
||||
|
|
@ -658,6 +709,13 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
interface = self.interface_by_name(interface_name)
|
||||
else:
|
||||
interface = None
|
||||
|
||||
# interface_arg is set to the name of some other arg that specifies the actual
|
||||
# interface name for this argument
|
||||
interface_arg_name = attrs.get("interface_arg", None)
|
||||
if interface_arg_name is not None:
|
||||
self.current_interface_arg_names[name] = interface_arg_name
|
||||
|
||||
enum_name = attrs.get("enum", None)
|
||||
enum = None
|
||||
if enum_name is not None:
|
||||
|
|
@ -673,14 +731,24 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
f"Failed to find enum '{intf.name}.{enum_name}'",
|
||||
self.location,
|
||||
)
|
||||
|
||||
allow_null = attrs.get("allow-null", "false") == "true"
|
||||
arg = Argument.create(
|
||||
name=name,
|
||||
protocol_type=proto_type,
|
||||
summary=summary,
|
||||
enum=enum,
|
||||
interface=interface,
|
||||
allow_null=allow_null,
|
||||
)
|
||||
self.current_message.add_argument(arg)
|
||||
if proto_type == "new_id":
|
||||
if self.current_new_id_arg is not None:
|
||||
raise XmlError.create(
|
||||
f"Multiple args of type '{proto_type}' for '{self.current_interface.name}.{self.current_message.name}'",
|
||||
self.location,
|
||||
)
|
||||
self.current_new_id_arg = arg
|
||||
elif element == "entry":
|
||||
if self.current_interface is None:
|
||||
raise XmlError.create(
|
||||
|
|
@ -734,6 +802,40 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
if self._run_counter <= 2:
|
||||
return
|
||||
|
||||
# Populate `interface_arg` and `interface_arg_for`, now we have all arguments
|
||||
if name in ["request", "event"]:
|
||||
assert isinstance(self.current_message, Message)
|
||||
assert isinstance(self.current_interface, Interface)
|
||||
# obj is the argument of type object that the interface applies to
|
||||
# iname is the argument of type "interface_name" that specifies the interface
|
||||
for obj, iname in self.current_interface_arg_names.items():
|
||||
obj_arg = self.current_message.find_argument(obj)
|
||||
iname_arg = self.current_message.find_argument(iname)
|
||||
|
||||
assert obj_arg is not None
|
||||
assert iname_arg is not None
|
||||
|
||||
obj_arg.interface_arg = iname_arg
|
||||
iname_arg.interface_arg_for = obj_arg
|
||||
self.current_interface_arg_names = {}
|
||||
|
||||
if self.current_new_id_arg is not None:
|
||||
arg = self.current_new_id_arg
|
||||
version_arg = self.current_message.find_argument("version")
|
||||
if version_arg is None:
|
||||
# Sigh, protocol bug: ei_connection.sync one doesn't have a version arg
|
||||
if (
|
||||
f"{self.current_interface.plainname}.{self.current_message.name}"
|
||||
!= "connection.sync"
|
||||
):
|
||||
raise XmlError.create(
|
||||
f"Unable to find a version argument for {self.current_interface.plainname}.{self.current_message.name}::{arg.name}",
|
||||
self.location,
|
||||
)
|
||||
else:
|
||||
arg.version_arg = version_arg
|
||||
version_arg.version_arg_for = arg
|
||||
self.current_new_id_arg = None
|
||||
if name == "request":
|
||||
assert isinstance(self.current_message, Request)
|
||||
self.current_message = None
|
||||
|
|
@ -789,6 +891,7 @@ def generate_source(
|
|||
data["interfaces"] = proto.interfaces
|
||||
data["extra"] = extra_data
|
||||
|
||||
loader: jinja2.BaseLoader
|
||||
if template == "-":
|
||||
loader = jinja2.FunctionLoader(lambda _: sys.stdin.read())
|
||||
filename = "<stdin>"
|
||||
|
|
@ -819,9 +922,7 @@ def generate_source(
|
|||
|
||||
import re
|
||||
|
||||
return re.sub(
|
||||
rf"({component}[_-]\w*)(\.[.\w]*)?", rf"{quotes}\1\2{quotes}", str
|
||||
)
|
||||
return re.sub(rf"({component}[_-]\w*)((\.\w+)*)", rf"{quotes}\1\2{quotes}", str)
|
||||
|
||||
env.filters["c_type"] = filter_c_type
|
||||
env.filters["as_c_arg"] = filter_as_c_arg
|
||||
|
|
@ -831,7 +932,7 @@ def generate_source(
|
|||
return jtemplate.stream(data)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def scanner(argv: list[str]) -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=dedent(
|
||||
"""
|
||||
|
|
@ -893,7 +994,7 @@ def main() -> None:
|
|||
"template", type=str, help="The Jinja2 compatible template file"
|
||||
)
|
||||
|
||||
ns = parser.parse_args()
|
||||
ns = parser.parse_args(argv)
|
||||
assert ns.protocol.exists()
|
||||
|
||||
try:
|
||||
|
|
@ -913,17 +1014,17 @@ def main() -> None:
|
|||
|
||||
extra_data = json.loads(ns.jinja_extra_data)
|
||||
elif ns.jinja_extra_data_file is not None:
|
||||
if ns.jinja_extra_data.name.endswith(
|
||||
if ns.jinja_extra_data_file.name.endswith(
|
||||
".yml"
|
||||
) or ns.jinja_extra_data.name.endswith(".yaml"):
|
||||
) or ns.jinja_extra_data_file.name.endswith(".yaml"):
|
||||
import yaml
|
||||
|
||||
with open(ns.jinja_extra_data) as fd:
|
||||
with open(ns.jinja_extra_data_file) as fd:
|
||||
extra_data = yaml.safe_load(fd)
|
||||
elif ns.jinja_extra_data.name.endswith(".json"):
|
||||
elif ns.jinja_extra_data_file.name.endswith(".json"):
|
||||
import json
|
||||
|
||||
with open(ns.jinja_extra_data) as fd:
|
||||
with open(ns.jinja_extra_data_file) as fd:
|
||||
extra_data = json.load(fd)
|
||||
else:
|
||||
print("Unknown file format for jinja data", file=sys.stderr)
|
||||
|
|
@ -940,4 +1041,4 @@ def main() -> None:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
scanner(sys.argv[1:])
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ protocol_dtd = files(protocol_dtd_path)
|
|||
xmllint = find_program('xmllint', required: false)
|
||||
if xmllint.found()
|
||||
test('dtdcheck', xmllint,
|
||||
args: ['--dtdvalid', protocol_dtd, protocol_xml]
|
||||
)
|
||||
args: ['--dtdvalid', protocol_dtd, protocol_xml],
|
||||
)
|
||||
endif
|
||||
|
||||
pymod = import('python')
|
||||
required_python_modules = ['attr', 'jinja2']
|
||||
pymod.find_installation('python3', modules: required_python_modules)
|
||||
required_python_modules = ['jinja2']
|
||||
python = pymod.find_installation('python3', modules: required_python_modules)
|
||||
if python.language_version().version_compare('<3.9')
|
||||
error('Python 3.9 or later required')
|
||||
endif
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
2
ruff.toml
Normal file
2
ruff.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Same as Black.
|
||||
line-length = 88
|
||||
|
|
@ -49,12 +49,6 @@ struct brei_header {
|
|||
} _packed_;
|
||||
static_assert(sizeof(struct brei_header) == 16, "Unexpected size for brei_header struct");
|
||||
|
||||
struct brei_string {
|
||||
uint32_t len;
|
||||
const char str[];
|
||||
};
|
||||
static_assert(sizeof(struct brei_string) == 4, "Unexpected size for brei_string struct");
|
||||
|
||||
/**
|
||||
* For a given string length (including null byte) return
|
||||
* the number of bytes needed on the protocol, including the
|
||||
|
|
@ -63,12 +57,13 @@ static_assert(sizeof(struct brei_string) == 4, "Unexpected size for brei_string
|
|||
static inline uint32_t
|
||||
brei_string_proto_length(uint32_t slen)
|
||||
{
|
||||
uint32_t length = sizeof(struct brei_string) + slen;
|
||||
uint32_t length = 4 + slen;
|
||||
uint32_t protolen = (length + 3)/4 * 4;
|
||||
assert(protolen % 4 == 0);
|
||||
return protolen;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
brei_context_destroy(struct brei_context *ctx)
|
||||
{
|
||||
|
|
@ -183,7 +178,7 @@ brei_log_msg(struct brei_context *brei,
|
|||
|
||||
static struct brei_result *
|
||||
brei_demarshal(struct brei_context *brei, struct iobuf *buf, const char *signature,
|
||||
size_t *nargs_out, union brei_arg **args_out)
|
||||
size_t *nargs_out, union brei_arg **args_out, char ***strings_out)
|
||||
{
|
||||
size_t nargs = strlen(signature);
|
||||
if (nargs > 256) {
|
||||
|
|
@ -193,11 +188,15 @@ brei_demarshal(struct brei_context *brei, struct iobuf *buf, const char *signatu
|
|||
|
||||
/* This over-allocates if we have more than one char per type but meh */
|
||||
_cleanup_free_ union brei_arg *args = xalloc(nargs * sizeof(*args));
|
||||
/* This over-allocates since not all args are strings but meh.
|
||||
Needs to be NULL-terminated for strv_freep to work */
|
||||
_cleanup_(strv_freep) char **strings = xalloc((nargs + 1) * sizeof(*strings));
|
||||
|
||||
const char *s = signature;
|
||||
union brei_arg *arg = args;
|
||||
uint32_t *p = (uint32_t*)iobuf_data(buf);
|
||||
uint32_t *end = (uint32_t*)iobuf_data_end(buf);
|
||||
size_t nstrings = 0;
|
||||
|
||||
nargs = 0;
|
||||
while (*s) {
|
||||
|
|
@ -223,24 +222,29 @@ brei_demarshal(struct brei_context *brei, struct iobuf *buf, const char *signatu
|
|||
arg->h = iobuf_take_fd(buf);
|
||||
break;
|
||||
case 's': {
|
||||
struct brei_string *s = (struct brei_string *)p;
|
||||
|
||||
uint32_t slen = *p;
|
||||
uint32_t remaining = end - p;
|
||||
uint32_t protolen = brei_string_proto_length(s->len); /* in bytes */
|
||||
uint32_t protolen = brei_string_proto_length(slen); /* in bytes */
|
||||
uint32_t len32 = protolen/4; /* p and end are uint32_t* */
|
||||
|
||||
if (remaining < len32) {
|
||||
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Invalid string length %u, only %u bytes remaining", s->len, remaining * 4);
|
||||
"Invalid string length %u, only %u bytes remaining", slen, remaining * 4);
|
||||
}
|
||||
|
||||
if (s->len == 0) {
|
||||
|
||||
if (slen == 0) {
|
||||
arg->s = NULL;
|
||||
} else if (s->str[s->len - 1] != '\0') {
|
||||
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Message string not zero-terminated");
|
||||
} else {
|
||||
arg->s = s->str;
|
||||
_cleanup_free_ char *str = xalloc(slen);
|
||||
memcpy(str, p + 1, slen);
|
||||
if (str[slen - 1] != '\0') {
|
||||
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Message string not zero-terminated");
|
||||
}
|
||||
strings[nstrings] = steal(&str);
|
||||
arg->s = strings[nstrings];
|
||||
nstrings++;
|
||||
}
|
||||
p += len32;
|
||||
break;
|
||||
|
|
@ -255,6 +259,7 @@ brei_demarshal(struct brei_context *brei, struct iobuf *buf, const char *signatu
|
|||
}
|
||||
|
||||
*args_out = steal(&args);
|
||||
*strings_out = steal(&strings);
|
||||
*nargs_out = nargs;
|
||||
|
||||
return NULL;
|
||||
|
|
@ -275,25 +280,25 @@ brei_marshal(struct brei_context *brei, struct iobuf *buf, const char *signature
|
|||
switch (*s) {
|
||||
case 'i':
|
||||
i = va_arg(args, int32_t);
|
||||
iobuf_append(buf, (const char*)(&i), 4);
|
||||
iobuf_append_u32(buf, i);
|
||||
break;
|
||||
case 'u':
|
||||
u = va_arg(args, uint32_t);
|
||||
iobuf_append(buf, (const char*)(&u), 4);
|
||||
iobuf_append_u32(buf, u);
|
||||
break;
|
||||
case 'x':
|
||||
x = va_arg(args, int64_t);
|
||||
iobuf_append(buf, (const char*)(&x), 8);
|
||||
iobuf_append_u64(buf, x);
|
||||
break;
|
||||
case 'o':
|
||||
case 'n':
|
||||
case 't':
|
||||
t = va_arg(args, uint64_t);
|
||||
iobuf_append(buf, (const char*)(&t), 8);
|
||||
iobuf_append_u64(buf, t);
|
||||
break;
|
||||
case 'f':
|
||||
f = va_arg(args, double);
|
||||
iobuf_append(buf, (const char*)(&f), 4);
|
||||
iobuf_append_f32(buf, f);
|
||||
break;
|
||||
case 'h':
|
||||
fd = va_arg(args, int);
|
||||
|
|
@ -304,8 +309,8 @@ brei_marshal(struct brei_context *brei, struct iobuf *buf, const char *signature
|
|||
const char *str = va_arg(args, const char*);
|
||||
|
||||
/* FIXME: nullable strings */
|
||||
size_t slen = str ? strlen(str) + 1 : 0;
|
||||
iobuf_append(buf, (const char*)&slen, 4);
|
||||
uint32_t slen = str ? strlen(str) + 1 : 0;
|
||||
iobuf_append_u32(buf, slen);
|
||||
if (slen > 0) {
|
||||
iobuf_append(buf, str, slen);
|
||||
if (slen % 4)
|
||||
|
|
@ -340,7 +345,7 @@ brei_marshal_message(struct brei_context *brei,
|
|||
size_t message_len = iobuf_len(buf) + sizeof(struct brei_header);
|
||||
uint32_t header[4] = {0, 0, message_len, opcode};
|
||||
memcpy(header, &id, sizeof(id));
|
||||
iobuf_prepend(buf, (const char *)header, sizeof(header));
|
||||
iobuf_prepend(buf, header, sizeof(header));
|
||||
|
||||
return brei_result_new_success(steal(&buf));
|
||||
}
|
||||
|
|
@ -412,14 +417,15 @@ brei_dispatch(struct brei_context *brei,
|
|||
iobuf_pop(buf, headersize);
|
||||
|
||||
/* Demarshal the protocol into a set of arguments */
|
||||
_cleanup_free_ union brei_arg * args = NULL;
|
||||
_cleanup_free_ union brei_arg *args = NULL;
|
||||
_cleanup_(strv_freep) char **strings = NULL;
|
||||
const char *signature = interface->incoming[opcode].signature;
|
||||
size_t nargs = 0;
|
||||
result = brei_demarshal(brei, buf, signature, &nargs, &args);
|
||||
result = brei_demarshal(brei, buf, signature, &nargs, &args, &strings);
|
||||
if (result)
|
||||
goto error;
|
||||
|
||||
log_debug(brei, "dispatching %s.%s() on object %#" PRIx64 "", interface->name, interface->incoming[opcode].name, object_id);
|
||||
log_debug(brei, "object %#" PRIx64 " dispatching %s.%s()", object_id, interface->name, interface->incoming[opcode].name);
|
||||
|
||||
/* Success! Let's pass this on to the
|
||||
* context to process */
|
||||
|
|
@ -491,8 +497,9 @@ MUNIT_TEST(test_brei_marshal)
|
|||
}
|
||||
|
||||
_cleanup_free_ union brei_arg *args = NULL;
|
||||
_cleanup_(strv_freep) char **strings = NULL;
|
||||
size_t nargs = 0;
|
||||
_unref_(brei_result) *result = brei_demarshal(brei, buf, "noiusf", &nargs, &args);
|
||||
_unref_(brei_result) *result = brei_demarshal(brei, buf, "noiusf", &nargs, &args, &strings);
|
||||
munit_assert_ptr_null(result);
|
||||
munit_assert_int(nargs, ==, 6);
|
||||
|
||||
|
|
@ -503,6 +510,10 @@ MUNIT_TEST(test_brei_marshal)
|
|||
munit_assert_string_equal(args[4].s, str);
|
||||
munit_assert_double_equal(args[5].f, 1.45, 3 /* precision */);
|
||||
|
||||
/* make sure strings is filled in as expected and null-terminated */
|
||||
munit_assert_ptr_equal(args[4].s, strings[0]);
|
||||
munit_assert_ptr_null(strings[1]);
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
|
|
@ -521,8 +532,9 @@ MUNIT_TEST(test_brei_marshal_bad_sig)
|
|||
}
|
||||
|
||||
_cleanup_free_ union brei_arg *args = NULL;
|
||||
_cleanup_(strv_freep) char **strings = NULL;
|
||||
size_t nargs = 789;
|
||||
_unref_(brei_result) *result = brei_demarshal(brei, buf, "nxoiusf", &nargs, &args);
|
||||
_unref_(brei_result) *result = brei_demarshal(brei, buf, "nxoiusf", &nargs, &args, &strings);
|
||||
munit_assert_ptr_not_null(result);
|
||||
munit_assert_int(brei_result_get_reason(result), ==,
|
||||
BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL);
|
||||
|
|
@ -571,14 +583,28 @@ bytes_to_int32(uint32_t count)
|
|||
return (uint32_t)(((uint64_t)count + 3)/4);
|
||||
}
|
||||
|
||||
static inline void
|
||||
buffer_debug(const void *buffer, size_t sz)
|
||||
{
|
||||
const size_t stride = 8;
|
||||
_cleanup_(strv_freep) char **strv = strv_from_mem(buffer, sz, stride);
|
||||
|
||||
munit_logf(MUNIT_LOG_DEBUG, "Logging buffer size %zu", sz);
|
||||
for (size_t offset = 0; offset < sz; offset += stride) {
|
||||
const char *s = strv[offset/stride];
|
||||
munit_assert_ptr_not_null(s);
|
||||
munit_logf(MUNIT_LOG_DEBUG, "%02zu: %s", offset, s);
|
||||
}
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_brei_send_message)
|
||||
{
|
||||
int sv[2];
|
||||
int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, sv);
|
||||
munit_assert_int(rc, ==, 0);
|
||||
|
||||
int sock_read = sv[0];
|
||||
int sock_write = sv[1];
|
||||
_cleanup_close_ int sock_read = sv[0];
|
||||
_cleanup_close_ int sock_write = sv[1];
|
||||
|
||||
const int header_size = 16;
|
||||
|
||||
|
|
@ -594,6 +620,8 @@ MUNIT_TEST(test_brei_send_message)
|
|||
int len = read(sock_read, buf, sizeof(buf));
|
||||
munit_assert_int(len, ==, msglen);
|
||||
|
||||
buffer_debug(buf, len);
|
||||
|
||||
const struct brei_header *header = (const struct brei_header*)buf;
|
||||
munit_assert_int(header->sender_id, ==, id);
|
||||
munit_assert_int(header->msglen, ==, msglen);
|
||||
|
|
@ -617,6 +645,8 @@ MUNIT_TEST(test_brei_send_message)
|
|||
} ufloat;
|
||||
munit_assert_int(len, ==, msglen);
|
||||
|
||||
buffer_debug(buf, len);
|
||||
|
||||
const struct brei_header *header = (const struct brei_header*)buf;
|
||||
munit_assert_int(header->sender_id, ==, id);
|
||||
munit_assert_int(header->msglen, ==, msglen);
|
||||
|
|
@ -629,10 +659,10 @@ MUNIT_TEST(test_brei_send_message)
|
|||
|
||||
{
|
||||
const char string[12] = "hello wor"; /* tests padding too */
|
||||
int slen = bytes_to_int32(strlen0(string)) * 4;
|
||||
munit_assert_int(slen, ==, sizeof(string));
|
||||
uint32_t string_len = bytes_to_int32(strlen0(string)) * 4;
|
||||
munit_assert_int(string_len, ==, sizeof(string));
|
||||
|
||||
const int msglen = header_size + 24 + slen; /* 4 bytes + 2 * 8 bytes + string length */
|
||||
const int msglen = header_size + 24 + string_len; /* 4 bytes + 2 * 8 bytes + string length */
|
||||
object_id_t id = 2;
|
||||
uint32_t opcode = 3;
|
||||
const char *signature = "ison";
|
||||
|
|
@ -645,20 +675,28 @@ MUNIT_TEST(test_brei_send_message)
|
|||
int len = read(sock_read, buf, sizeof(buf));
|
||||
munit_assert_int(len, ==, msglen);
|
||||
|
||||
buffer_debug(buf, len);
|
||||
|
||||
const struct brei_header *header = (const struct brei_header*)buf;
|
||||
munit_assert_int(header->sender_id, ==, id);
|
||||
munit_assert_uint64(header->sender_id, ==, id);
|
||||
munit_assert_int(header->msglen, ==, msglen);
|
||||
munit_assert_int(header->opcode, ==, opcode);
|
||||
|
||||
munit_assert_int(buf[4], ==, -42);
|
||||
|
||||
const struct brei_string *s = (const struct brei_string *)&buf[5];
|
||||
munit_assert_int(s->len, ==, strlen0(string));
|
||||
munit_assert_string_equal(s->str, string);
|
||||
munit_assert_int(memcmp(s->str, string, brei_string_proto_length(s->len) - 4), ==, 0);
|
||||
uint32_t slen = buf[5];
|
||||
munit_assert_int(slen, ==, strlen0(string));
|
||||
char protostring[sizeof(string)] = {0};
|
||||
assert(brei_string_proto_length(slen) - 4 == sizeof(protostring));
|
||||
memcpy(protostring, &buf[6], brei_string_proto_length(slen) - 4);
|
||||
munit_assert_string_equal(protostring, string);
|
||||
munit_assert_int(memcmp(protostring, string, brei_string_proto_length(slen) - 4), ==, 0);
|
||||
|
||||
munit_assert_int(buf[6 + slen/4], ==, 0xab);
|
||||
munit_assert_int(buf[8 + slen/4], ==, 0xcdef);
|
||||
object_id_t a, b;
|
||||
memcpy(&a, &buf[6 + string_len/4], sizeof(a));
|
||||
memcpy(&b, &buf[8 + string_len/4], sizeof(b));
|
||||
munit_assert_uint64(a, ==, 0xab);
|
||||
munit_assert_uint64(b, ==, 0xcdef);
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -676,20 +714,28 @@ MUNIT_TEST(test_brei_send_message)
|
|||
int len = read(sock_read, buf, sizeof(buf));
|
||||
munit_assert_int(len, ==, msglen);
|
||||
|
||||
buffer_debug(buf, len);
|
||||
|
||||
const struct brei_header *header = (const struct brei_header*)buf;
|
||||
munit_assert_int(header->sender_id, ==, id);
|
||||
munit_assert_uint64(header->sender_id, ==, id);
|
||||
munit_assert_int(header->msglen, ==, msglen);
|
||||
munit_assert_int(header->opcode, ==, opcode);
|
||||
|
||||
const struct brei_string *s1 = (const struct brei_string*)&buf[4];
|
||||
munit_assert_int(s1->len, ==, strlen0(string1));
|
||||
munit_assert_string_equal(s1->str, string1);
|
||||
munit_assert_int(memcmp(s1->str, string1, brei_string_proto_length(s1->len) - 4), ==, 0);
|
||||
uint32_t s1len = buf[4];
|
||||
munit_assert_int(s1len, ==, strlen0(string1));
|
||||
char protostring1[sizeof(string1)] = {0};
|
||||
assert(brei_string_proto_length(s1len) - 4 == sizeof(protostring1));
|
||||
memcpy(protostring1, &buf[5], brei_string_proto_length(s1len) - 4);
|
||||
munit_assert_string_equal(protostring1, string1);
|
||||
munit_assert_int(memcmp(protostring1, string1, brei_string_proto_length(s1len) - 4), ==, 0);
|
||||
|
||||
const struct brei_string *s2 = (const struct brei_string *)&buf[8];
|
||||
munit_assert_int(s2->len, ==, strlen0(string2));
|
||||
munit_assert_string_equal(s2->str, string2);
|
||||
munit_assert_int(memcmp(s2->str, string2, brei_string_proto_length(s2->len) - 4), ==, 0);
|
||||
uint32_t s2len = buf[8];
|
||||
munit_assert_int(s2len, ==, strlen0(string2));
|
||||
char protostring2[sizeof(string2)] = {0};
|
||||
assert(brei_string_proto_length(s2len) - 4 == sizeof(protostring2));
|
||||
memcpy(protostring2, &buf[9], brei_string_proto_length(s2len) - 4);
|
||||
munit_assert_string_equal(protostring2, string2);
|
||||
munit_assert_int(memcmp(protostring2, string2, brei_string_proto_length(s2len) - 4), ==, 0);
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -720,7 +766,7 @@ MUNIT_TEST(test_brei_send_message)
|
|||
uint32_t *buf = (uint32_t*)iobuf_data(recv);
|
||||
munit_assert_int(len, ==, msglen);
|
||||
const struct brei_header *header = (const struct brei_header*)buf;
|
||||
munit_assert_int(header->sender_id, ==, id);
|
||||
munit_assert_uint64(header->sender_id, ==, id);
|
||||
munit_assert_int(header->msglen, ==, msglen);
|
||||
munit_assert_int(header->opcode, ==, opcode);
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ struct brei_object {
|
|||
|
||||
/**
|
||||
* Marshal the message for the given object id, opcode and signature into
|
||||
* a struct iobuf. On succes, the returned result has that iobuf
|
||||
* a struct iobuf. On success, the returned result has that iobuf
|
||||
* as brei_result_get_data(), otherwise the result is the error.
|
||||
*/
|
||||
struct brei_result *
|
||||
|
|
|
|||
|
|
@ -121,14 +121,14 @@ static struct brei_result *
|
|||
case {{incoming.fqdn.upper()}}:
|
||||
if (obj->version < {{incoming.fqdn.upper()}}_SINCE_VERSION)
|
||||
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_ERROR,
|
||||
"Opcode %u not supported in this interface version\n", opcode);
|
||||
"Opcode %u not supported in this interface version", opcode);
|
||||
assert(interface->{{incoming.name}} != NULL);
|
||||
return interface->{{incoming.name}}({{interface.name}}{% for arg in incoming.arguments %}, (args + {{loop.index - 1}})->{{arg.signature}}{% endfor %});
|
||||
{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_ERROR,
|
||||
"Opcode %u not supported in this interface version\n", opcode);
|
||||
"Opcode %u not supported in this interface version", opcode);
|
||||
}
|
||||
|
||||
static const struct brei_message {{interface.name}}_requests[] = {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 button WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 button WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 callback WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
@ -68,6 +68,12 @@ ei_callback_get_id(struct ei_callback *callback)
|
|||
return callback->proto_object.id;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ei_callback_get_version(struct ei_callback *callback)
|
||||
{
|
||||
return callback->proto_object.version;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
handle_msg_done(struct ei_callback *callback, uint64_t callback_data)
|
||||
{
|
||||
|
|
@ -101,18 +107,3 @@ ei_callback_new(struct ei *ei, ei_callback_func func, void *callback_data)
|
|||
|
||||
return callback; /* ref owned by caller */
|
||||
}
|
||||
|
||||
struct ei_callback *
|
||||
ei_callback_new_for_id(struct ei *ei, object_id_t id, uint32_t version)
|
||||
{
|
||||
struct ei_callback *callback = ei_callback_create(&ei->object);
|
||||
|
||||
callback->proto_object.id = id;
|
||||
callback->proto_object.implementation = callback;
|
||||
callback->proto_object.interface = &ei_callback_proto_interface;
|
||||
callback->proto_object.version = version; /* FIXME */
|
||||
ei_register_object(ei, &callback->proto_object);
|
||||
list_init(&callback->link);
|
||||
|
||||
return callback; /* ref owned by caller */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
* 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 callback WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
@ -51,6 +51,7 @@ struct ei_callback {
|
|||
OBJECT_DECLARE_GETTER(ei_callback, context, struct ei*);
|
||||
OBJECT_DECLARE_GETTER(ei_callback, proto_object, const struct brei_object *);
|
||||
OBJECT_DECLARE_GETTER(ei_callback, id, object_id_t);
|
||||
OBJECT_DECLARE_GETTER(ei_callback, version, uint32_t);
|
||||
OBJECT_DECLARE_GETTER(ei_callback, interface, const struct ei_callback_interface *);
|
||||
OBJECT_DECLARE_GETTER(ei_callback, user_data, void *);
|
||||
OBJECT_DECLARE_SETTER(ei_callback, user_data, void *);
|
||||
|
|
@ -59,6 +60,3 @@ OBJECT_DECLARE_UNREF(ei_callback);
|
|||
|
||||
struct ei_callback *
|
||||
ei_callback_new(struct ei *ei, ei_callback_func func, void *callback_data);
|
||||
|
||||
struct ei_callback *
|
||||
ei_callback_new_for_id(struct ei *ei, object_id_t id, uint32_t version);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "util-version.h"
|
||||
|
||||
#include "libei-private.h"
|
||||
#include "libei-connection.h"
|
||||
#include "ei-proto.h"
|
||||
|
||||
static void
|
||||
|
|
@ -43,12 +44,8 @@ ei_connection_destroy(struct ei_connection *connection)
|
|||
struct ei *ei = ei_connection_get_context(connection);
|
||||
ei_unregister_object(ei, &connection->proto_object);
|
||||
|
||||
struct ei_callback *cb;
|
||||
list_for_each_safe(cb, &connection->pending_callbacks, link) {
|
||||
list_remove(&cb->link);
|
||||
free(ei_callback_get_user_data(cb));
|
||||
ei_callback_unref(cb);
|
||||
}
|
||||
/* should be a noop */
|
||||
ei_connection_remove_pending_callbacks(connection);
|
||||
}
|
||||
|
||||
OBJECT_IMPLEMENT_REF(ei_connection);
|
||||
|
|
@ -101,19 +98,57 @@ ei_connection_new(struct ei *ei, object_id_t id, uint32_t version)
|
|||
return connection; /* ref owned by caller */
|
||||
}
|
||||
|
||||
struct callback_user_data {
|
||||
ei_connection_sync_callback_t cb;
|
||||
void *user_data;
|
||||
};
|
||||
void
|
||||
ei_connection_remove_pending_callbacks(struct ei_connection *connection)
|
||||
{
|
||||
struct ei_callback *cb;
|
||||
list_for_each_safe(cb, &connection->pending_callbacks, link) {
|
||||
list_remove(&cb->link);
|
||||
ei_connection_sync_callback_unref(cb->user_data);
|
||||
ei_callback_unref(cb);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ei_connection_sync_callback_destroy(struct ei_connection_sync_callback *callback)
|
||||
{
|
||||
if (callback->destroy)
|
||||
callback->destroy(callback);
|
||||
}
|
||||
|
||||
static
|
||||
OBJECT_IMPLEMENT_CREATE(ei_connection_sync_callback);
|
||||
OBJECT_IMPLEMENT_REF(ei_connection_sync_callback);
|
||||
OBJECT_IMPLEMENT_UNREF(ei_connection_sync_callback);
|
||||
OBJECT_IMPLEMENT_GETTER(ei_connection_sync_callback, user_data, void *);
|
||||
static
|
||||
OBJECT_IMPLEMENT_PARENT(ei_connection_sync_callback, ei);
|
||||
|
||||
struct ei *
|
||||
ei_connection_sync_callback_get_context(struct ei_connection_sync_callback *callback)
|
||||
{
|
||||
return ei_connection_sync_callback_parent(callback);
|
||||
}
|
||||
|
||||
struct ei_connection_sync_callback *
|
||||
ei_connection_sync_callback_new(struct ei *ei,
|
||||
ei_connection_sync_callback_done_t done,
|
||||
ei_connection_sync_callback_destroy_t destroy,
|
||||
void *user_data)
|
||||
{
|
||||
struct ei_connection_sync_callback *callback = ei_connection_sync_callback_create(&ei->object);
|
||||
callback->done = done;
|
||||
callback->destroy = destroy;
|
||||
callback->user_data = user_data;
|
||||
return callback;
|
||||
}
|
||||
|
||||
static void
|
||||
sync_callback(struct ei_callback *callback, void *callback_data, uint64_t proto_data)
|
||||
{
|
||||
struct ei_connection *connection = callback_data;
|
||||
|
||||
_cleanup_free_ struct callback_user_data *data = ei_callback_get_user_data(callback);
|
||||
if (data->cb)
|
||||
data->cb(connection, data->user_data);
|
||||
_unref_(ei_connection_sync_callback) *data = ei_callback_get_user_data(callback);
|
||||
if (data->done)
|
||||
data->done(data);
|
||||
|
||||
/* remove from pending callbacks */
|
||||
list_remove(&callback->link);
|
||||
|
|
@ -121,7 +156,8 @@ sync_callback(struct ei_callback *callback, void *callback_data, uint64_t proto_
|
|||
}
|
||||
|
||||
void
|
||||
ei_connection_sync(struct ei_connection *connection, ei_connection_sync_callback_t cb, void *user_data)
|
||||
ei_connection_sync(struct ei_connection *connection,
|
||||
struct ei_connection_sync_callback *callback)
|
||||
{
|
||||
struct ei *ei = ei_connection_get_context(connection);
|
||||
|
||||
|
|
@ -130,12 +166,9 @@ ei_connection_sync(struct ei_connection *connection, ei_connection_sync_callback
|
|||
* then we extract the user_data on the object and call into the
|
||||
* cb supplied to this function.
|
||||
*/
|
||||
struct ei_callback *callback = ei_callback_new(ei, sync_callback, connection);
|
||||
struct callback_user_data *data = xalloc(sizeof *data);
|
||||
data->cb = cb;
|
||||
data->user_data = user_data;
|
||||
ei_callback_set_user_data(callback, data);
|
||||
list_append(&connection->pending_callbacks, &callback->link);
|
||||
struct ei_callback *cb = ei_callback_new(ei, sync_callback, connection);
|
||||
ei_callback_set_user_data(cb, ei_connection_sync_callback_ref(callback));
|
||||
list_append(&connection->pending_callbacks, &cb->link);
|
||||
|
||||
ei_connection_request_sync(connection, ei_callback_get_id(callback));
|
||||
ei_connection_request_sync(connection, ei_callback_get_id(cb), ei_callback_get_version(cb));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,18 +25,21 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "util-mem.h"
|
||||
#include "util-object.h"
|
||||
#include "util-list.h"
|
||||
#include "brei-shared.h"
|
||||
|
||||
struct ei;
|
||||
struct ei_connection;
|
||||
struct ei_connection_sync_callback;
|
||||
|
||||
/* This is a protocol-only object, not exposed in the API */
|
||||
struct ei_connection {
|
||||
struct object object;
|
||||
struct brei_object proto_object;
|
||||
|
||||
/* struct ei_connection_sync_callback */
|
||||
struct list pending_callbacks;
|
||||
};
|
||||
|
||||
|
|
@ -51,14 +54,41 @@ OBJECT_DECLARE_UNREF(ei_connection);
|
|||
struct ei_connection *
|
||||
ei_connection_new(struct ei *ei, object_id_t id, uint32_t version);
|
||||
|
||||
void
|
||||
ei_connection_remove_pending_callbacks(struct ei_connection *connection);
|
||||
|
||||
/**
|
||||
* Called when the ei_callback.done event is received after
|
||||
* an ei_connection_sync() request.
|
||||
*/
|
||||
typedef void (*ei_connection_sync_callback_t)(struct ei_connection *connection,
|
||||
void *user_data);
|
||||
typedef void (*ei_connection_sync_callback_done_t)(struct ei_connection_sync_callback *callback);
|
||||
|
||||
/**
|
||||
* Called for each registered callback when the last reference to it is
|
||||
* destroyed. This should be used to clean up user_data, if need be.
|
||||
*
|
||||
* This function is always called, even if disconnected and the done() function is never called.
|
||||
*/
|
||||
typedef void (*ei_connection_sync_callback_destroy_t)(struct ei_connection_sync_callback *callback);
|
||||
|
||||
struct ei_connection_sync_callback {
|
||||
struct object object; /* parent is struct ei */
|
||||
ei_connection_sync_callback_done_t done;
|
||||
ei_connection_sync_callback_destroy_t destroy;
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
struct ei_connection_sync_callback *
|
||||
ei_connection_sync_callback_new(struct ei *ei,
|
||||
ei_connection_sync_callback_done_t done,
|
||||
ei_connection_sync_callback_destroy_t destroy,
|
||||
void *user_data);
|
||||
|
||||
OBJECT_DECLARE_REF(ei_connection_sync_callback);
|
||||
OBJECT_DECLARE_UNREF(ei_connection_sync_callback);
|
||||
OBJECT_DECLARE_GETTER(ei_connection_sync_callback, context, struct ei*);
|
||||
OBJECT_DECLARE_GETTER(ei_connection_sync_callback, user_data, void*);
|
||||
DEFINE_UNREF_CLEANUP_FUNC(ei_connection_sync_callback);
|
||||
|
||||
void
|
||||
ei_connection_sync(struct ei_connection *connection,
|
||||
ei_connection_sync_callback_t callback,
|
||||
void *user_data);
|
||||
ei_connection_sync(struct ei_connection *connection, struct ei_connection_sync_callback *callback);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "util-bits.h"
|
||||
#include "util-macros.h"
|
||||
|
|
@ -91,6 +92,7 @@ ei_device_destroy(struct ei_device *device)
|
|||
ei_keyboard_unref(device->keyboard);
|
||||
ei_seat_unref(seat);
|
||||
free(device->name);
|
||||
free(device->pending_region_mapping_id);
|
||||
}
|
||||
|
||||
_public_
|
||||
|
|
@ -110,7 +112,9 @@ _public_
|
|||
OBJECT_IMPLEMENT_GETTER(ei_device, user_data, void *);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_SETTER(ei_device, user_data, void *);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(ei_device, width, uint32_t);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(ei_device, height, uint32_t);
|
||||
OBJECT_IMPLEMENT_GETTER_AS_REF(ei_device, proto_object, const struct brei_object *);
|
||||
|
||||
|
|
@ -138,6 +142,9 @@ ei_device_in_region(struct ei_device *device, double x, double y)
|
|||
{
|
||||
struct ei_region *r;
|
||||
|
||||
if (list_empty(&device->regions))
|
||||
return true;
|
||||
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (ei_region_contains(r, x, y))
|
||||
return true;
|
||||
|
|
@ -192,11 +199,26 @@ handle_msg_region(struct ei_device *device, uint32_t x, uint32_t y,
|
|||
ei_region_set_offset(r, x, y);
|
||||
ei_region_set_size(r, w, h);
|
||||
ei_region_set_physical_scale(r, scale);
|
||||
|
||||
_cleanup_free_ char *mapping_id = steal(&device->pending_region_mapping_id);
|
||||
if (mapping_id)
|
||||
ei_region_set_mapping_id(r, mapping_id);
|
||||
|
||||
ei_device_add_region(device, r);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
handle_msg_region_mapping_id(struct ei_device *device, const char *mapping_id)
|
||||
{
|
||||
if (device->pending_region_mapping_id)
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"EIS sent the region mapping_id twice");
|
||||
device->pending_region_mapping_id = xstrdup(mapping_id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
handle_msg_done(struct ei_device *device)
|
||||
{
|
||||
|
|
@ -315,7 +337,9 @@ handle_msg_start_emulating(struct ei_device *device, uint32_t serial, uint32_t s
|
|||
case EI_DEVICE_STATE_EMULATING:
|
||||
case EI_DEVICE_STATE_REMOVED_FROM_SERVER:
|
||||
result = brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Invalid device state %ud for a start_emulating event", device->state);
|
||||
"Invalid device state %u for a start_emulating event (sequence %u)",
|
||||
device->state,
|
||||
sequence);
|
||||
break;
|
||||
case EI_DEVICE_STATE_RESUMED:
|
||||
ei_queue_device_start_emulating_event(device, sequence);
|
||||
|
|
@ -345,7 +369,7 @@ handle_msg_stop_emulating(struct ei_device *device, uint32_t serial)
|
|||
case EI_DEVICE_STATE_RESUMED:
|
||||
case EI_DEVICE_STATE_REMOVED_FROM_SERVER:
|
||||
result = brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Invalid device state %ud for a stop_emulating event", device->state);
|
||||
"Invalid device state %u for a stop_emulating event", device->state);
|
||||
break;
|
||||
case EI_DEVICE_STATE_EMULATING:
|
||||
ei_queue_device_stop_emulating_event(device);
|
||||
|
|
@ -378,7 +402,7 @@ maybe_error_on_device_state(struct ei_device *device, const char *event_type)
|
|||
}
|
||||
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Invalid device state %ud for a %s event", device->state, event_type);
|
||||
"Invalid device state %u for a %s event", device->state, event_type);
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
|
|
@ -402,33 +426,41 @@ handle_msg_interface(struct ei_device *device, object_id_t id, const char *name,
|
|||
{
|
||||
DISCONNECT_IF_INVALID_ID(device, id);
|
||||
|
||||
struct ei *ei = ei_device_get_context(device);
|
||||
|
||||
if (streq(name, EI_POINTER_INTERFACE_NAME)) {
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_pointer, id, version);
|
||||
if (device->pointer)
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Duplicate ei_pointer interface object on device");
|
||||
device->pointer = ei_pointer_new(device, id, version);
|
||||
} else if (streq(name, EI_POINTER_ABSOLUTE_INTERFACE_NAME)) {
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_pointer_absolute, id, version);
|
||||
if (device->pointer_absolute)
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Duplicate ei_pointer_absolute interface object on device");
|
||||
device->pointer_absolute = ei_pointer_absolute_new(device, id, version);
|
||||
} else if (streq(name, EI_SCROLL_INTERFACE_NAME)) {
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_scroll, id, version);
|
||||
if (device->scroll)
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Duplicate ei_scroll interface object on device");
|
||||
device->scroll = ei_scroll_new(device, id, version);
|
||||
} else if (streq(name, EI_BUTTON_INTERFACE_NAME)) {
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_button, id, version);
|
||||
if (device->button)
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Duplicate ei_button interface object on device");
|
||||
device->button = ei_button_new(device, id, version);
|
||||
} else if (streq(name, EI_KEYBOARD_INTERFACE_NAME)) {
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_keyboard, id, version);
|
||||
if (device->keyboard)
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Duplicate ei_keyboard interface object on device");
|
||||
|
||||
device->keyboard = ei_keyboard_new(device, id, version);
|
||||
} else if (streq(name, EI_TOUCHSCREEN_INTERFACE_NAME)) {
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_touchscreen, id, version);
|
||||
if (device->touchscreen)
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Duplicate ei_touchscreen interface object on device");
|
||||
|
|
@ -454,6 +486,8 @@ static const struct ei_device_interface interface = {
|
|||
.stop_emulating = handle_msg_stop_emulating,
|
||||
.frame = handle_msg_frame,
|
||||
.interface = handle_msg_interface,
|
||||
/* v2 */
|
||||
.region_mapping_id = handle_msg_region_mapping_id,
|
||||
};
|
||||
|
||||
const struct ei_device_interface *
|
||||
|
|
@ -692,6 +726,7 @@ handle_msg_keymap(struct ei_keyboard *keyboard, uint32_t keymap_type, uint32_t k
|
|||
struct ei_device *device = ei_keyboard_get_device(keyboard);
|
||||
|
||||
ei_device_set_keymap(device, keymap_type, keymap_fd, keymap_sz);
|
||||
xclose (keymap_fd);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -827,6 +862,31 @@ handle_msg_touch_up(struct ei_touchscreen *touchscreen, uint32_t touchid)
|
|||
return maybe_error_on_device_state(device, "touch up");
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
handle_msg_touch_cancel(struct ei_touchscreen *touchscreen, uint32_t touchid)
|
||||
{
|
||||
struct ei_device *device = ei_touchscreen_get_device(touchscreen);
|
||||
|
||||
DISCONNECT_IF_SENDER_CONTEXT(device);
|
||||
|
||||
if (!ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)) {
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Touch cancel event for non-touch device");
|
||||
}
|
||||
|
||||
struct ei *ei = ei_device_get_context(device);
|
||||
if (ei->interface_versions.ei_touchscreen < EI_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) {
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Touch cancel event for touchscreen version v1");
|
||||
}
|
||||
|
||||
if (device->state == EI_DEVICE_STATE_EMULATING) {
|
||||
ei_queue_touch_cancel_event(device, touchid);
|
||||
return NULL;
|
||||
}
|
||||
return maybe_error_on_device_state(device, "touch cancel");
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
handle_msg_touchscreen_destroy(struct ei_touchscreen *touchscreen, uint32_t serial)
|
||||
{
|
||||
|
|
@ -844,6 +904,7 @@ static const struct ei_touchscreen_interface touchscreen_interface = {
|
|||
.down = handle_msg_touch_down,
|
||||
.motion = handle_msg_touch_motion,
|
||||
.up = handle_msg_touch_up,
|
||||
.cancel = handle_msg_touch_cancel,
|
||||
};
|
||||
|
||||
const struct ei_touchscreen_interface *
|
||||
|
|
@ -930,7 +991,7 @@ ei_device_keyboard_get_keymap(struct ei_device *device)
|
|||
}
|
||||
|
||||
static struct ei_keymap *
|
||||
ei_keymap_new(enum ei_keymap_type type, int fd, size_t size)
|
||||
ei_keymap_new(struct ei *ei, enum ei_keymap_type type, int fd, size_t size)
|
||||
{
|
||||
_unref_(ei_keymap) *keymap = ei_keymap_create(NULL);
|
||||
|
||||
|
|
@ -938,15 +999,20 @@ ei_keymap_new(enum ei_keymap_type type, int fd, size_t size)
|
|||
case EI_KEYMAP_TYPE_XKB:
|
||||
break;
|
||||
default:
|
||||
log_bug(ei, "Unsupported keymap type: %u", type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fd < 0 || size == 0)
|
||||
if (fd < 0 || size == 0) {
|
||||
log_bug(ei, "Invalid keymap fd %d with size %zu", fd, size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int newfd = xdup(fd);
|
||||
if (newfd < 0)
|
||||
if (newfd < 0) {
|
||||
log_bug(ei, "Failed to dup keymap fd: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
keymap->fd = newfd;
|
||||
keymap->type = type;
|
||||
|
|
@ -965,12 +1031,10 @@ ei_device_set_keymap(struct ei_device *device,
|
|||
if (!type)
|
||||
return;
|
||||
|
||||
_unref_(ei_keymap) *keymap = ei_keymap_new(type, keymap_fd, size);
|
||||
if (!keymap) {
|
||||
log_bug(ei_device_get_context(device),
|
||||
"Failed to apply server-requested keymap");
|
||||
struct ei *ei = ei_device_get_context(device);
|
||||
_unref_(ei_keymap) *keymap = ei_keymap_new(ei, type, keymap_fd, size);
|
||||
if (!keymap)
|
||||
return; /* FIXME: ei_device_remove() here */
|
||||
}
|
||||
|
||||
keymap->device = device;
|
||||
device->keymap = ei_keymap_ref(keymap);
|
||||
|
|
@ -1029,6 +1093,7 @@ ei_device_removed_by_server(struct ei_device *device)
|
|||
{
|
||||
struct ei_seat *seat = ei_device_get_seat(device);
|
||||
struct ei *ei = ei_device_get_context(device);
|
||||
struct ei_event *event;
|
||||
|
||||
switch (device->state) {
|
||||
case EI_DEVICE_STATE_NEW:
|
||||
|
|
@ -1039,6 +1104,11 @@ ei_device_removed_by_server(struct ei_device *device)
|
|||
case EI_DEVICE_STATE_PAUSED:
|
||||
case EI_DEVICE_STATE_RESUMED:
|
||||
case EI_DEVICE_STATE_EMULATING:
|
||||
list_for_each_safe(event, &device->pending_event_queue, link) {
|
||||
list_remove(&event->link);
|
||||
ei_event_unref(event);
|
||||
}
|
||||
|
||||
/* in the case of ei_disconnect() we may fake the
|
||||
* removal by the server, so we need to also remove the
|
||||
* pointer/keyboard/touch interfaces
|
||||
|
|
@ -1185,6 +1255,18 @@ ei_device_get_region(struct ei_device *device, size_t index)
|
|||
return list_nth_entry(struct ei_region, &device->regions, link, index);
|
||||
}
|
||||
|
||||
_public_ struct ei_region *
|
||||
ei_device_get_region_at(struct ei_device *device, double x, double y)
|
||||
{
|
||||
struct ei_region *r;
|
||||
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (ei_region_contains(r, x, y))
|
||||
return r;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void
|
||||
ei_device_resume_scrolling(struct ei_device *device, double x, double y)
|
||||
{
|
||||
|
|
@ -1322,7 +1404,7 @@ ei_device_pointer_motion(struct ei_device *device,
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1341,7 +1423,7 @@ ei_device_pointer_motion_absolute(struct ei_device *device,
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1363,7 +1445,7 @@ ei_device_button_button(struct ei_device *device,
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1388,7 +1470,7 @@ ei_device_scroll_delta(struct ei_device *device, double x, double y)
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1408,7 +1490,7 @@ ei_device_scroll_stop(struct ei_device *device, bool x, bool y)
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1437,7 +1519,7 @@ ei_device_scroll_cancel(struct ei_device *device, bool x, bool y)
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1470,10 +1552,15 @@ ei_device_scroll_discrete(struct ei_device *device, int32_t x, int32_t y)
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (abs(x) == 1 || abs(y) == 1) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: suspicious discrete event value 1, did you mean 120?", __func__);
|
||||
}
|
||||
|
||||
ei_device_resume_scrolling(device, x, y);
|
||||
|
||||
ei_send_pointer_scroll_discrete(device, x, y);
|
||||
|
|
@ -1508,7 +1595,7 @@ ei_device_keyboard_key(struct ei_device *device,
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1591,6 +1678,21 @@ ei_send_touch_up(struct ei_device *device, uint32_t tid)
|
|||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
ei_send_touch_cancel(struct ei_device *device, uint32_t tid)
|
||||
{
|
||||
struct ei *ei = ei_device_get_context(device);
|
||||
|
||||
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
|
||||
return 0;
|
||||
|
||||
device->send_frame_event = true;
|
||||
|
||||
int rc = ei_touchscreen_request_cancel(device->touchscreen, tid);
|
||||
if (rc)
|
||||
ei_disconnect(ei);
|
||||
return rc;
|
||||
}
|
||||
|
||||
_public_ struct ei_touch *
|
||||
ei_device_touch_new(struct ei_device *device)
|
||||
|
|
@ -1615,7 +1717,7 @@ ei_touch_down(struct ei_touch *touch, double x, double y)
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1625,14 +1727,11 @@ ei_touch_down(struct ei_touch *touch, double x, double y)
|
|||
return;
|
||||
}
|
||||
|
||||
struct ei_region *r;
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (!ei_region_contains(r, x, y)) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
|
||||
touch->state = TOUCH_IS_UP;
|
||||
return;
|
||||
}
|
||||
if (!ei_device_in_region(device, x, y)) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
|
||||
touch->state = TOUCH_IS_UP;
|
||||
return;
|
||||
}
|
||||
|
||||
touch->state = TOUCH_IS_DOWN;
|
||||
|
|
@ -1647,7 +1746,7 @@ ei_touch_motion(struct ei_touch *touch, double x, double y)
|
|||
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1657,14 +1756,11 @@ ei_touch_motion(struct ei_touch *touch, double x, double y)
|
|||
return;
|
||||
}
|
||||
|
||||
struct ei_region *r;
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (!ei_region_contains(r, x, y)) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
|
||||
ei_touch_up(touch);
|
||||
return;
|
||||
}
|
||||
if (!ei_device_in_region(device, x, y)) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
|
||||
ei_touch_up(touch);
|
||||
return;
|
||||
}
|
||||
|
||||
ei_send_touch_motion(touch->device, touch->tracking_id, x, y);
|
||||
|
|
@ -1676,7 +1772,7 @@ ei_touch_up(struct ei_touch *touch)
|
|||
struct ei_device *device = ei_touch_get_device(touch);
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not not emulating", __func__);
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1690,6 +1786,32 @@ ei_touch_up(struct ei_touch *touch)
|
|||
ei_send_touch_up(touch->device, touch->tracking_id);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
ei_touch_cancel(struct ei_touch *touch)
|
||||
{
|
||||
struct ei_device *device = ei_touch_get_device(touch);
|
||||
if (device->state != EI_DEVICE_STATE_EMULATING) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: device is not emulating", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (touch->state != TOUCH_IS_DOWN) {
|
||||
log_bug_client(ei_device_get_context(device),
|
||||
"%s: touch %u is not currently down", __func__, touch->tracking_id);
|
||||
return;
|
||||
}
|
||||
|
||||
touch->state = TOUCH_IS_UP;
|
||||
|
||||
struct ei *ei = ei_device_get_context(device);
|
||||
|
||||
if (ei->interface_versions.ei_touchscreen >= EI_TOUCHSCREEN_REQUEST_CANCEL_SINCE_VERSION)
|
||||
ei_send_touch_cancel(touch->device, touch->tracking_id);
|
||||
else
|
||||
ei_send_touch_up(touch->device, touch->tracking_id);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
ei_device_frame(struct ei_device *device, uint64_t time)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ struct ei_device {
|
|||
} scroll_state;
|
||||
|
||||
struct ei_keymap *keymap;
|
||||
|
||||
char *pending_region_mapping_id;
|
||||
};
|
||||
|
||||
struct ei_keymap {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ ei_event_type_to_string(enum ei_event_type type)
|
|||
CASE_RETURN_STRING(EI_EVENT_DEVICE_PAUSED);
|
||||
CASE_RETURN_STRING(EI_EVENT_DEVICE_RESUMED);
|
||||
CASE_RETURN_STRING(EI_EVENT_KEYBOARD_MODIFIERS);
|
||||
CASE_RETURN_STRING(EI_EVENT_PONG);
|
||||
CASE_RETURN_STRING(EI_EVENT_SYNC);
|
||||
CASE_RETURN_STRING(EI_EVENT_FRAME);
|
||||
CASE_RETURN_STRING(EI_EVENT_DEVICE_START_EMULATING);
|
||||
CASE_RETURN_STRING(EI_EVENT_DEVICE_STOP_EMULATING);
|
||||
|
|
@ -71,12 +73,13 @@ ei_event_type_to_string(enum ei_event_type type)
|
|||
static void
|
||||
ei_event_destroy(struct ei_event *event)
|
||||
{
|
||||
struct ei *ei = ei_event_get_context(event);
|
||||
|
||||
switch (event->type) {
|
||||
case EI_EVENT_CONNECT:
|
||||
case EI_EVENT_DISCONNECT:
|
||||
case EI_EVENT_SEAT_ADDED:
|
||||
case EI_EVENT_SEAT_REMOVED:
|
||||
case EI_EVENT_DEVICE_ADDED:
|
||||
case EI_EVENT_DEVICE_REMOVED:
|
||||
case EI_EVENT_DEVICE_PAUSED:
|
||||
case EI_EVENT_DEVICE_RESUMED:
|
||||
|
|
@ -96,6 +99,17 @@ ei_event_destroy(struct ei_event *event)
|
|||
case EI_EVENT_TOUCH_UP:
|
||||
case EI_EVENT_TOUCH_MOTION:
|
||||
break;
|
||||
case EI_EVENT_DEVICE_ADDED:
|
||||
if (ei->interface_versions.ei_device >= EI_DEVICE_REQUEST_READY_SINCE_VERSION)
|
||||
ei_device_request_ready(event->device);
|
||||
break;
|
||||
case EI_EVENT_PONG:
|
||||
ei_ping_unref(event->pong.ping);
|
||||
break;
|
||||
case EI_EVENT_SYNC:
|
||||
ei_sync_event_send_done(event);
|
||||
ei_pingpong_unref(event->sync.pingpong);
|
||||
break;
|
||||
}
|
||||
ei_device_unref(event->device);
|
||||
ei_seat_unref(event->seat);
|
||||
|
|
@ -154,7 +168,8 @@ check_event_type(struct ei_event *event,
|
|||
|
||||
if (!rc)
|
||||
log_bug_client(ei_event_get_context(event),
|
||||
"Invalid event type %u passed to %s()",
|
||||
"Invalid event type (%s) %u passed to %s()",
|
||||
ei_event_type_to_string(type),
|
||||
type, function_name);
|
||||
|
||||
return rc;
|
||||
|
|
@ -203,6 +218,14 @@ ei_event_keyboard_get_xkb_group(struct ei_event *event)
|
|||
return event->modifiers.group;
|
||||
}
|
||||
|
||||
_public_ struct ei_ping *
|
||||
ei_event_pong_get_ping(struct ei_event *event)
|
||||
{
|
||||
require_event_type(event, NULL, EI_EVENT_PONG);
|
||||
|
||||
return event->pong.ping;
|
||||
}
|
||||
|
||||
_public_ double
|
||||
ei_event_pointer_get_dx(struct ei_event *event)
|
||||
{
|
||||
|
|
@ -346,6 +369,14 @@ ei_event_touch_get_y(struct ei_event *event)
|
|||
return event->touch.y;
|
||||
}
|
||||
|
||||
_public_ bool
|
||||
ei_event_touch_get_is_cancel(struct ei_event *event)
|
||||
{
|
||||
require_event_type(event,false, EI_EVENT_TOUCH_UP);
|
||||
|
||||
return event->touch.is_cancel;
|
||||
}
|
||||
|
||||
_public_ uint64_t
|
||||
ei_event_get_time(struct ei_event *event)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -64,10 +64,17 @@ struct ei_event {
|
|||
struct {
|
||||
uint32_t touchid;
|
||||
double x, y;
|
||||
bool is_cancel;
|
||||
} touch;
|
||||
struct {
|
||||
uint32_t sequence;
|
||||
} start_emulating;
|
||||
struct {
|
||||
struct ei_ping *ping;
|
||||
} pong;
|
||||
struct {
|
||||
struct ei_pingpong *pingpong;
|
||||
} sync;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -79,6 +86,3 @@ ei_event_new_for_device(struct ei_device *device);
|
|||
|
||||
struct ei *
|
||||
ei_event_get_context(struct ei_event *event);
|
||||
|
||||
struct ei_event *
|
||||
ei_event_ref(struct ei_event *event);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "util-version.h"
|
||||
|
||||
#include "libei-private.h"
|
||||
#include "libei-connection.h"
|
||||
#include "ei-proto.h"
|
||||
|
||||
static void
|
||||
|
|
@ -150,9 +151,9 @@ handle_msg_interface_version(struct ei_handshake *setup, const char *name, uint3
|
|||
}
|
||||
|
||||
static void
|
||||
connected(struct ei_connection *connection, void *user_data)
|
||||
on_connected(struct ei_connection_sync_callback *callback)
|
||||
{
|
||||
struct ei *ei = ei_connection_get_context(connection);
|
||||
struct ei *ei = ei_connection_sync_callback_get_context(callback);
|
||||
|
||||
/* If we get here, the server didn't immediately disconnect us */
|
||||
if (ei->state == EI_STATE_DISCONNECTED)
|
||||
|
|
@ -169,14 +170,20 @@ handle_msg_connection(struct ei_handshake *setup, uint32_t serial, object_id_t i
|
|||
/* we're done with our handshake, drop it */
|
||||
ei_handshake_unref(steal(&ei->handshake));
|
||||
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_handshake, id, version);
|
||||
|
||||
ei->connection = ei_connection_new(ei, id, version);
|
||||
ei->state = EI_STATE_CONNECTING;
|
||||
ei_update_serial(ei, serial);
|
||||
|
||||
_unref_(ei_connection_sync_callback) *cb = ei_connection_sync_callback_new(ei,
|
||||
on_connected,
|
||||
NULL,
|
||||
NULL);
|
||||
/* Send a sync on the connection - EIS should immediately send a
|
||||
* disconnect event where applicable, so if we get through to our
|
||||
* sync callback, we didn't immediately get disconnected */
|
||||
ei_connection_sync(ei->connection, connected, NULL);
|
||||
ei_connection_sync(ei->connection, cb);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 keyboard WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 keyboard WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
124
src/libei-ping.c
Normal file
124
src/libei-ping.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/* SPDX-License-Identifier: MIT */
|
||||
/*
|
||||
* Copyright © 2024 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "util-mem.h"
|
||||
#include "util-macros.h"
|
||||
#include "util-object.h"
|
||||
#include "util-list.h"
|
||||
#include "brei-shared.h"
|
||||
|
||||
#include "libei-private.h"
|
||||
#include "libei-connection.h"
|
||||
|
||||
struct ei_ping {
|
||||
struct object object;
|
||||
uint64_t id;
|
||||
void *user_data;
|
||||
|
||||
struct ei *context;
|
||||
bool is_pending;
|
||||
bool is_done;
|
||||
};
|
||||
|
||||
static void
|
||||
ei_ping_destroy(struct ei_ping *ping)
|
||||
{
|
||||
if (!ping->is_pending)
|
||||
ei_unref(ping->context);
|
||||
}
|
||||
|
||||
static
|
||||
OBJECT_IMPLEMENT_CREATE(ei_ping);
|
||||
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_REF(ei_ping);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_ping);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(ei_ping, id, uint64_t);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(ei_ping, user_data, void*);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_SETTER(ei_ping, user_data, void*);
|
||||
static
|
||||
OBJECT_IMPLEMENT_GETTER(ei_ping, context, struct ei*);
|
||||
|
||||
_public_ struct ei_ping *
|
||||
ei_new_ping(struct ei *ei)
|
||||
{
|
||||
static uint64_t id = 0;
|
||||
|
||||
struct ei_ping *ping = ei_ping_create(NULL);
|
||||
ping->id = ++id;
|
||||
/* Ref our context while it's pending (i.e. only the caller has the ref).
|
||||
* Once it's pending we no longer need the ref.
|
||||
*/
|
||||
ping->context = ei_ref(ei);
|
||||
ping->is_pending = false;
|
||||
ping->is_done = false;
|
||||
|
||||
return ping;
|
||||
}
|
||||
|
||||
static void
|
||||
on_pong(struct ei_connection_sync_callback *callback)
|
||||
{
|
||||
struct ei_ping *ping = ei_connection_sync_callback_get_user_data(callback);
|
||||
ping->is_done = true;
|
||||
|
||||
struct ei *ei = ei_connection_sync_callback_get_context(callback);
|
||||
ei_queue_pong_event(ei, ping);
|
||||
/* ei_ping ref is removed in on_destroy */
|
||||
}
|
||||
|
||||
static void
|
||||
on_destroy(struct ei_connection_sync_callback *callback)
|
||||
{
|
||||
/* This is only called if we never received a pong */
|
||||
_unref_(ei_ping) *ping = ei_connection_sync_callback_get_user_data(callback);
|
||||
|
||||
/* We never got a pong because we got disconnected. Queue a fake pong event */
|
||||
if (!ping->is_done) {
|
||||
struct ei *ei = ei_connection_sync_callback_get_context(callback);
|
||||
ei_queue_pong_event(ei, ping);
|
||||
}
|
||||
}
|
||||
|
||||
_public_ void
|
||||
ei_ping(struct ei_ping *ping)
|
||||
{
|
||||
struct ei *ei = ei_ping_get_context(ping);
|
||||
|
||||
ei_unref(ping->context);
|
||||
ping->context = ei;
|
||||
ping->is_pending = true;
|
||||
|
||||
_unref_(ei_connection_sync_callback) *cb = ei_connection_sync_callback_new(ei,
|
||||
on_pong,
|
||||
on_destroy,
|
||||
ei_ping_ref(ping));
|
||||
ei_connection_sync(ei->connection, cb);
|
||||
}
|
||||
|
|
@ -76,24 +76,6 @@ ei_pingpong_get_interface(struct ei_pingpong *pingpong) {
|
|||
return &interface;
|
||||
}
|
||||
|
||||
struct ei_pingpong *
|
||||
ei_pingpong_new(struct ei *ei, ei_pingpong_func func, void *pingpong_data)
|
||||
{
|
||||
struct ei_pingpong *pingpong = ei_pingpong_create(&ei->object);
|
||||
|
||||
pingpong->proto_object.id = ei_get_new_id(ei);
|
||||
pingpong->proto_object.implementation = pingpong;
|
||||
pingpong->proto_object.interface = &ei_pingpong_proto_interface;
|
||||
pingpong->proto_object.version = VERSION_V(1);
|
||||
pingpong->pingpong_data = pingpong_data;
|
||||
ei_register_object(ei, &pingpong->proto_object);
|
||||
|
||||
pingpong->func = func;
|
||||
list_init(&pingpong->link);
|
||||
|
||||
return pingpong; /* ref owned by caller */
|
||||
}
|
||||
|
||||
struct ei_pingpong *
|
||||
ei_pingpong_new_for_id(struct ei *ei, object_id_t id, uint32_t version)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@
|
|||
struct ei;
|
||||
struct ei_pingpong;
|
||||
|
||||
typedef void (*ei_pingpong_func)(struct ei_pingpong *pingpong, void *pingpong_data, uint32_t proto_data);
|
||||
|
||||
/* This is a protocol-only object, not exposed in the API */
|
||||
struct ei_pingpong {
|
||||
struct object object;
|
||||
|
|
@ -42,7 +40,6 @@ struct ei_pingpong {
|
|||
|
||||
struct list link; /* for use by the callers, if needed */
|
||||
|
||||
ei_pingpong_func func;
|
||||
void *pingpong_data; /* Note: pingpong-data is attached to the pingpong */
|
||||
};
|
||||
|
||||
|
|
@ -55,8 +52,5 @@ OBJECT_DECLARE_SETTER(ei_pingpong, user_data, void *);
|
|||
OBJECT_DECLARE_REF(ei_pingpong);
|
||||
OBJECT_DECLARE_UNREF(ei_pingpong);
|
||||
|
||||
struct ei_pingpong *
|
||||
ei_pingpong_new(struct ei *ei, ei_pingpong_func func, void *pingpong_data);
|
||||
|
||||
struct ei_pingpong *
|
||||
ei_pingpong_new_for_id(struct ei *ei, object_id_t id, uint32_t version);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 pointer WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 pointer WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,17 @@ struct ei_interface_versions {
|
|||
uint32_t ei_touchscreen;
|
||||
};
|
||||
|
||||
struct ei_unsent {
|
||||
struct list node;
|
||||
struct iobuf *buf;
|
||||
};
|
||||
|
||||
struct ei_defunct_object {
|
||||
struct list node;
|
||||
object_id_t object_id;
|
||||
uint64_t time;
|
||||
};
|
||||
|
||||
struct ei {
|
||||
struct object object;
|
||||
|
||||
|
|
@ -86,6 +97,7 @@ struct ei {
|
|||
struct ei_handshake *handshake;
|
||||
struct ei_interface_versions interface_versions;
|
||||
struct list proto_objects; /* brei_object list */
|
||||
struct list defunct_objects;
|
||||
object_id_t next_object_id;
|
||||
|
||||
uint32_t serial;
|
||||
|
|
@ -94,6 +106,8 @@ struct ei {
|
|||
struct brei_context *brei;
|
||||
struct sink *sink;
|
||||
struct source *source;
|
||||
struct list unsent_queue;
|
||||
|
||||
struct ei_backend_interface backend_interface;
|
||||
void *backend;
|
||||
enum ei_state state;
|
||||
|
|
@ -106,6 +120,8 @@ struct ei {
|
|||
enum ei_log_priority priority;
|
||||
} log;
|
||||
|
||||
ei_clock_now_func clock_now;
|
||||
|
||||
bool is_sender;
|
||||
};
|
||||
|
||||
|
|
@ -115,9 +131,6 @@ ei_get_interface(struct ei *ei);
|
|||
int
|
||||
ei_set_socket(struct ei *ei, int fd);
|
||||
|
||||
void
|
||||
ei_disconnect(struct ei *ei);
|
||||
|
||||
struct ei *
|
||||
ei_get_context(struct ei *ei);
|
||||
|
||||
|
|
@ -143,6 +156,9 @@ int
|
|||
ei_send_message(struct ei *ei, const struct brei_object *object,
|
||||
uint32_t opcode, const char *signature, size_t nargs, ...);
|
||||
|
||||
int
|
||||
ei_unsent_flush(struct ei* ei);
|
||||
|
||||
void
|
||||
ei_connected(struct ei *ei);
|
||||
|
||||
|
|
@ -152,6 +168,12 @@ ei_queue_connect_event(struct ei *ei);
|
|||
void
|
||||
ei_queue_disconnect_event(struct ei *ei);
|
||||
|
||||
void
|
||||
ei_queue_pong_event(struct ei *ei, struct ei_ping *ping);
|
||||
|
||||
void
|
||||
ei_queue_sync_event(struct ei *ei, struct ei_pingpong *ping);
|
||||
|
||||
void
|
||||
ei_queue_device_removed_event(struct ei_device *device);
|
||||
|
||||
|
|
@ -218,6 +240,11 @@ ei_queue_touch_motion_event(struct ei_device *device, uint32_t touchid,
|
|||
void
|
||||
ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid);
|
||||
|
||||
void
|
||||
ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid);
|
||||
|
||||
void
|
||||
ei_sync_event_send_done(struct ei_event *e);
|
||||
|
||||
_printf_(6, 7) void
|
||||
ei_log_msg(struct ei *ei,
|
||||
|
|
@ -244,3 +271,13 @@ ei_log_msg_va(struct ei *ei,
|
|||
ei_log_msg((T_), EI_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪳 libei bug: " __VA_ARGS__)
|
||||
#define log_bug_client(T_, ...) \
|
||||
ei_log_msg((T_), EI_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪲 Bug: " __VA_ARGS__)
|
||||
|
||||
#define DISCONNECT_IF_INVALID_VERSION(ei_, intf_, id_, version_) do { \
|
||||
struct ei *_ei = (ei_); \
|
||||
uint32_t _version = (version_); \
|
||||
uint64_t _id = (id_); \
|
||||
if (_ei->interface_versions.intf_ < _version) { \
|
||||
log_bug(ei_, "Received invalid version %u for object id %#" PRIx64 ". Disconnecting", _version, _id); \
|
||||
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Received invalid version %u for object id %#" PRIx64 ".", _version, _id); \
|
||||
} \
|
||||
} while(0)
|
||||
|
|
|
|||
|
|
@ -24,12 +24,14 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "util-strings.h"
|
||||
|
||||
#include "libei-private.h"
|
||||
|
||||
static void
|
||||
ei_region_destroy(struct ei_region *region)
|
||||
{
|
||||
free(region->mapping_id);
|
||||
list_remove(®ion->link);
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +56,8 @@ OBJECT_IMPLEMENT_GETTER(ei_region, height, uint32_t);
|
|||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(ei_region, physical_scale, double);
|
||||
OBJECT_IMPLEMENT_SETTER(ei_region, physical_scale, double);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(ei_region, mapping_id, const char *);
|
||||
|
||||
struct ei_region *
|
||||
ei_region_new(void)
|
||||
|
|
@ -80,6 +84,12 @@ ei_region_set_size(struct ei_region *region, uint32_t w, uint32_t h)
|
|||
region->height = h;
|
||||
}
|
||||
|
||||
void
|
||||
ei_region_set_mapping_id(struct ei_region *region, const char *mapping_id)
|
||||
{
|
||||
region->mapping_id = xstrdup(mapping_id);
|
||||
}
|
||||
|
||||
_public_ bool
|
||||
ei_region_contains(struct ei_region *r, double x, double y)
|
||||
{
|
||||
|
|
@ -98,3 +108,78 @@ ei_region_convert_point(struct ei_region *r, double *x, double *y)
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _enable_tests_
|
||||
#include "util-munit.h"
|
||||
MUNIT_TEST(test_region_setters)
|
||||
{
|
||||
_unref_(ei_region) *r = ei_region_new();
|
||||
|
||||
ei_region_set_size(r, 1, 2);
|
||||
ei_region_set_offset(r, 3, 4);
|
||||
ei_region_set_physical_scale(r, 5.6);
|
||||
ei_region_set_mapping_id(r, "foo");
|
||||
|
||||
munit_assert_int(ei_region_get_width(r), ==, 1);
|
||||
munit_assert_int(ei_region_get_height(r), ==, 2);
|
||||
munit_assert_int(ei_region_get_x(r), ==, 3);
|
||||
munit_assert_int(ei_region_get_y(r), ==, 4);
|
||||
munit_assert_double(ei_region_get_physical_scale(r), ==, 5.6);
|
||||
munit_assert_string_equal(ei_region_get_mapping_id(r), "foo");
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_region_contains)
|
||||
{
|
||||
struct ei_region r = {0};
|
||||
|
||||
ei_region_set_size(&r, 100, 200);
|
||||
ei_region_set_offset(&r, 300, 400);
|
||||
|
||||
munit_assert_true(ei_region_contains(&r, 300, 400));
|
||||
munit_assert_true(ei_region_contains(&r, 399.9, 599.9));
|
||||
|
||||
munit_assert_false(ei_region_contains(&r, 299.9, 400));
|
||||
munit_assert_false(ei_region_contains(&r, 300, 399.9));
|
||||
|
||||
munit_assert_false(ei_region_contains(&r, 400.1, 400));
|
||||
munit_assert_false(ei_region_contains(&r, 400, 399.9));
|
||||
|
||||
munit_assert_false(ei_region_contains(&r, 299.9, 599.9));
|
||||
munit_assert_false(ei_region_contains(&r, 300, 600.1));
|
||||
|
||||
munit_assert_false(ei_region_contains(&r, 400, 599.9));
|
||||
munit_assert_false(ei_region_contains(&r, 399, 600));
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_region_convert)
|
||||
{
|
||||
struct ei_region r = {0};
|
||||
|
||||
ei_region_set_size(&r, 640, 480);
|
||||
ei_region_set_offset(&r, 100, 200);
|
||||
|
||||
double x = 100;
|
||||
double y = 200;
|
||||
munit_assert_true(ei_region_convert_point(&r, &x, &y));
|
||||
munit_assert_double_equal(x, 0, 4 /* precision */);
|
||||
munit_assert_double_equal(y, 0, 4 /* precision */);
|
||||
|
||||
x = 101.2;
|
||||
y = 202.3;
|
||||
munit_assert_true(ei_region_convert_point(&r, &x, &y));
|
||||
munit_assert_double_equal(x, 1.2, 4 /* precision */);
|
||||
munit_assert_double_equal(y, 2.3, 4 /* precision */);
|
||||
|
||||
x = 99.9;
|
||||
y = 199.9;
|
||||
munit_assert_false(ei_region_convert_point(&r, &x, &y));
|
||||
munit_assert_double_equal(x, 99.9, 4 /* precision */);
|
||||
munit_assert_double_equal(y, 199.9, 4 /* precision */);
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ struct ei_region {
|
|||
uint32_t x, y;
|
||||
uint32_t width, height;
|
||||
double physical_scale;
|
||||
char *mapping_id;
|
||||
};
|
||||
|
||||
struct ei_region *
|
||||
|
|
@ -47,3 +48,6 @@ ei_region_set_offset(struct ei_region *region, uint32_t x, uint32_t y);
|
|||
|
||||
void
|
||||
ei_region_set_physical_scale(struct ei_region *region, double scale);
|
||||
|
||||
void
|
||||
ei_region_set_mapping_id(struct ei_region *region, const char *mapping_id);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 scroll WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 scroll WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,8 @@ handle_msg_device(struct ei_seat *seat, object_id_t id, uint32_t version)
|
|||
|
||||
struct ei *ei = ei_seat_get_context(seat);
|
||||
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_device, id, version);
|
||||
|
||||
log_debug(ei, "Added device %#" PRIx64 "@v%u", id, version);
|
||||
/* device is in the seat's device list */
|
||||
struct ei_device *device = ei_device_new(seat, id, version);
|
||||
|
|
@ -231,10 +233,10 @@ ei_seat_has_capability(struct ei_seat *seat,
|
|||
{
|
||||
switch (cap) {
|
||||
case EI_DEVICE_CAP_POINTER:
|
||||
/* FIXME: a seat without pointer or pointer_absolute but button
|
||||
/* FIXME: a seat without pointer or pointer_absolute but button
|
||||
* and/or scroll should count as pointer here but that's niche
|
||||
* enough that we can figure that out when needed */
|
||||
return seat->capabilities.map[EI_POINTER_INTERFACE_INDEX] != 0;
|
||||
return seat->capabilities.map[EI_POINTER_INTERFACE_INDEX] != 0;
|
||||
case EI_DEVICE_CAP_POINTER_ABSOLUTE:
|
||||
return seat->capabilities.map[EI_POINTER_ABSOLUTE_INTERFACE_INDEX] != 0;
|
||||
case EI_DEVICE_CAP_KEYBOARD:
|
||||
|
|
@ -303,6 +305,7 @@ ei_seat_bind_capabilities(struct ei_seat *seat, ...)
|
|||
while ((cap = va_arg(args, enum ei_device_capability)) > 0) {
|
||||
mask_add(mask,ei_seat_cap_mask(seat, cap));
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
if (seat->capabilities.bound == mask)
|
||||
return;
|
||||
|
|
@ -330,6 +333,7 @@ ei_seat_unbind_capabilities(struct ei_seat *seat, ...)
|
|||
while ((cap = va_arg(args, enum ei_device_capability)) > 0) {
|
||||
mask_remove(mask, ei_seat_cap_mask(seat, cap));
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
if (seat->capabilities.bound == mask)
|
||||
return;
|
||||
|
|
@ -345,3 +349,37 @@ ei_seat_unbind_capabilities(struct ei_seat *seat, ...)
|
|||
|
||||
ei_seat_send_bind(seat, seat->capabilities.bound);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
ei_seat_request_device_with_capabilities(struct ei_seat *seat, ...)
|
||||
{
|
||||
switch (seat->state) {
|
||||
case EI_SEAT_STATE_DONE:
|
||||
break;
|
||||
case EI_SEAT_STATE_NEW:
|
||||
case EI_SEAT_STATE_REMOVED:
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t mask = 0;
|
||||
enum ei_device_capability cap;
|
||||
|
||||
va_list args;
|
||||
va_start(args, seat);
|
||||
while ((cap = va_arg(args, enum ei_device_capability)) > 0) {
|
||||
mask_add(mask, ei_seat_cap_mask(seat, cap));
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
if (mask == 0)
|
||||
return;
|
||||
|
||||
/* Check if requested capabilities are a subset of bound capabilities */
|
||||
if (!mask_all(seat->capabilities.bound, mask)) {
|
||||
struct ei *ei = ei_seat_get_context(seat);
|
||||
log_bug_client(ei, "Requested capabilities are not a subset of the bound capabilities");
|
||||
return;
|
||||
}
|
||||
|
||||
ei_seat_request_request_device(seat, mask);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 touchscreen WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 touch WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
246
src/libei.c
246
src/libei.c
|
|
@ -50,6 +50,12 @@ _Static_assert(sizeof(enum ei_keymap_type) == sizeof(int), "Invalid enum size");
|
|||
_Static_assert(sizeof(enum ei_event_type) == sizeof(int), "Invalid enum size");
|
||||
_Static_assert(sizeof(enum ei_log_priority) == sizeof(int), "Invalid enum size");
|
||||
|
||||
static void
|
||||
ei_unsent_free(struct ei_unsent *unsent);
|
||||
|
||||
static void
|
||||
ei_defunct_object_free(struct ei_defunct_object *obj);
|
||||
|
||||
static void
|
||||
ei_destroy(struct ei *ei)
|
||||
{
|
||||
|
|
@ -59,14 +65,26 @@ ei_destroy(struct ei *ei)
|
|||
while ((e = ei_get_event(ei)) != NULL)
|
||||
ei_event_unref(e);
|
||||
|
||||
struct ei_unsent *unsent;
|
||||
list_for_each_safe(unsent, &ei->unsent_queue, node) {
|
||||
ei_unsent_free(unsent);
|
||||
}
|
||||
|
||||
if (ei->backend_interface.destroy)
|
||||
ei->backend_interface.destroy(ei, ei->backend);
|
||||
|
||||
ei->backend = NULL;
|
||||
ei_handshake_unref(ei->handshake);
|
||||
ei_connection_unref(ei->connection);
|
||||
brei_context_unref(ei->brei);
|
||||
|
||||
sink_unref(ei->sink);
|
||||
free(ei->name);
|
||||
|
||||
struct ei_defunct_object *obj;
|
||||
list_for_each_safe(obj, &ei->defunct_objects, node) {
|
||||
ei_defunct_object_free(obj);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
|
|
@ -100,20 +118,22 @@ ei_create_context(bool is_sender, void *user_data)
|
|||
list_init(&ei->event_queue);
|
||||
list_init(&ei->seats);
|
||||
list_init(&ei->proto_objects);
|
||||
list_init(&ei->unsent_queue);
|
||||
list_init(&ei->defunct_objects);
|
||||
|
||||
ei->interface_versions = (struct ei_interface_versions){
|
||||
.ei_connection = VERSION_V(1),
|
||||
.ei_handshake = VERSION_V(1),
|
||||
.ei_callback = VERSION_V(1),
|
||||
.ei_pingpong = VERSION_V(1),
|
||||
.ei_seat = VERSION_V(1),
|
||||
.ei_device = VERSION_V(1),
|
||||
.ei_seat = VERSION_V(2),
|
||||
.ei_device = VERSION_V(3),
|
||||
.ei_pointer = VERSION_V(1),
|
||||
.ei_pointer_absolute = VERSION_V(1),
|
||||
.ei_scroll = VERSION_V(1),
|
||||
.ei_button = VERSION_V(1),
|
||||
.ei_keyboard = VERSION_V(1),
|
||||
.ei_touchscreen = VERSION_V(1),
|
||||
.ei_touchscreen = VERSION_V(2),
|
||||
};
|
||||
/* This must be v1 until the server tells us otherwise */
|
||||
ei->handshake = ei_handshake_new(ei, VERSION_V(1));
|
||||
|
|
@ -151,15 +171,27 @@ ei_update_serial(struct ei *ei, uint32_t serial)
|
|||
void
|
||||
ei_register_object(struct ei *ei, struct brei_object *object)
|
||||
{
|
||||
log_debug(ei, "registering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
|
||||
log_debug(ei, "object %#" PRIx64 " registering %s v%u", object->id, object->interface->name, object->version);
|
||||
list_append(&ei->proto_objects, &object->link);
|
||||
}
|
||||
|
||||
static void
|
||||
ei_defunct_object_free(struct ei_defunct_object *obj)
|
||||
{
|
||||
list_remove(&obj->node);
|
||||
free(obj);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ei_unregister_object(struct ei *ei, struct brei_object *object)
|
||||
{
|
||||
log_debug(ei, "deregistering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
|
||||
log_debug(ei, "object %#" PRIx64 " deregistering %s v%u", object->id, object->interface->name, object->version);
|
||||
list_remove(&object->link);
|
||||
struct ei_defunct_object *obj = xalloc(sizeof *obj);
|
||||
obj->object_id = object->id;
|
||||
obj->time = ei_now(ei);
|
||||
list_append(&ei->defunct_objects, &obj->node);
|
||||
}
|
||||
|
||||
_public_ bool
|
||||
|
|
@ -253,8 +285,10 @@ queue_event(struct ei *ei, struct ei_event *event)
|
|||
break;
|
||||
case EI_EVENT_FRAME:
|
||||
/* silently discard empty frames */
|
||||
if (list_empty(&device->pending_event_queue))
|
||||
if (list_empty(&device->pending_event_queue)) {
|
||||
ei_event_unref(event);
|
||||
return;
|
||||
}
|
||||
|
||||
struct ei_event *pending;
|
||||
list_for_each_safe(pending, &device->pending_event_queue, link) {
|
||||
|
|
@ -265,7 +299,7 @@ queue_event(struct ei *ei, struct ei_event *event)
|
|||
break;
|
||||
default:
|
||||
if (device && !list_empty(&device->pending_event_queue))
|
||||
ei_queue_frame_event(device, ei_now(ei));
|
||||
ei_queue_frame_event(device, ei_now(ei));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -294,6 +328,39 @@ ei_queue_disconnect_event(struct ei *ei)
|
|||
queue_event(ei, e);
|
||||
}
|
||||
|
||||
void
|
||||
ei_queue_pong_event(struct ei *ei, struct ei_ping *ping)
|
||||
{
|
||||
struct ei_event *e = ei_event_new(ei);
|
||||
e->type = EI_EVENT_PONG;
|
||||
e->pong.ping = ei_ping_ref(ping);
|
||||
|
||||
queue_event(ei, e);
|
||||
}
|
||||
|
||||
void
|
||||
ei_sync_event_send_done(struct ei_event *e)
|
||||
{
|
||||
struct ei *ei = ei_event_get_context(e);
|
||||
_unref_(ei_pingpong) *pp = steal(&e->sync.pingpong);
|
||||
log_debug(ei_event_get_context(e),
|
||||
"object %#" PRIx64 ": ping pong done",
|
||||
ei_pingpong_get_id(pp));
|
||||
|
||||
if (ei->state < EI_STATE_DISCONNECTED)
|
||||
ei_pingpong_request_done(pp, 0);
|
||||
}
|
||||
|
||||
void
|
||||
ei_queue_sync_event(struct ei *ei, struct ei_pingpong *ping)
|
||||
{
|
||||
struct ei_event *e = ei_event_new(ei);
|
||||
e->type = EI_EVENT_SYNC;
|
||||
e->sync.pingpong = ei_pingpong_ref(ping);
|
||||
|
||||
queue_event(ei, e);
|
||||
}
|
||||
|
||||
void
|
||||
ei_queue_seat_added_event(struct ei_seat *seat)
|
||||
{
|
||||
|
|
@ -516,7 +583,7 @@ ei_queue_touch_down_event(struct ei_device *device, uint32_t touchid,
|
|||
struct ei_event *e = ei_event_new_for_device(device);
|
||||
|
||||
e->type = EI_EVENT_TOUCH_DOWN;
|
||||
e->touch.touchid = touchid,
|
||||
e->touch.touchid = touchid;
|
||||
e->touch.x = x;
|
||||
e->touch.y = y;
|
||||
|
||||
|
|
@ -530,7 +597,7 @@ ei_queue_touch_motion_event(struct ei_device *device, uint32_t touchid,
|
|||
struct ei_event *e = ei_event_new_for_device(device);
|
||||
|
||||
e->type = EI_EVENT_TOUCH_MOTION;
|
||||
e->touch.touchid = touchid,
|
||||
e->touch.touchid = touchid;
|
||||
e->touch.x = x;
|
||||
e->touch.y = y;
|
||||
|
||||
|
|
@ -543,12 +610,25 @@ ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid)
|
|||
struct ei_event *e = ei_event_new_for_device(device);
|
||||
|
||||
e->type = EI_EVENT_TOUCH_UP;
|
||||
e->touch.touchid = touchid,
|
||||
e->touch.touchid = touchid;
|
||||
e->touch.is_cancel = false;
|
||||
|
||||
queue_event(ei_device_get_context(device), e);
|
||||
}
|
||||
|
||||
void
|
||||
ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid)
|
||||
{
|
||||
struct ei_event *e = ei_event_new_for_device(device);
|
||||
|
||||
e->type = EI_EVENT_TOUCH_UP;
|
||||
e->touch.touchid = touchid;
|
||||
e->touch.is_cancel = true;
|
||||
|
||||
queue_event(ei_device_get_context(device), e);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
ei_disconnect(struct ei *ei)
|
||||
{
|
||||
if (ei->state == EI_STATE_DISCONNECTED ||
|
||||
|
|
@ -566,8 +646,9 @@ ei_disconnect(struct ei *ei)
|
|||
ei_seat_remove(seat);
|
||||
}
|
||||
|
||||
if (state != EI_STATE_NEW) {
|
||||
if (state != EI_STATE_NEW && state != EI_STATE_BACKEND) {
|
||||
ei_connection_request_disconnect(ei->connection);
|
||||
ei_connection_remove_pending_callbacks(ei->connection);
|
||||
}
|
||||
ei_queue_disconnect_event(ei);
|
||||
ei->state = EI_STATE_DISCONNECTED;
|
||||
|
|
@ -590,6 +671,9 @@ handle_msg_seat(struct ei_connection *connection, object_id_t seat_id, uint32_t
|
|||
DISCONNECT_IF_INVALID_ID(connection, seat_id);
|
||||
|
||||
struct ei *ei = ei_connection_get_context(connection);
|
||||
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_seat, seat_id, version);
|
||||
|
||||
struct ei_seat *seat = ei_seat_new(ei, seat_id, version);
|
||||
|
||||
/* We might get the seat event before our callback finished, so make sure
|
||||
|
|
@ -654,6 +738,10 @@ handle_msg_disconnected(struct ei_connection *connection, uint32_t last_serial,
|
|||
|
||||
if (reason == EI_CONNECTION_DISCONNECT_REASON_DISCONNECTED) {
|
||||
log_info(ei, "Disconnected by EIS");
|
||||
/* We got disconnected, disconnect our source because whatever
|
||||
* we'd receive after this is garbage and the server won't
|
||||
* want to hear anything from us anyway. */
|
||||
source_remove(ei->source);
|
||||
ei_disconnect(ei);
|
||||
return NULL;
|
||||
} else {
|
||||
|
|
@ -664,11 +752,29 @@ handle_msg_disconnected(struct ei_connection *connection, uint32_t last_serial,
|
|||
}
|
||||
|
||||
static struct brei_result *
|
||||
handle_msg_invalid_object(struct ei_connection *connection, uint32_t last_serial, object_id_t object)
|
||||
handle_msg_invalid_object(struct ei_connection *connection, uint32_t last_serial, object_id_t object_id)
|
||||
{
|
||||
struct ei *ei = ei_connection_get_context(connection);
|
||||
|
||||
log_bug(ei, "Invalid object %#" PRIx64 " after %u, I don't yet know how to handle that", object, last_serial);
|
||||
/* The protocol is async, so what can happen is:
|
||||
*
|
||||
* server sends A->destroyed()
|
||||
* client sends A->foo()
|
||||
* client receives A->destroyed()
|
||||
* server receives A->foo()
|
||||
* server sends invalid_object_id(A)
|
||||
* client receives invalid_object_id(A)
|
||||
*
|
||||
* This is expected and we shouldn't have a problem with that.
|
||||
*/
|
||||
|
||||
struct ei_defunct_object *defunct;
|
||||
list_for_each_safe(defunct, &ei->defunct_objects, node) {
|
||||
if (defunct->object_id == object_id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
log_bug(ei, "Invalid object %#" PRIx64 " after serial %u, I don't yet know how to handle that", object_id, last_serial);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -679,9 +785,11 @@ handle_msg_ping(struct ei_connection *connection, object_id_t id, uint32_t vers
|
|||
DISCONNECT_IF_INVALID_ID(connection, id);
|
||||
|
||||
struct ei *ei = ei_connection_get_context(connection);
|
||||
_unref_(ei_pingpong) *pingpong = ei_pingpong_new_for_id(ei, id, version);
|
||||
|
||||
ei_pingpong_request_done(pingpong, 0);
|
||||
DISCONNECT_IF_INVALID_VERSION(ei, ei_pingpong, id, version);
|
||||
|
||||
_unref_(ei_pingpong) *pingpong = ei_pingpong_new_for_id(ei, id, version);
|
||||
ei_queue_sync_event(ei_connection_get_context(connection), pingpong);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -720,15 +828,33 @@ lookup_object(object_id_t object_id, struct brei_object **object, void *userdata
|
|||
static void
|
||||
connection_dispatch(struct source *source, void *userdata)
|
||||
{
|
||||
static uint8_t cleanup;
|
||||
struct ei *ei = userdata;
|
||||
enum ei_state old_state = ei->state;
|
||||
|
||||
_unref_(brei_result) *result = brei_dispatch(ei->brei, source_get_fd(source),
|
||||
lookup_object, ei);
|
||||
if (result) {
|
||||
log_warn(ei, "Connection error: %s", brei_result_get_explanation(result));
|
||||
brei_drain_fd(source_get_fd(source));
|
||||
/* Flush any pending writes, if we have them */
|
||||
int rc = ei_unsent_flush(ei);
|
||||
if (rc < 0 && rc != -EAGAIN) {
|
||||
log_warn(ei, "Error flushing unsent queue: %s", strerror(-rc));
|
||||
ei_disconnect(ei);
|
||||
} else {
|
||||
_unref_(brei_result) *result = brei_dispatch(ei->brei, source_get_fd(source),
|
||||
lookup_object, ei);
|
||||
if (result) {
|
||||
log_warn(ei, "Connection error: %s", brei_result_get_explanation(result));
|
||||
brei_drain_fd(source_get_fd(source));
|
||||
ei_disconnect(ei);
|
||||
} else if (++cleanup % 20 == 0) {
|
||||
uint64_t now = ei_now(ei);
|
||||
struct ei_defunct_object *defunct;
|
||||
|
||||
list_for_each_safe(defunct, &ei->defunct_objects, node) {
|
||||
/* Drop defunct objects after 5s */
|
||||
if (now - defunct->time < s2us(5))
|
||||
break;
|
||||
ei_defunct_object_free(defunct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char *states[] = {
|
||||
|
|
@ -746,11 +872,50 @@ connection_dispatch(struct source *source, void *userdata)
|
|||
states[ei->state]);
|
||||
}
|
||||
|
||||
static void
|
||||
ei_queue_unsent(struct ei *ei, struct source *source, struct iobuf *buf)
|
||||
{
|
||||
if (list_empty(&ei->unsent_queue)) {
|
||||
source_enable_write(source, true);
|
||||
}
|
||||
|
||||
struct ei_unsent *unsent = xalloc(sizeof *unsent);
|
||||
unsent->buf = buf;
|
||||
list_append(&ei->unsent_queue, &unsent->node);
|
||||
}
|
||||
|
||||
static void
|
||||
ei_unsent_free(struct ei_unsent *unsent)
|
||||
{
|
||||
list_remove(&unsent->node);
|
||||
iobuf_free(unsent->buf);
|
||||
free(unsent);
|
||||
}
|
||||
|
||||
int
|
||||
ei_unsent_flush(struct ei* ei)
|
||||
{
|
||||
if (list_empty(&ei->unsent_queue))
|
||||
return 0;
|
||||
|
||||
struct source *source = ei->source;
|
||||
int fd = source_get_fd(source);
|
||||
struct ei_unsent *unsent;
|
||||
list_for_each_safe(unsent, &ei->unsent_queue, node) {
|
||||
int rc = iobuf_send(unsent->buf, fd);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
ei_unsent_free(unsent);
|
||||
}
|
||||
source_enable_write(source, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ei_send_message(struct ei *ei, const struct brei_object *object,
|
||||
uint32_t opcode, const char *signature, size_t nargs, ...)
|
||||
{
|
||||
log_debug(ei, "sending: object %#" PRIx64 " (%s@v%u:%s(%u)) signature '%s'",
|
||||
log_debug(ei, "object %#" PRIx64 " sending: (%s@v%u:%s(%u)) signature '%s'",
|
||||
object->id,
|
||||
object->interface->name,
|
||||
object->interface->version,
|
||||
|
|
@ -775,7 +940,19 @@ ei_send_message(struct ei *ei, const struct brei_object *object,
|
|||
_cleanup_iobuf_ struct iobuf *buf = brei_result_get_data(result);
|
||||
assert(buf);
|
||||
int fd = source_get_fd(ei->source);
|
||||
int rc = iobuf_send(buf, fd);
|
||||
int rc = -EPIPE;
|
||||
if (fd != -1) {
|
||||
rc = ei_unsent_flush(ei);
|
||||
if (rc >= 0)
|
||||
rc = iobuf_send(buf, fd);
|
||||
if (rc == -EAGAIN) {
|
||||
ei_queue_unsent(ei, ei->source, steal(&buf));
|
||||
rc = 0;
|
||||
} else if (rc < 0){
|
||||
log_warn(ei, "failed to send message: %s", strerror(-rc));
|
||||
source_remove(ei->source);
|
||||
}
|
||||
}
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
|
|
@ -819,19 +996,30 @@ ei_configure_name(struct ei *ei, const char *name)
|
|||
ei->name = xstrdup(name);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
ei_clock_set_now_func(struct ei *ei, ei_clock_now_func func)
|
||||
{
|
||||
ei->clock_now = func;
|
||||
}
|
||||
|
||||
_public_ uint64_t
|
||||
ei_now(struct ei *ei)
|
||||
{
|
||||
uint64_t ts = 0;
|
||||
int rc = now(&ts);
|
||||
|
||||
if (rc < 0) {
|
||||
/* We should probably disconnect here but the chances of this
|
||||
* happening are so slim it's not worth worrying about. Plus,
|
||||
* if this fails we're likely to be inside eis_device_frame()
|
||||
* so we should flush a frame event before disconnecting and... */
|
||||
log_error(ei, "clock_gettime failed: %s", strerror(-rc));
|
||||
if (ei->clock_now)
|
||||
ts = ei->clock_now(ei);
|
||||
else {
|
||||
int rc = now(&ts);
|
||||
if (rc < 0) {
|
||||
/* We should probably disconnect here but the chances of this
|
||||
* happening are so slim it's not worth worrying about. Plus,
|
||||
* if this fails we're likely to be inside eis_device_frame()
|
||||
* so we should flush a frame event before disconnecting and... */
|
||||
log_error(ei, "clock_gettime failed: %s", strerror(-rc));
|
||||
}
|
||||
}
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
|
|
|
|||
503
src/libei.h
503
src/libei.h
|
|
@ -60,13 +60,11 @@ extern "C" {
|
|||
* libei clients come in @ref ei_new_sender "sender" and @ref ei_new_receiver
|
||||
* "receiver" modes, depending on whether the client sends or receives events
|
||||
* from the EIS implementation. See @ref libei-sender and @ref libei-receiver
|
||||
for
|
||||
* API calls specific to either mode.
|
||||
* for API calls specific to either mode.
|
||||
*
|
||||
* @note A libei context is restricted to either sender or receiver mode, not
|
||||
* both. The EIS implementation however may accept both sender and receiver
|
||||
clients,
|
||||
* and will work as corresponding receiver or sender for this
|
||||
* clients, and will work as corresponding receiver or sender for this
|
||||
* client. It is up to the implementation to disconnect clients that it does not
|
||||
* want to allow. See eis_client_is_sender() for details.
|
||||
*
|
||||
|
|
@ -92,7 +90,7 @@ extern "C" {
|
|||
* The sender client API is available only to clients created with
|
||||
* ei_new_sender(). For those clients the EIS implemententation creates
|
||||
* devices and but it is the libei client that sends events **to** EIS
|
||||
implementation.
|
||||
* implementation.
|
||||
* The primary use-case is input emulation from a client, akin to xdotool.
|
||||
*
|
||||
* It is a client bug to call any of these functions for a client created
|
||||
|
|
@ -205,6 +203,28 @@ struct ei_keymap;
|
|||
*/
|
||||
struct ei_region;
|
||||
|
||||
/**
|
||||
* @struct ei_touch
|
||||
*
|
||||
* A single touch initiated by a sender context.
|
||||
*
|
||||
* @see ei_device_touch_new
|
||||
* @see ei_touch_down
|
||||
* @see ei_touch_motion
|
||||
* @see ei_touch_up
|
||||
*/
|
||||
struct ei_touch;
|
||||
|
||||
/**
|
||||
* @struct ei_ping
|
||||
*
|
||||
* A callback struct returned by ei_new_ping() to handle
|
||||
* roundtrips to the EIS implementation.
|
||||
*
|
||||
* @see ei_new_ping
|
||||
*/
|
||||
struct ei_ping;
|
||||
|
||||
/**
|
||||
* @enum ei_device_type
|
||||
* @ingroup libei-device
|
||||
|
|
@ -294,6 +314,15 @@ enum ei_keymap_type {
|
|||
EI_KEYMAP_TYPE_XKB = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* @enum ei_event_type
|
||||
*
|
||||
* This enum is not exhaustive, future versions of this library may add
|
||||
* new event types.
|
||||
*
|
||||
* Unknown events must be released by the caller with ei_event_unref(),
|
||||
* see ei_get_event().
|
||||
*/
|
||||
enum ei_event_type {
|
||||
/**
|
||||
* The server has approved the connection to this client. Where the
|
||||
|
|
@ -367,13 +396,24 @@ enum ei_event_type {
|
|||
|
||||
/**
|
||||
* Any events sent from this device will be discarded until the next
|
||||
* resume. The state of a device is not expected to change between
|
||||
* pause/resume - for any significant state changes the server is
|
||||
* expected to remove the device instead.
|
||||
* resume.
|
||||
*
|
||||
* Pausing a device resets the logical state of the device to neutral.
|
||||
* This includes:
|
||||
* - any buttons or keys logically down are released
|
||||
* - any modifiers logically down are released
|
||||
* - any touches logically down are released
|
||||
*
|
||||
* Sender clients must wait until @ref EI_EVENT_DEVICE_RESUMED
|
||||
* before sending events.
|
||||
*/
|
||||
EI_EVENT_DEVICE_PAUSED,
|
||||
|
||||
/**
|
||||
* The client may send events.
|
||||
*
|
||||
* Once resumed, a sender client may call ei_device_start_emulating()
|
||||
* and begin emulating events.
|
||||
*/
|
||||
EI_EVENT_DEVICE_RESUMED,
|
||||
|
||||
|
|
@ -385,14 +425,58 @@ enum ei_event_type {
|
|||
* ei_event_keyboard_get_xkb_mods_locked(), and
|
||||
* ei_event_keyboard_get_xkb_group().
|
||||
*
|
||||
* This event is sent in response to an external modifier state
|
||||
* change. Where the client triggers a modifier state change in
|
||||
* response to ei_device_keyboard_key(), no such event is sent.
|
||||
* This event is sent in response to any modifier state or effective
|
||||
* group change, including where the change is triggered by a client
|
||||
* call to ei_device_keyboard_key().
|
||||
*
|
||||
* For receiver clients, this will always be properly ordered with
|
||||
* EI_EVENT_KEYBOARD_KEY events, so each key event should be
|
||||
* interpreted should using the most recently received modifier
|
||||
* state.
|
||||
*
|
||||
* For sender clients, the this event is not inherently synchronized
|
||||
* with calls to ei_device_keyboard_key(), but the client may call
|
||||
* ei_ping() when synchronization is required. When the corresponding
|
||||
* EI_EVENT_PONG event is received, all key events sent prior to the
|
||||
* sync request are guaranteed to have been processed, and any
|
||||
* directly-resulting modifiers events are guaranteed to have been
|
||||
* received. Note, however, that it is still possible for
|
||||
* indirectly-triggered state changes, such as via a keyboard
|
||||
* shortcut not encoded in the keymap, to be reported after the done
|
||||
* event.
|
||||
*
|
||||
* This event may arrive while a device is paused.
|
||||
*
|
||||
* Note: It was previously specified that a where a sender client
|
||||
* triggers a modifier state change in response to
|
||||
* ei_device_keyboard_key(), no MODIFIERS event would be sent.
|
||||
* Clients were expected to mix calls to xkb_state_update_key() and
|
||||
* xkb_state_update_mask() to track the state with libxkbcommon,
|
||||
* which could lead to disagreements between the client and server as
|
||||
* to the current state.
|
||||
*/
|
||||
EI_EVENT_KEYBOARD_MODIFIERS,
|
||||
|
||||
/**
|
||||
* Returned in response to ei_ping().
|
||||
*
|
||||
* Note that in the ei protocol, the matching client request is called `sync`
|
||||
* but for consistency in the libeis/libei API the C API uses
|
||||
* ei_ping() with `EI_EVENT_PONG` as return.
|
||||
*/
|
||||
EI_EVENT_PONG = 90,
|
||||
|
||||
/**
|
||||
* This event represents a synchronization request (ping) from the EIS
|
||||
* implementation. The corresponding reply (pong) will be sent when
|
||||
* it is unref'd. It has no other API.
|
||||
*
|
||||
* The caller must ensure that any state changes triggered by messages
|
||||
* received prior to this event have been resolved and communicated to the
|
||||
* EIS implementation prior to calling ei_event_unref().
|
||||
*/
|
||||
EI_EVENT_SYNC,
|
||||
|
||||
/**
|
||||
* "Hardware" frame event. This event **must** be sent by the server
|
||||
* and notifies the client that the previous set of events belong to
|
||||
|
|
@ -542,7 +626,7 @@ ei_new(void *user_data);
|
|||
* receive events.
|
||||
*
|
||||
* A context supports exactly one backend, set up with one of
|
||||
* ei_setup_backend_socket() or ei_setup_backend_fd().
|
||||
* ei_setup_backend_fd() or ei_setup_backend_socket().
|
||||
*
|
||||
* @param user_data An opaque pointer to be returned with ei_get_user_data()
|
||||
*
|
||||
|
|
@ -562,7 +646,7 @@ ei_new_sender(void *user_data);
|
|||
* send events.
|
||||
*
|
||||
* A context supports exactly one backend, set up with one of
|
||||
* ei_setup_backend_socket() or ei_setup_backend_fd().
|
||||
* ei_setup_backend_fd() or ei_setup_backend_socket().
|
||||
*
|
||||
* @param user_data An opaque pointer to be returned with ei_get_user_data()
|
||||
*
|
||||
|
|
@ -654,9 +738,9 @@ ei_log_context_get_func(struct ei_log_context *ctx);
|
|||
* messages with a log level equal or greater than than the one set in
|
||||
* ei_log_set_priority().
|
||||
*
|
||||
* The context passed to this function contains auxilary information about
|
||||
* The context passed to this function contains auxiliary information about
|
||||
* this log message such as the line number, file name and function name
|
||||
* this message occured in. The log context is valid only within the current
|
||||
* this message occurred in. The log context is valid only within the current
|
||||
* invocation of the log handler.
|
||||
*
|
||||
* @param ei The EI context
|
||||
|
|
@ -693,6 +777,24 @@ ei_log_set_priority(struct ei *ei, enum ei_log_priority priority);
|
|||
enum ei_log_priority
|
||||
ei_log_get_priority(const struct ei *ei);
|
||||
|
||||
/**
|
||||
* Optional override function for ei_now().
|
||||
*
|
||||
* By default ei_now() returns the current timestamp in CLOCK_MONOTONIC. This
|
||||
* may be overridden by a caller to provide a different timestamp.
|
||||
*
|
||||
* There is rarely a need to override this function. It exists for the libei-internal test suite.
|
||||
*/
|
||||
typedef uint64_t (*ei_clock_now_func)(struct ei *ei);
|
||||
|
||||
/**
|
||||
* Override the function that returns the current time ei_now().
|
||||
*
|
||||
* There is rarely a need to override this function. It exists for the libei-internal test suite.
|
||||
*/
|
||||
void
|
||||
ei_clock_set_now_func(struct ei *, ei_clock_now_func func);
|
||||
|
||||
/**
|
||||
* Set the name for this client. This is a suggestion to the
|
||||
* server only and may not be honored.
|
||||
|
|
@ -702,17 +804,48 @@ ei_log_get_priority(const struct ei *ei);
|
|||
* the EIS implementation.
|
||||
*
|
||||
* This function must be called immediately after ei_new() and before
|
||||
* setting up a backend with ei_setup_backend_socket() or
|
||||
* ei_setup_backend_fd().
|
||||
* setting up a backend with ei_setup_backend_fd() or
|
||||
* ei_setup_backend_socket().
|
||||
*/
|
||||
void
|
||||
ei_configure_name(struct ei * ei, const char *name);
|
||||
|
||||
/**
|
||||
* Initialize the ei context on the given socket file descriptor.
|
||||
* The ei context will initiate the conversation with the EIS server listening
|
||||
* on the other end of this socket.
|
||||
*
|
||||
* This is the preferred entry point for libei and should be the default
|
||||
* used in new clients. It allows for private-by-default socket file descriptors
|
||||
* and the policy on how the socket is created is delegated to the caller.
|
||||
*
|
||||
* If the connection was successful, an event of type @ref EI_EVENT_CONNECT
|
||||
* or @ref EI_EVENT_DISCONNECT will become available after a future call to
|
||||
* ei_dispatch().
|
||||
*
|
||||
* If the connection failed, use ei_unref() to release the data allocated
|
||||
* for this context.
|
||||
*
|
||||
* This function takes ownership of the file descriptor, and will close it
|
||||
* when tearing down.
|
||||
*
|
||||
* @return zero on success or a negative errno on failure
|
||||
*/
|
||||
int
|
||||
ei_setup_backend_fd(struct ei *ei, int fd);
|
||||
|
||||
/**
|
||||
* Set this ei context to use the socket backend. The ei context will
|
||||
* connect to the socket at the given path and initiate the conversation
|
||||
* with the EIS server listening on that socket.
|
||||
*
|
||||
* @note This backend requires the socket to be accessible
|
||||
* to connect to and forces the EIS implementation to to identification and
|
||||
* access control for clients. In almost all cases, the EIS implementation
|
||||
* should use pre-created fds per client and the client should instead use
|
||||
* ei_setup_backend_fd(). This backend is primarily useful for testing and
|
||||
* debugging.
|
||||
*
|
||||
* If @a socketpath is `NULL`, the value of the environment variable
|
||||
* `LIBEI_SOCKET` is used. If @a socketpath does not start with '/', it is
|
||||
* relative to `$XDG_RUNTIME_DIR`. If `XDG_RUNTIME_DIR` is not set, this
|
||||
|
|
@ -730,26 +863,6 @@ ei_configure_name(struct ei * ei, const char *name);
|
|||
int
|
||||
ei_setup_backend_socket(struct ei *ei, const char *socketpath);
|
||||
|
||||
/**
|
||||
* Initialize the ei context on the given socket. The ei context will
|
||||
* initiate the conversation with the EIS server listening on the other end
|
||||
* of this socket.
|
||||
*
|
||||
* If the connection was successful, an event of type @ref EI_EVENT_CONNECT
|
||||
* or @ref EI_EVENT_DISCONNECT will become available after a future call to
|
||||
* ei_dispatch().
|
||||
*
|
||||
* If the connection failed, use ei_unref() to release the data allocated
|
||||
* for this context.
|
||||
*
|
||||
* This function takes ownership of the file descriptor, and will close it
|
||||
* when tearing down.
|
||||
*
|
||||
* @return zero on success or a negative errno on failure
|
||||
*/
|
||||
int
|
||||
ei_setup_backend_fd(struct ei *ei, int fd);
|
||||
|
||||
/**
|
||||
* libei keeps a single file descriptor for all events. This fd should be
|
||||
* monitored for events by the caller's mainloop, e.g. using select(). When
|
||||
|
|
@ -759,6 +872,94 @@ ei_setup_backend_fd(struct ei *ei, int fd);
|
|||
int
|
||||
ei_get_fd(struct ei *ei);
|
||||
|
||||
/**
|
||||
* Create a new ei_ping object to trigger a round trip to the EIS implementation.
|
||||
* See ei_ping() for details.
|
||||
*
|
||||
* The returned @ref ei_ping is refcounted, use ei_ping_unref() to
|
||||
* drop the reference.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
struct ei_ping *
|
||||
ei_new_ping(struct ei *ei);
|
||||
|
||||
/**
|
||||
* Return a unique, increasing id for this struct. This ID is assigned at
|
||||
* struct creation and constant for the lifetime of the struct. The ID
|
||||
* does not exist at the protocol level.
|
||||
*
|
||||
* This is a convenience API to make it easier to put this struct into
|
||||
* e.g. a hashtable without having to use the pointer value itself.
|
||||
*
|
||||
* The ID increases by an unspecified amount.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
uint64_t
|
||||
ei_ping_get_id(struct ei_ping *ping);
|
||||
|
||||
/**
|
||||
* Increase the refcount of this struct by one. Use ei_ping_unref() to decrease
|
||||
* the refcount.
|
||||
*
|
||||
* @return the argument passed into the function
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
struct ei_ping *
|
||||
ei_ping_ref(struct ei_ping *ei_ping);
|
||||
|
||||
/**
|
||||
* Decrease the refcount of this struct by one. When the refcount reaches
|
||||
* zero, all allocated resources for this struct are released.
|
||||
*
|
||||
* @return always NULL
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
struct ei_ping *
|
||||
ei_ping_unref(struct ei_ping *ei_ping);
|
||||
|
||||
/**
|
||||
* Set a custom data pointer for this struct. libei will not look at or
|
||||
* modify the pointer. Use ei_ping_get_user_data() to retrieve a previously set
|
||||
* user data.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
void
|
||||
ei_ping_set_user_data(struct ei_ping *ei_ping, void *user_data);
|
||||
|
||||
/**
|
||||
* Return the custom data pointer for this struct. libei will not look at or
|
||||
* modify the pointer. Use ei_ping_set_user_data() to change the user data.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
void *
|
||||
ei_ping_get_user_data(struct ei_ping *ei_ping);
|
||||
|
||||
/**
|
||||
* Issue a roundtrip request to the EIS implementation, resulting in
|
||||
* an @ref EI_EVENT_PONG event when this roundtrip has been processed. Client
|
||||
* requests are processed in-order by the EIS implementation so this
|
||||
* function can be used as synchronization point between requests.
|
||||
*
|
||||
* If the client is disconnected before the roundtrip is complete,
|
||||
* libei will emulate a @ref EI_EVENT_PONG event before @ref
|
||||
* EI_EVENT_DISCONNECT.
|
||||
*
|
||||
* Note that in the ei protocol, the client request is `ei_connection.sync`
|
||||
* followed by `ei_callback.done`. For consistency with the
|
||||
* libeis API the C API uses ei_ping() with and @ref EI_EVENT_PONG as return
|
||||
* event.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
void
|
||||
ei_ping(struct ei_ping *ping);
|
||||
|
||||
/**
|
||||
* Main event dispatching function. Reads events of the file descriptors
|
||||
* and processes them internally. Use ei_get_event() to retrieve the
|
||||
|
|
@ -774,7 +975,8 @@ ei_dispatch(struct ei *ei);
|
|||
/**
|
||||
* Return the next event from the event queue, removing it from the queue.
|
||||
*
|
||||
* The returned object must be released by the caller with ei_event_unref()
|
||||
* The returned object must be released by the caller with ei_event_unref(),
|
||||
* even if the event type is unknown to the caller.
|
||||
*/
|
||||
struct ei_event *
|
||||
ei_get_event(struct ei *ei);
|
||||
|
|
@ -802,12 +1004,30 @@ ei_peek_event(struct ei *ei);
|
|||
* @returns a timestamp in microseconds for the current time to pass into
|
||||
* ei_device_frame().
|
||||
*
|
||||
* In the current implementation, the returned timestamp is CLOCK_MONOTONIC
|
||||
* for compatibility with evdev and libinput.
|
||||
* By default, the returned timestamp is CLOCK_MONOTONIC for compatibility with
|
||||
* evdev and libinput. This can be overridden with ei_clock_set_now_func().
|
||||
*/
|
||||
uint64_t
|
||||
ei_now(struct ei *ei);
|
||||
|
||||
/**
|
||||
* Disconnect the current ei context from the EIS implementation.
|
||||
*
|
||||
* After a call to ei_disconnect(), ei_get_event() will return
|
||||
* events to remove resources (e.g. seats and devices) as if they
|
||||
* had been removed by the EIS implementation. The last event
|
||||
* returned by ei_get_event() is @ref EI_EVENT_DISCONNECT after
|
||||
* which the context should be considered inert and any
|
||||
* remaining resources released with ei_unref().
|
||||
*
|
||||
* This does not free the resources associated with the ei context, use
|
||||
* ei_unref().
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
void
|
||||
ei_disconnect(struct ei *ei);
|
||||
|
||||
/**
|
||||
* @ingroup libei-seat
|
||||
*
|
||||
|
|
@ -836,7 +1056,7 @@ ei_seat_get_name(struct ei_seat *seat);
|
|||
/**
|
||||
* @ingroup libei-seat
|
||||
*
|
||||
* Return true if the capabilitiy is available on this seat or false
|
||||
* Return true if the capability is available on this seat or false
|
||||
* otherwise. The return value of this function is not affected by
|
||||
* ei_seat_confirm_capability().
|
||||
*/
|
||||
|
|
@ -868,7 +1088,7 @@ __attribute__((sentinel));
|
|||
/**
|
||||
* @ingroup libei-seat
|
||||
*
|
||||
* Unbind a seat's capabilities, terminatd by ``NULL``.
|
||||
* Unbind a seat's capabilities, terminated by ``NULL``.
|
||||
* This function indicates the the application is
|
||||
* no longer interested in devices with the given capability.
|
||||
*
|
||||
|
|
@ -880,6 +1100,31 @@ void
|
|||
ei_seat_unbind_capabilities(struct ei_seat *seat, ...)
|
||||
__attribute__((sentinel));
|
||||
|
||||
/**
|
||||
* @ingroup libei-seat
|
||||
*
|
||||
* Request a new device with (a subset of) the given capabilities from the EIS
|
||||
* implementation. If the EIS implementation creates a device in response
|
||||
* to this request the device will arrive via an event of type @ref
|
||||
* EI_EVENT_DEVICE_ADDED.
|
||||
*
|
||||
* The device's capabilities may not match the requested capabilities. For
|
||||
* example requesting a pointer + keyboard device may result in the creation
|
||||
* of a pointer-only device or it may result in the creation of a pointer +
|
||||
* keyboard + touch device. It is up to the caller to handle capability
|
||||
* mismatches.
|
||||
*
|
||||
* This function should be used by a caller when the current set of devices
|
||||
* is insufficient for the functionality the client requires. For example
|
||||
* a client that has previously called ei_device_close() on a device may
|
||||
* need a device again with similar capabilities.
|
||||
*
|
||||
* The capabilities must be a subset of the capabilities requested in
|
||||
* ei_seat_bind_capabilities().
|
||||
*/
|
||||
void
|
||||
ei_seat_request_device_with_capabilities(struct ei_seat *seat, ...)
|
||||
__attribute__((sentinel));
|
||||
|
||||
/**
|
||||
* @ingroup libei-seat
|
||||
|
|
@ -902,13 +1147,24 @@ struct ei *
|
|||
ei_seat_get_context(struct ei_seat *seat);
|
||||
|
||||
/**
|
||||
* Release resources associated with this event. This function always
|
||||
* returns NULL.
|
||||
* Increase the refcount of this struct by one. Use ei_event_unref() to decrease
|
||||
* the refcount. This function always returns the same event that the reference
|
||||
* was added to.
|
||||
*
|
||||
* The caller cannot increase the refcount of an event. Events should be
|
||||
* considered transient data and not be held longer than required.
|
||||
* ei_event_unref() is provided for consistency (as opposed to, say,
|
||||
* ei_event_free()).
|
||||
* Events should be considered transient data and not be held longer than
|
||||
* required.
|
||||
*/
|
||||
struct ei_event *
|
||||
ei_event_ref(struct ei_event *event);
|
||||
|
||||
/**
|
||||
* Decrease the refcount of this struct by one. When the refcount reaches
|
||||
* zero, all allocated resources for this struct are released.
|
||||
*
|
||||
* Events should be considered transient data and not be held longer than
|
||||
* required.
|
||||
*
|
||||
* @return always NULL
|
||||
*/
|
||||
struct ei_event *
|
||||
ei_event_unref(struct ei_event *event);
|
||||
|
|
@ -958,8 +1214,7 @@ ei_device_ref(struct ei_device *device);
|
|||
* @ingroup libei-device
|
||||
*
|
||||
* Decrease the refcount of this struct by one. When the refcount reaches
|
||||
* zero, the context disconnects from the server and all allocated resources
|
||||
* are released.
|
||||
* zero, all allocated resources for this struct are released.
|
||||
*
|
||||
* @return always NULL
|
||||
*/
|
||||
|
|
@ -1009,17 +1264,6 @@ ei_device_get_width(struct ei_device *device);
|
|||
uint32_t
|
||||
ei_device_get_height(struct ei_device *device);
|
||||
|
||||
/**
|
||||
* @ingroup libei-device
|
||||
*
|
||||
* Return the keymap assigned to this device by the EIS implementation or NULL
|
||||
* if no keymap is set.
|
||||
*
|
||||
* The keymap is constant for the life of the device.
|
||||
*/
|
||||
struct ei_keymap *
|
||||
ei_device_get_keymap(struct ei_device *device);
|
||||
|
||||
/**
|
||||
* @ingroup libei-keymap
|
||||
*
|
||||
|
|
@ -1075,8 +1319,7 @@ ei_keymap_ref(struct ei_keymap *keymap);
|
|||
* @ingroup libei-keymap
|
||||
*
|
||||
* Decrease the refcount of this struct by one. When the refcount reaches
|
||||
* zero, the context disconnects from the server and all allocated resources
|
||||
* are released.
|
||||
* zero, all allocated resources for this struct are released.
|
||||
*
|
||||
* @return always NULL
|
||||
*/
|
||||
|
|
@ -1169,6 +1412,17 @@ ei_device_has_capability(struct ei_device *device,
|
|||
struct ei_region *
|
||||
ei_device_get_region(struct ei_device *device, size_t index);
|
||||
|
||||
/**
|
||||
* @ingroup libei-device
|
||||
*
|
||||
* Return the region that contains the given point x/y (in desktop-wide
|
||||
* coordinates) or NULL if the coordinates are outside all regions.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
struct ei_region *
|
||||
ei_device_get_region_at(struct ei_device *device, double x, double y);
|
||||
|
||||
/**
|
||||
* @ingroup libei-region
|
||||
*/
|
||||
|
|
@ -1217,6 +1471,36 @@ ei_region_get_width(struct ei_region *region);
|
|||
uint32_t
|
||||
ei_region_get_height(struct ei_region *region);
|
||||
|
||||
/**
|
||||
* @ingroup libei-region
|
||||
*
|
||||
* Get the unique identifier (representing an external resource) that is
|
||||
* attached to this region, if any. This is only available if the EIS
|
||||
* implementation supports version 2 or later of the ei_device protocol
|
||||
* interface *and* the EIS implementation chooses to attach such an identifier to
|
||||
* the region.
|
||||
*
|
||||
* This ID can be used by the client to identify an external resource that has a
|
||||
* relationship with this region.
|
||||
*
|
||||
* For example the client may receive a data stream with the video
|
||||
* data that this region represents. By attaching the same identifier to the data
|
||||
* stream and this region the EIS implementation can inform the client
|
||||
* that the video data stream and the region represent paired data.
|
||||
* Note that in this example use-case, if the stream is resized
|
||||
* there may be a transition period where two regions have the same identifier -
|
||||
* the old region and the new region with the updated size. A client must be
|
||||
* able to handle the case where to mapping ids are identical.
|
||||
*
|
||||
* libei does not look at or modify the value of the mapping id. Because the ID is
|
||||
* assigned by the caller libei makes no guarantee that the ID is unique
|
||||
* and/or corresponds to any particular format.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
const char *
|
||||
ei_region_get_mapping_id(struct ei_region *region);
|
||||
|
||||
/**
|
||||
* @ingroup libei-region
|
||||
*
|
||||
|
|
@ -1385,6 +1669,10 @@ ei_device_start_emulating(struct ei_device *device, uint32_t sequence);
|
|||
* Notify the EIS implementation that the given device is no longer sending
|
||||
* events. See ei_device_start_emulating() for details.
|
||||
*
|
||||
* If the device is not logically in a neutral state, that state is left
|
||||
* as-is. It is the caller's responsibility to release any buttons, keys, touch
|
||||
* sequences, etc. when stopping emulation to avoid adverse side effects.
|
||||
*
|
||||
* This method is only available on an ei sender context.
|
||||
*/
|
||||
void
|
||||
|
|
@ -1418,8 +1706,8 @@ ei_device_frame(struct ei_device *device, uint64_t time);
|
|||
* This method is only available on an ei sender context.
|
||||
*
|
||||
* @param device The EI device
|
||||
* @param x The x movement in logical pixels
|
||||
* @param y The y movement in logical pixels
|
||||
* @param x The x movement in logical pixels or mm, depending on the device type
|
||||
* @param y The y movement in logical pixels or mm, depending on the device type
|
||||
*/
|
||||
void
|
||||
ei_device_pointer_motion(struct ei_device *device, double x, double y);
|
||||
|
|
@ -1436,8 +1724,8 @@ ei_device_pointer_motion(struct ei_device *device, double x, double y);
|
|||
* This method is only available on an ei sender context.
|
||||
*
|
||||
* @param device The EI device
|
||||
* @param x The x position in logical pixels
|
||||
* @param y The y position in logical pixels
|
||||
* @param x The x position in logical pixels or mm, depending on the device type
|
||||
* @param y The y position in logical pixels or mm, depending on the device type
|
||||
*/
|
||||
void
|
||||
ei_device_pointer_motion_absolute(struct ei_device *device,
|
||||
|
|
@ -1474,8 +1762,8 @@ ei_device_button_button(struct ei_device *device,
|
|||
* This method is only available on an ei sender context.
|
||||
*
|
||||
* @param device The EI device
|
||||
* @param x The x scroll distance in logical pixels
|
||||
* @param y The y scroll distance in logical pixels
|
||||
* @param x The x scroll distance in logical pixels or mm, depending on the device type
|
||||
* @param y The y scroll distance in logical pixels or mm, depending on the device type
|
||||
*
|
||||
* @see ei_device_scroll_discrete
|
||||
*/
|
||||
|
|
@ -1581,7 +1869,7 @@ ei_device_scroll_cancel(struct ei_device *device, bool cancel_x, bool cancel_y);
|
|||
*
|
||||
* Note that this is a keymap-independent key code, equivalent to the scancode
|
||||
* a physical keyboard would produce. To generate a specific key symbol, a
|
||||
* client must look at the keymap returned by ei_device_get_keymap() and
|
||||
* client must look at the keymap returned by ei_device_keyboard_get_keymap() and
|
||||
* generate the appropriate keycodes.
|
||||
*
|
||||
* This method is only available on an ei sender context.
|
||||
|
|
@ -1642,6 +1930,20 @@ ei_touch_motion(struct ei_touch *touch, double x, double y);
|
|||
void
|
||||
ei_touch_up(struct ei_touch *touch);
|
||||
|
||||
/**
|
||||
* @ingroup libei-sender
|
||||
*
|
||||
* Cancel this touch. After this call, the touch event becomes inert and
|
||||
* no longer responds to either ei_touch_down(), ei_touch_motion() or
|
||||
* ei_touch_up() and the caller should call ei_touch_unref().
|
||||
*
|
||||
* This is only available if the EIS implementation supports version 2
|
||||
* or later of the ei_touchscreen protocol interface. Otherwise,
|
||||
* this function is equivalent to calling ei_touch_up().
|
||||
*/
|
||||
void
|
||||
ei_touch_cancel(struct ei_touch *touch);
|
||||
|
||||
/**
|
||||
* @ingroup libei-sender
|
||||
*
|
||||
|
|
@ -1657,8 +1959,7 @@ ei_touch_ref(struct ei_touch *touch);
|
|||
* @ingroup libei-sender
|
||||
*
|
||||
* Decrease the refcount of this struct by one. When the refcount reaches
|
||||
* zero, the context disconnects from the server and all allocated resources
|
||||
* are released.
|
||||
* zero, all allocated resources for this struct are released.
|
||||
*
|
||||
* @return always NULL
|
||||
*/
|
||||
|
|
@ -1668,8 +1969,9 @@ ei_touch_unref(struct ei_touch *touch);
|
|||
/**
|
||||
* @ingroup libei-sender
|
||||
*
|
||||
* Return the custom data pointer for this context. libei will not look at or
|
||||
* modify the pointer. Use ei_touch_set_user_data() to change the user data.
|
||||
* Set a custom data pointer for this context. libei will not look at or
|
||||
* modify the pointer. Use ei_touch_get_user_data() to retrieve a previously
|
||||
* set user data.
|
||||
*/
|
||||
void
|
||||
ei_touch_set_user_data(struct ei_touch *touch, void *user_data);
|
||||
|
|
@ -1677,9 +1979,8 @@ ei_touch_set_user_data(struct ei_touch *touch, void *user_data);
|
|||
/**
|
||||
* @ingroup libei-sender
|
||||
*
|
||||
* Set a custom data pointer for this context. libei will not look at or
|
||||
* modify the pointer. Use ei_touch_get_user_data() to retrieve a previously
|
||||
* set user data.
|
||||
* Return the custom data pointer for this context. libei will not look at or
|
||||
* modify the pointer. Use ei_touch_set_user_data() to change the user data.
|
||||
*/
|
||||
void *
|
||||
ei_touch_get_user_data(struct ei_touch *touch);
|
||||
|
|
@ -1704,6 +2005,18 @@ ei_touch_get_device(struct ei_touch *touch);
|
|||
struct ei_seat *
|
||||
ei_event_get_seat(struct ei_event *event);
|
||||
|
||||
/**
|
||||
* Returns the associated @ref ei_ping struct with this event.
|
||||
*
|
||||
* For events of type other than @ref EI_EVENT_PONG this function
|
||||
* returns NULL.
|
||||
*
|
||||
* This does not increase the refcount of the ei_pong. Use ei_pong_ref()
|
||||
* to keep a reference beyond the immediate scope.
|
||||
*/
|
||||
struct ei_ping *
|
||||
ei_event_pong_get_ping(struct ei_event *event);
|
||||
|
||||
/**
|
||||
* @ingroup libei-receiver
|
||||
*
|
||||
|
|
@ -1720,7 +2033,7 @@ ei_event_emulating_get_sequence(struct ei_event *event);
|
|||
*
|
||||
* For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the
|
||||
* mask of currently logically pressed-down modifiers.
|
||||
* See ei_device_get_keymap() for the corresponding keymap.
|
||||
* See ei_device_keyboard_get_keymap() for the corresponding keymap.
|
||||
*/
|
||||
uint32_t
|
||||
ei_event_keyboard_get_xkb_mods_depressed(struct ei_event *event);
|
||||
|
|
@ -1730,7 +2043,7 @@ ei_event_keyboard_get_xkb_mods_depressed(struct ei_event *event);
|
|||
*
|
||||
* For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the
|
||||
* mask of currently logically latched modifiers.
|
||||
* See ei_device_get_keymap() for the corresponding keymap.
|
||||
* See ei_device_keyboard_get_keymap() for the corresponding keymap.
|
||||
*/
|
||||
uint32_t
|
||||
ei_event_keyboard_get_xkb_mods_latched(struct ei_event *event);
|
||||
|
|
@ -1740,7 +2053,7 @@ ei_event_keyboard_get_xkb_mods_latched(struct ei_event *event);
|
|||
*
|
||||
* For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the
|
||||
* mask of currently logically locked modifiers.
|
||||
* See ei_device_get_keymap() for the corresponding keymap.
|
||||
* See ei_device_keyboard_get_keymap() for the corresponding keymap.
|
||||
*/
|
||||
uint32_t
|
||||
ei_event_keyboard_get_xkb_mods_locked(struct ei_event *event);
|
||||
|
|
@ -1749,8 +2062,21 @@ ei_event_keyboard_get_xkb_mods_locked(struct ei_event *event);
|
|||
* @ingroup libei-receiver
|
||||
*
|
||||
* For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the
|
||||
* logical group state.
|
||||
* See ei_device_get_keymap() for the corresponding keymap.
|
||||
* current effective group.
|
||||
*
|
||||
* This may be passed to xkb_state_update_mask() as either
|
||||
* depressed_layout (effectively pretending the user is holding down some
|
||||
* key for this group at all times) or locked_layout (treating it as a
|
||||
* layout the user has switched to through some mechanism), but never
|
||||
* both at the same time. The other two layout arguments must be set to
|
||||
* zero.
|
||||
*
|
||||
* Note: Because the client only knows the current effective group and
|
||||
* not the combination of state from which it was calculated, any attempt
|
||||
* to predict how future key presses will impact the group state will
|
||||
* necessarily be unreliable.
|
||||
*
|
||||
* See ei_device_keyboard_get_keymap() for the corresponding keymap.
|
||||
*/
|
||||
uint32_t
|
||||
ei_event_keyboard_get_xkb_group(struct ei_event *event);
|
||||
|
|
@ -1917,6 +2243,19 @@ ei_event_touch_get_x(struct ei_event *event);
|
|||
double
|
||||
ei_event_touch_get_y(struct ei_event *event);
|
||||
|
||||
/**
|
||||
* @ingroup libei-receiver
|
||||
*
|
||||
* For an event of type @ref EI_EVENT_TOUCH_UP
|
||||
* return true if the event was cancelled instead of
|
||||
* logically released.
|
||||
*
|
||||
* Support for touch cancellation requires the EIS implementation and client to
|
||||
* support version 2 or later of the ei_touchscreen protocol interface.
|
||||
*/
|
||||
bool
|
||||
ei_event_touch_get_is_cancel(struct ei_event *event);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 button WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 button WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 callback WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
* 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 callback WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -47,12 +47,25 @@
|
|||
DEFINE_TRISTATE(started, finished, connected);
|
||||
DEFINE_UNREF_CLEANUP_FUNC(brei_result);
|
||||
|
||||
struct eis_unsent {
|
||||
struct list node;
|
||||
struct iobuf *buf;
|
||||
};
|
||||
|
||||
static void
|
||||
eis_unsent_free(struct eis_unsent *unsent);
|
||||
|
||||
static void
|
||||
client_drop_seats(struct eis_client *client);
|
||||
|
||||
static void
|
||||
eis_client_destroy(struct eis_client *client)
|
||||
{
|
||||
struct eis_unsent *unsent;
|
||||
list_for_each_safe(unsent, &client->unsent_queue, node) {
|
||||
eis_unsent_free(unsent);
|
||||
}
|
||||
|
||||
client_drop_seats(client);
|
||||
eis_handshake_unref(client->setup);
|
||||
eis_connection_unref(client->connection);
|
||||
|
|
@ -127,7 +140,7 @@ void
|
|||
eis_client_register_object(struct eis_client *client, struct brei_object *object)
|
||||
{
|
||||
struct eis *eis = eis_client_get_context(client);
|
||||
log_debug(eis, "registering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
|
||||
log_debug(eis, "object %#" PRIx64 " registering %s v%u", object->id, object->interface->name, object->version);
|
||||
list_append(&client->proto_objects, &object->link);
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +148,7 @@ void
|
|||
eis_client_unregister_object(struct eis_client *client, struct brei_object *object)
|
||||
{
|
||||
struct eis *eis = eis_client_get_context(client);
|
||||
log_debug(eis, "deregistering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
|
||||
log_debug(eis, "object %#" PRIx64 " deregistering %s v%u", object->id, object->interface->name, object->version);
|
||||
list_remove(&object->link);
|
||||
}
|
||||
|
||||
|
|
@ -151,13 +164,54 @@ eis_client_is_sender(struct eis_client *client)
|
|||
return client->is_sender;
|
||||
}
|
||||
|
||||
static void
|
||||
eis_client_queue_unsent(struct eis_client *client,
|
||||
struct source *source,
|
||||
struct iobuf *buf)
|
||||
{
|
||||
if (list_empty(&client->unsent_queue)) {
|
||||
source_enable_write(source, true);
|
||||
}
|
||||
|
||||
struct eis_unsent *unsent = xalloc(sizeof *unsent);
|
||||
unsent->buf = buf;
|
||||
list_append(&client->unsent_queue, &unsent->node);
|
||||
}
|
||||
|
||||
static void
|
||||
eis_unsent_free(struct eis_unsent *unsent)
|
||||
{
|
||||
list_remove(&unsent->node);
|
||||
iobuf_free(unsent->buf);
|
||||
free(unsent);
|
||||
}
|
||||
|
||||
static int
|
||||
eis_client_unsent_flush(struct eis_client* client)
|
||||
{
|
||||
if (list_empty(&client->unsent_queue))
|
||||
return 0;
|
||||
|
||||
struct source *source = client->source;
|
||||
int fd = source_get_fd(source);
|
||||
struct eis_unsent *unsent;
|
||||
list_for_each_safe(unsent, &client->unsent_queue, node) {
|
||||
int rc = iobuf_send(unsent->buf, fd);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
eis_unsent_free(unsent);
|
||||
}
|
||||
source_enable_write(source, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
eis_client_send_message(struct eis_client *client, const struct brei_object *object,
|
||||
uint32_t opcode, const char *signature, size_t nargs, ...)
|
||||
{
|
||||
struct eis *eis = eis_client_get_context(client);
|
||||
|
||||
log_debug(eis, "sending: object %#" PRIx64 " (%s@v%u:%s(%u)) signature '%s'",
|
||||
log_debug(eis, "object %#" PRIx64 " sending (%s@v%u:%s(%u)) signature '%s'",
|
||||
object->id,
|
||||
object->interface->name,
|
||||
object->interface->version,
|
||||
|
|
@ -182,7 +236,23 @@ eis_client_send_message(struct eis_client *client, const struct brei_object *obj
|
|||
_cleanup_iobuf_ struct iobuf *buf = brei_result_get_data(result);
|
||||
assert(buf);
|
||||
int fd = source_get_fd(client->source);
|
||||
int rc = iobuf_send(buf, fd);
|
||||
int rc = -EPIPE;
|
||||
if (fd != -1) {
|
||||
rc = eis_client_unsent_flush(client);
|
||||
if (rc >= 0)
|
||||
rc = iobuf_send(buf, fd);
|
||||
if (rc == -EAGAIN) {
|
||||
eis_client_queue_unsent(client, client->source, steal(&buf));
|
||||
rc = 0;
|
||||
} else if (rc < 0){
|
||||
if (rc == -EPIPE) {
|
||||
log_debug(eis, "failed to send message: %s", strerror(-rc));
|
||||
} else {
|
||||
log_warn(eis, "failed to send message: %s", strerror(-rc));
|
||||
}
|
||||
source_remove(client->source);
|
||||
}
|
||||
}
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
|
||||
|
|
@ -247,6 +317,7 @@ client_disconnect(struct eis_client *client,
|
|||
case EIS_CLIENT_STATE_CONNECTING:
|
||||
case EIS_CLIENT_STATE_CONNECTED:
|
||||
client_drop_seats(client);
|
||||
eis_connection_remove_pending_callbacks(client->connection);
|
||||
eis_queue_disconnect_event(client);
|
||||
eis_connection_event_disconnected(client->connection,
|
||||
client->last_client_serial,
|
||||
|
|
@ -318,17 +389,16 @@ client_msg_disconnect(struct eis_connection *connection)
|
|||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_sync(struct eis_connection *connection, object_id_t new_id)
|
||||
client_msg_sync(struct eis_connection *connection, object_id_t new_id, uint32_t version)
|
||||
{
|
||||
struct eis_client *client = eis_connection_get_client(connection);
|
||||
|
||||
DISCONNECT_IF_INVALID_ID(client, new_id);
|
||||
DISCONNECT_IF_INVALID_VERSION(client, ei_connection, new_id, version);
|
||||
|
||||
struct eis_callback *callback = eis_callback_new(client, new_id, client->interface_versions.ei_callback);
|
||||
log_debug(eis_client_get_context(client) , "object %#" PRIx64 ": connection sync done", new_id);
|
||||
int rc = eis_callback_event_done(callback, 0);
|
||||
eis_callback_unref(callback);
|
||||
return brei_result_new_from_neg_errno(rc);
|
||||
eis_queue_sync_event(client, new_id, version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct eis_connection_interface intf_state_new = {
|
||||
|
|
@ -390,18 +460,26 @@ client_dispatch(struct source *source, void *userdata)
|
|||
_unref_(eis_client) *client = eis_client_ref(userdata);
|
||||
enum eis_client_state old_state = client->state;
|
||||
|
||||
_unref_(brei_result) *result = brei_dispatch(client->brei, source_get_fd(source),
|
||||
lookup_object, client);
|
||||
if (result) {
|
||||
if (old_state != EIS_CLIENT_STATE_REQUESTED_DISCONNECT ||
|
||||
brei_result_get_reason(result) != BREI_CONNECTION_DISCONNECT_REASON_TRANSPORT)
|
||||
log_warn(eis_client_get_context(client), "Client error: %s",
|
||||
brei_result_get_explanation(result));
|
||||
/* Flush any pending writes, if we have them */
|
||||
int rc = eis_client_unsent_flush(client);
|
||||
if (rc < 0 && rc != -EAGAIN) {
|
||||
log_warn(eis_client_get_context(client),
|
||||
"Error flushing unsent queue: %s", strerror(-rc));
|
||||
eis_client_disconnect(client);
|
||||
} else {
|
||||
_unref_(brei_result) *result = brei_dispatch(client->brei, source_get_fd(source),
|
||||
lookup_object, client);
|
||||
if (result) {
|
||||
if (old_state != EIS_CLIENT_STATE_REQUESTED_DISCONNECT ||
|
||||
brei_result_get_reason(result) != BREI_CONNECTION_DISCONNECT_REASON_TRANSPORT)
|
||||
log_warn(eis_client_get_context(client), "Client error: %s",
|
||||
brei_result_get_explanation(result));
|
||||
|
||||
brei_drain_fd(source_get_fd(source));
|
||||
eis_client_disconnect_with_reason(client,
|
||||
brei_result_get_reason(result),
|
||||
brei_result_get_explanation(result));
|
||||
brei_drain_fd(source_get_fd(source));
|
||||
eis_client_disconnect_with_reason(client,
|
||||
brei_result_get_reason(result),
|
||||
brei_result_get_explanation(result));
|
||||
}
|
||||
}
|
||||
|
||||
static const char *client_states[] = {
|
||||
|
|
@ -436,20 +514,21 @@ eis_client_new(struct eis *eis, int fd)
|
|||
list_init(&client->seats);
|
||||
list_init(&client->seats_pending);
|
||||
list_init(&client->proto_objects);
|
||||
list_init(&client->unsent_queue);
|
||||
|
||||
client->interface_versions = (struct eis_client_interface_versions){
|
||||
.ei_connection = VERSION_V(1),
|
||||
.ei_handshake = VERSION_V(1),
|
||||
.ei_callback = VERSION_V(1),
|
||||
.ei_pingpong = VERSION_V(1),
|
||||
.ei_seat = VERSION_V(1),
|
||||
.ei_device = VERSION_V(1),
|
||||
.ei_seat = VERSION_V(2),
|
||||
.ei_device = flag_is_set(eis->flags, EIS_FLAG_DEVICE_READY) ? VERSION_V(3) : VERSION_V(2),
|
||||
.ei_pointer = VERSION_V(1),
|
||||
.ei_pointer_absolute = VERSION_V(1),
|
||||
.ei_scroll = VERSION_V(1),
|
||||
.ei_button = VERSION_V(1),
|
||||
.ei_keyboard = VERSION_V(1),
|
||||
.ei_touchscreen = VERSION_V(1),
|
||||
.ei_touchscreen = VERSION_V(2),
|
||||
};
|
||||
struct source *s = source_new(fd, client_dispatch, client);
|
||||
int rc = sink_add_source(eis->sink, s);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ struct eis_client {
|
|||
struct brei_context *brei;
|
||||
struct eis_connection *connection;
|
||||
|
||||
struct list unsent_queue;
|
||||
|
||||
struct list proto_objects; /* struct brei_objects list */
|
||||
object_id_t next_object_id;
|
||||
object_id_t last_client_object_id;
|
||||
|
|
|
|||
|
|
@ -43,12 +43,8 @@ eis_connection_destroy(struct eis_connection *connection)
|
|||
struct eis_client *client = eis_connection_get_client(connection);
|
||||
eis_client_unregister_object(client, &connection->proto_object);
|
||||
|
||||
struct eis_pingpong *cb;
|
||||
list_for_each_safe(cb, &connection->pending_pingpongs, link) {
|
||||
list_remove(&cb->link);
|
||||
free(eis_pingpong_get_user_data(cb));
|
||||
eis_pingpong_unref(cb);
|
||||
}
|
||||
/* Should be a noop */
|
||||
eis_connection_remove_pending_callbacks(connection);
|
||||
}
|
||||
|
||||
OBJECT_IMPLEMENT_REF(eis_connection);
|
||||
|
|
@ -108,20 +104,71 @@ eis_connection_new(struct eis_client *client)
|
|||
return connection; /* ref owned by caller */
|
||||
}
|
||||
|
||||
struct pingpong_user_data {
|
||||
eis_connection_ping_callback_t cb;
|
||||
void *user_data;
|
||||
};
|
||||
void
|
||||
eis_connection_remove_pending_callbacks(struct eis_connection *connection)
|
||||
{
|
||||
struct eis_callback *cb;
|
||||
list_for_each_safe(cb, &connection->pending_pingpongs, link) {
|
||||
list_remove(&cb->link);
|
||||
eis_connection_ping_callback_unref(cb->user_data);
|
||||
eis_callback_unref(cb);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ping_pingpong(struct eis_pingpong *pingpong, void *pingpong_data,
|
||||
uint64_t proto_data)
|
||||
eis_connection_ping_callback_destroy(struct eis_connection_ping_callback *callback)
|
||||
{
|
||||
struct eis_connection *connection = pingpong_data;
|
||||
if (callback->destroy)
|
||||
callback->destroy(callback);
|
||||
}
|
||||
|
||||
_cleanup_free_ struct pingpong_user_data *data = eis_pingpong_get_user_data(pingpong);
|
||||
if (data->cb)
|
||||
data->cb(connection, data->user_data);
|
||||
static
|
||||
OBJECT_IMPLEMENT_CREATE(eis_connection_ping_callback);
|
||||
OBJECT_IMPLEMENT_REF(eis_connection_ping_callback);
|
||||
OBJECT_IMPLEMENT_UNREF(eis_connection_ping_callback);
|
||||
OBJECT_IMPLEMENT_GETTER(eis_connection_ping_callback, user_data, void *);
|
||||
static
|
||||
OBJECT_IMPLEMENT_PARENT(eis_connection_ping_callback, eis_connection);
|
||||
|
||||
struct eis_connection *
|
||||
eis_connection_ping_callback_get_connection(struct eis_connection_ping_callback *callback)
|
||||
{
|
||||
return eis_connection_ping_callback_parent(callback);
|
||||
}
|
||||
|
||||
struct eis_client *
|
||||
eis_connection_ping_callback_get_client(struct eis_connection_ping_callback *callback)
|
||||
{
|
||||
struct eis_connection *connection = eis_connection_ping_callback_get_connection(callback);
|
||||
return eis_connection_get_client(connection);
|
||||
}
|
||||
|
||||
struct eis *
|
||||
eis_connection_ping_callback_get_context(struct eis_connection_ping_callback *callback)
|
||||
{
|
||||
struct eis_connection *connection = eis_connection_ping_callback_get_connection(callback);
|
||||
return eis_connection_get_context(connection);
|
||||
}
|
||||
|
||||
struct eis_connection_ping_callback *
|
||||
eis_connection_ping_callback_new(struct eis_connection *connection,
|
||||
eis_connection_ping_callback_done_t done,
|
||||
eis_connection_ping_callback_destroy_t destroy,
|
||||
void *user_data)
|
||||
{
|
||||
struct eis_connection_ping_callback *callback = eis_connection_ping_callback_create(&connection->object);
|
||||
callback->done = done;
|
||||
callback->destroy = destroy;
|
||||
callback->user_data = user_data;
|
||||
return callback;
|
||||
}
|
||||
|
||||
static void
|
||||
pingpong_callback(struct eis_pingpong *pingpong, void *pingpong_data, uint64_t proto_data)
|
||||
{
|
||||
_unref_(eis_connection_ping_callback) *data = eis_pingpong_get_user_data(pingpong);
|
||||
if (data->done)
|
||||
data->done(data);
|
||||
|
||||
/* remove from pending callbacks */
|
||||
list_remove(&pingpong->link);
|
||||
|
|
@ -129,21 +176,18 @@ ping_pingpong(struct eis_pingpong *pingpong, void *pingpong_data,
|
|||
}
|
||||
|
||||
void
|
||||
eis_connection_ping(struct eis_connection *connection, eis_connection_ping_callback_t cb,
|
||||
void *user_data)
|
||||
eis_connection_ping(struct eis_connection *connection,
|
||||
struct eis_connection_ping_callback *cb)
|
||||
{
|
||||
struct eis_client *client = eis_connection_get_client(connection);
|
||||
|
||||
/* This is double-wrapped because we only use this for debugging purposes for
|
||||
* now. The actual callback calls sync_callback with our connection,
|
||||
* now. The actual callback calls pingpong_callback with our connection,
|
||||
* then we extract the user_data on the object and call into the
|
||||
* cb supplied to this function.
|
||||
*/
|
||||
struct eis_pingpong *pingpong = eis_pingpong_new(client, ping_pingpong, connection);
|
||||
struct pingpong_user_data *data = xalloc(sizeof *data);
|
||||
data->cb = cb;
|
||||
data->user_data = user_data;
|
||||
eis_pingpong_set_user_data(pingpong, data);
|
||||
struct eis_pingpong *pingpong = eis_pingpong_new(client, pingpong_callback, connection);
|
||||
eis_pingpong_set_user_data(pingpong, eis_connection_ping_callback_ref(cb));
|
||||
list_append(&connection->pending_pingpongs, &pingpong->link);
|
||||
|
||||
eis_connection_event_ping(connection, eis_pingpong_get_id(pingpong),
|
||||
|
|
|
|||
|
|
@ -25,11 +25,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "util-mem.h"
|
||||
#include "util-object.h"
|
||||
#include "brei-shared.h"
|
||||
|
||||
struct eis;
|
||||
struct eis_client;
|
||||
struct eis_connection_ping_callback;
|
||||
|
||||
/* This is a protocol-only object, not exposed in the API */
|
||||
struct eis_connection {
|
||||
|
|
@ -51,6 +53,8 @@ OBJECT_DECLARE_UNREF(eis_connection);
|
|||
struct eis_connection *
|
||||
eis_connection_new(struct eis_client *client);
|
||||
|
||||
void
|
||||
eis_connection_remove_pending_callbacks(struct eis_connection *connection);
|
||||
|
||||
/**
|
||||
* Called when the ei_callback.done request is received after
|
||||
|
|
@ -59,7 +63,40 @@ eis_connection_new(struct eis_client *client);
|
|||
typedef void (*eis_connection_ping_callback_t)(struct eis_connection *connection,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* Called when the ei_callback.done event is received after
|
||||
* an eis_connection_ping() request.
|
||||
*/
|
||||
typedef void (*eis_connection_ping_callback_done_t)(struct eis_connection_ping_callback *callback);
|
||||
|
||||
/**
|
||||
* Called for each registered callback when the last reference to it is
|
||||
* destroyed. This should be used to clean up user_data, if need be.
|
||||
*
|
||||
* This function is always called, even if disconnected and the done() function is never called.
|
||||
*/
|
||||
typedef void (*eis_connection_ping_callback_destroy_t)(struct eis_connection_ping_callback *callback);
|
||||
|
||||
struct eis_connection_ping_callback {
|
||||
struct object object; /* parent is struct eis */
|
||||
eis_connection_ping_callback_done_t done;
|
||||
eis_connection_ping_callback_destroy_t destroy;
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
struct eis_connection_ping_callback *
|
||||
eis_connection_ping_callback_new(struct eis_connection *connection,
|
||||
eis_connection_ping_callback_done_t done,
|
||||
eis_connection_ping_callback_destroy_t destroy,
|
||||
void *user_data);
|
||||
|
||||
OBJECT_DECLARE_REF(eis_connection_ping_callback);
|
||||
OBJECT_DECLARE_UNREF(eis_connection_ping_callback);
|
||||
OBJECT_DECLARE_GETTER(eis_connection_ping_callback, connection, struct eis_connection *);
|
||||
OBJECT_DECLARE_GETTER(eis_connection_ping_callback, client, struct eis_client *);
|
||||
OBJECT_DECLARE_GETTER(eis_connection_ping_callback, context, struct eis *);
|
||||
OBJECT_DECLARE_GETTER(eis_connection_ping_callback, user_data, void*);
|
||||
DEFINE_UNREF_CLEANUP_FUNC(eis_connection_ping_callback);
|
||||
|
||||
void
|
||||
eis_connection_ping(struct eis_connection *connection,
|
||||
eis_connection_ping_callback_t callback,
|
||||
void *user_data);
|
||||
eis_connection_ping(struct eis_connection *connection, struct eis_connection_ping_callback *callback);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util-macros.h"
|
||||
#include "util-bits.h"
|
||||
|
|
@ -92,7 +93,7 @@ eis_device_new_keymap(struct eis_device *device,
|
|||
return keymap;
|
||||
}
|
||||
|
||||
struct eis *
|
||||
_public_ struct eis *
|
||||
eis_device_get_context(struct eis_device *device)
|
||||
{
|
||||
return eis_client_get_context(eis_device_get_client(device));
|
||||
|
|
@ -193,6 +194,18 @@ eis_device_get_region(struct eis_device *device, size_t index)
|
|||
return list_nth_entry(struct eis_region, &device->regions, link, index);
|
||||
}
|
||||
|
||||
_public_ struct eis_region *
|
||||
eis_device_get_region_at(struct eis_device *device, double x, double y)
|
||||
{
|
||||
struct eis_region *r;
|
||||
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (eis_region_contains(r, x, y))
|
||||
return r;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
_public_ struct eis_client *
|
||||
eis_device_get_client(struct eis_device *device)
|
||||
{
|
||||
|
|
@ -204,6 +217,9 @@ eis_device_in_region(struct eis_device *device, double x, double y)
|
|||
{
|
||||
struct eis_region *r;
|
||||
|
||||
if (list_empty(&device->regions))
|
||||
return true;
|
||||
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (eis_region_contains(r, x, y))
|
||||
return true;
|
||||
|
|
@ -242,9 +258,10 @@ client_msg_start_emulating(struct eis_device *device, uint32_t serial, uint32_t
|
|||
case EIS_DEVICE_STATE_DEAD:
|
||||
case EIS_DEVICE_STATE_CLOSED_BY_CLIENT:
|
||||
case EIS_DEVICE_STATE_NEW:
|
||||
case EIS_DEVICE_STATE_AWAITING_READY:
|
||||
case EIS_DEVICE_STATE_EMULATING:
|
||||
result = brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Invalid device state %ud for a start_emulating event", device->state);
|
||||
"Invalid device state %u for a start_emulating event", device->state);
|
||||
break;
|
||||
case EIS_DEVICE_STATE_RESUMED:
|
||||
eis_queue_device_start_emulating_event(device, sequence);
|
||||
|
|
@ -271,9 +288,10 @@ client_msg_stop_emulating(struct eis_device *device, uint32_t serial)
|
|||
case EIS_DEVICE_STATE_DEAD:
|
||||
case EIS_DEVICE_STATE_CLOSED_BY_CLIENT:
|
||||
case EIS_DEVICE_STATE_NEW:
|
||||
case EIS_DEVICE_STATE_AWAITING_READY:
|
||||
case EIS_DEVICE_STATE_RESUMED:
|
||||
result = brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Invalid device state %ud for a stop_emulating event", device->state);
|
||||
"Invalid device state %u for a stop_emulating event", device->state);
|
||||
break;
|
||||
case EIS_DEVICE_STATE_EMULATING:
|
||||
eis_queue_device_stop_emulating_event(device);
|
||||
|
|
@ -306,13 +324,14 @@ maybe_error_on_device_state(struct eis_device *device, const char *event_type)
|
|||
case EIS_DEVICE_STATE_EMULATING:
|
||||
return NULL;
|
||||
case EIS_DEVICE_STATE_NEW:
|
||||
case EIS_DEVICE_STATE_AWAITING_READY:
|
||||
case EIS_DEVICE_STATE_CLOSED_BY_CLIENT:
|
||||
case EIS_DEVICE_STATE_DEAD:
|
||||
break;
|
||||
}
|
||||
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Invalid device state %ud for a %s event", device->state, event_type);
|
||||
"Invalid device state %u for a %s event", device->state, event_type);
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
|
|
@ -330,12 +349,25 @@ client_msg_frame(struct eis_device *device, uint32_t serial, uint64_t time)
|
|||
return maybe_error_on_device_state(device, "frame");
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_ready(struct eis_device *device)
|
||||
{
|
||||
if (device->state != EIS_DEVICE_STATE_AWAITING_READY)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Invalid device state %u for a ready event", device->state);
|
||||
|
||||
device->state = EIS_DEVICE_STATE_PAUSED;
|
||||
eis_queue_device_ready_event(device);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct eis_device_interface interface = {
|
||||
.release = client_msg_release,
|
||||
.start_emulating = client_msg_start_emulating,
|
||||
.stop_emulating = client_msg_stop_emulating,
|
||||
.frame = client_msg_frame,
|
||||
.ready = client_msg_ready,
|
||||
};
|
||||
|
||||
const struct eis_device_interface *
|
||||
|
|
@ -397,6 +429,13 @@ client_msg_button(struct eis_button *button, uint32_t btn, uint32_t state)
|
|||
"Button event for non-button device");
|
||||
}
|
||||
|
||||
if (btn >= KEY_CNT)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Button event for invalid button %x (KEY_CNT is %#x)", btn, KEY_CNT);
|
||||
|
||||
if (!eis_device_update_key_button_state(device, btn, state))
|
||||
return NULL;
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING) {
|
||||
eis_queue_pointer_button_event(device, btn, !!state);
|
||||
return NULL;
|
||||
|
|
@ -567,6 +606,13 @@ client_msg_keyboard_key(struct eis_keyboard *keyboard, uint32_t key, uint32_t st
|
|||
"Key event for non-keyboard device");
|
||||
}
|
||||
|
||||
if (key >= KEY_CNT)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Key event for invalid key %x (KEY_CNT is %#x)", key, KEY_CNT);
|
||||
|
||||
if (!eis_device_update_key_button_state(device, key, state))
|
||||
return NULL;
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING) {
|
||||
eis_queue_keyboard_key_event(device, key, !!state);
|
||||
return NULL;
|
||||
|
|
@ -596,6 +642,35 @@ eis_device_get_keyboard_interface(struct eis_device *device)
|
|||
return &keyboard_interface;
|
||||
}
|
||||
|
||||
/* Returns true and the position of the touch with the given ID, or
|
||||
* false and the first position that is available
|
||||
*/
|
||||
static bool
|
||||
find_touch(struct eis_device *device, uint32_t touchid, size_t *index)
|
||||
{
|
||||
ssize_t first_available = -1;
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) {
|
||||
if (device->touch_state.down[i] != UINT64_MAX) {
|
||||
if (device->touch_state.down[i] == touchid) {
|
||||
if (index)
|
||||
*index = i;
|
||||
return true;
|
||||
}
|
||||
} else if (first_available < 0) {
|
||||
first_available = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (index) {
|
||||
if (first_available < 0)
|
||||
*index = EIS_MAX_TOUCHES;
|
||||
else
|
||||
*index = (size_t)first_available;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_touch_down(struct eis_touchscreen *touchscreen,
|
||||
uint32_t touchid, float x, float y)
|
||||
|
|
@ -610,6 +685,17 @@ client_msg_touch_down(struct eis_touchscreen *touchscreen,
|
|||
}
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING) {
|
||||
size_t first_available;
|
||||
if (find_touch(device, touchid, &first_available)) {
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Touch down event for duplicated touch ID");
|
||||
}
|
||||
|
||||
if (first_available >= EIS_MAX_TOUCHES)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_ERROR,
|
||||
"Too many simultaneous touch events");
|
||||
|
||||
device->touch_state.down[first_available] = touchid;
|
||||
eis_queue_touch_down_event(device, touchid, x, y);
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -631,13 +717,26 @@ client_msg_touch_motion(struct eis_touchscreen *touchscreen,
|
|||
}
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING) {
|
||||
eis_queue_touch_motion_event(device, touchid, x, y);
|
||||
/* Silently ignore motion for non-existing touches */
|
||||
if (find_touch(device, touchid, NULL))
|
||||
eis_queue_touch_motion_event(device, touchid, x, y);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return maybe_error_on_device_state(device, "touch motion");
|
||||
}
|
||||
|
||||
static bool
|
||||
release_touch(struct eis_device *device, uint32_t touchid)
|
||||
{
|
||||
size_t index;
|
||||
bool rc = find_touch(device, touchid, &index);
|
||||
if (rc)
|
||||
device->touch_state.down[index] = UINT64_MAX;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_touch_up(struct eis_touchscreen *touchscreen, uint32_t touchid)
|
||||
{
|
||||
|
|
@ -650,14 +749,46 @@ client_msg_touch_up(struct eis_touchscreen *touchscreen, uint32_t touchid)
|
|||
"Touch up event for non-touch device");
|
||||
}
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING) {
|
||||
eis_queue_touch_up_event(device, touchid);
|
||||
/* End the touch locally even if we're not emulating, but
|
||||
* silently ignore touch end/cancel for non-existing touches */
|
||||
if (release_touch(device, touchid)) {
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING)
|
||||
eis_queue_touch_up_event(device, touchid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return maybe_error_on_device_state(device, "touch up");
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_touch_cancel(struct eis_touchscreen *touchscreen, uint32_t touchid)
|
||||
{
|
||||
struct eis_device *device = eis_touchscreen_get_device(touchscreen);
|
||||
|
||||
DISCONNECT_IF_RECEIVER_CONTEXT(device);
|
||||
|
||||
if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) {
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Touch cancel event for non-touch device");
|
||||
}
|
||||
|
||||
struct eis_client *client = eis_device_get_client(device);
|
||||
if (client->interface_versions.ei_touchscreen < EIS_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) {
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Touch cancel event for touchscreen version v1");
|
||||
}
|
||||
|
||||
/* End the touch locally even if we're not emulating, but
|
||||
* silently ignore touch end/cancel for non-existing touches */
|
||||
if (release_touch(device, touchid)) {
|
||||
if (device->state == EIS_DEVICE_STATE_EMULATING)
|
||||
eis_queue_touch_cancel_event(device, touchid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return maybe_error_on_device_state(device, "touch cancel");
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_touchscreen_release(struct eis_touchscreen *touchscreen)
|
||||
{
|
||||
|
|
@ -673,6 +804,7 @@ static const struct eis_touchscreen_interface touchscreen_interface = {
|
|||
.down = client_msg_touch_down,
|
||||
.motion = client_msg_touch_motion,
|
||||
.up = client_msg_touch_up,
|
||||
.cancel = client_msg_touch_cancel,
|
||||
};
|
||||
|
||||
const struct eis_touchscreen_interface *
|
||||
|
|
@ -704,6 +836,10 @@ eis_seat_new_device(struct eis_seat *seat)
|
|||
|
||||
list_append(&seat->devices, &device->link);
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) {
|
||||
device->touch_state.down[i] = UINT64_MAX;
|
||||
}
|
||||
|
||||
return eis_device_ref(device);
|
||||
}
|
||||
|
||||
|
|
@ -760,7 +896,7 @@ _public_ void
|
|||
eis_device_configure_size(struct eis_device *device, uint32_t width, uint32_t height)
|
||||
{
|
||||
if (device->type != EIS_DEVICE_TYPE_PHYSICAL) {
|
||||
log_bug_client(eis_device_get_context(device), "Device type physical requird for size");
|
||||
log_bug_client(eis_device_get_context(device), "Device type physical required for size");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -788,7 +924,7 @@ eis_device_add(struct eis_device *device)
|
|||
"%s: adding device without capabilities", __func__);
|
||||
}
|
||||
|
||||
device->state = EIS_DEVICE_STATE_PAUSED;
|
||||
device->state = EIS_DEVICE_STATE_AWAITING_READY;
|
||||
eis_client_register_object(client, &device->proto_object);
|
||||
eis_seat_event_device(seat, device->proto_object.id, device->proto_object.version);
|
||||
int rc = eis_device_event_name(device, device->name);
|
||||
|
|
@ -807,6 +943,17 @@ eis_device_add(struct eis_device *device)
|
|||
if (device->type == EIS_DEVICE_TYPE_VIRTUAL) {
|
||||
struct eis_region *r;
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (r->mapping_id) {
|
||||
if (client->interface_versions.ei_device >= EIS_DEVICE_EVENT_REGION_MAPPING_ID_SINCE_VERSION) {
|
||||
rc = eis_device_event_region_mapping_id(device, r->mapping_id);
|
||||
if (rc < 0)
|
||||
goto out;
|
||||
} else {
|
||||
/* If our client doesn't support mapping_id, drop it */
|
||||
free(r->mapping_id);
|
||||
r->mapping_id = NULL;
|
||||
}
|
||||
}
|
||||
rc = eis_device_event_region(device, r->x, r->y, r->width, r->height, r->physical_scale);
|
||||
if (rc < 0)
|
||||
goto out;
|
||||
|
|
@ -868,9 +1015,19 @@ eis_device_add(struct eis_device *device)
|
|||
}
|
||||
|
||||
rc = eis_device_event_done(device);
|
||||
if (rc < 0)
|
||||
goto out;
|
||||
|
||||
struct eis *eis = eis_device_get_context(device);
|
||||
if (client->interface_versions.ei_device < EIS_DEVICE_REQUEST_READY_SINCE_VERSION) {
|
||||
device->state = EIS_DEVICE_STATE_PAUSED;
|
||||
if (flag_is_set(eis->flags, EIS_FLAG_DEVICE_READY))
|
||||
eis_queue_device_ready_event(device);
|
||||
}
|
||||
|
||||
out:
|
||||
if (rc < 0) {
|
||||
log_error(eis_client_get_context(client), "Failed to add device, disconnecting client\n");
|
||||
log_error(eis_client_get_context(client), "Failed to add device, disconnecting client");
|
||||
eis_client_disconnect(client);
|
||||
}
|
||||
return;
|
||||
|
|
@ -916,6 +1073,12 @@ eis_device_remove(struct eis_device *device)
|
|||
if (device->state != EIS_DEVICE_STATE_NEW)
|
||||
eis_device_event_destroyed(device, eis_client_get_next_serial(client));
|
||||
|
||||
struct eis_event *event;
|
||||
list_for_each_safe(event, &device->pending_event_queue, link) {
|
||||
list_remove(&event->link);
|
||||
eis_event_unref(event);
|
||||
}
|
||||
|
||||
device->state = EIS_DEVICE_STATE_DEAD;
|
||||
eis_client_unregister_object(client, &device->proto_object);
|
||||
list_remove(&device->link);
|
||||
|
|
@ -1018,12 +1181,8 @@ eis_device_pointer_motion_absolute(struct eis_device *device,
|
|||
if (device->state != EIS_DEVICE_STATE_EMULATING)
|
||||
return;
|
||||
|
||||
struct eis_region *r;
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (!eis_region_contains(r, x, y)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!eis_device_in_region(device, x, y))
|
||||
return;
|
||||
|
||||
device->send_frame_event = true;
|
||||
|
||||
|
|
@ -1158,6 +1317,11 @@ eis_device_scroll_discrete(struct eis_device *device, int32_t x, int32_t y)
|
|||
if (device->state != EIS_DEVICE_STATE_EMULATING)
|
||||
return;
|
||||
|
||||
if (abs(x) == 1 || abs(y) == 1) {
|
||||
log_bug_client(eis_device_get_context(device),
|
||||
"%s: suspicious discrete event value 1, did you mean 120?", __func__);
|
||||
}
|
||||
|
||||
eis_device_resume_scrolling(device, x, y);
|
||||
|
||||
device->send_frame_event = true;
|
||||
|
|
@ -1235,14 +1399,11 @@ eis_touch_down(struct eis_touch *touch, double x, double y)
|
|||
return;
|
||||
}
|
||||
|
||||
struct eis_region *r;
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (!eis_region_contains(r, x, y)) {
|
||||
log_bug_client(eis_device_get_context(device),
|
||||
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
|
||||
touch->state = TOUCH_IS_UP;
|
||||
return;
|
||||
}
|
||||
if (!eis_device_in_region(device, x, y)) {
|
||||
log_bug_client(eis_device_get_context(device),
|
||||
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
|
||||
touch->state = TOUCH_IS_UP;
|
||||
return;
|
||||
}
|
||||
|
||||
touch->state = TOUCH_IS_DOWN;
|
||||
|
|
@ -1258,14 +1419,11 @@ eis_touch_motion(struct eis_touch *touch, double x, double y)
|
|||
return;
|
||||
|
||||
struct eis_device *device = eis_touch_get_device(touch);
|
||||
struct eis_region *r;
|
||||
list_for_each(r, &device->regions, link) {
|
||||
if (!eis_region_contains(r, x, y)) {
|
||||
log_bug_client(eis_device_get_context(device),
|
||||
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
|
||||
eis_touch_up(touch);
|
||||
return;
|
||||
}
|
||||
if (!eis_device_in_region(device, x, y)) {
|
||||
log_bug_client(eis_device_get_context(device),
|
||||
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
|
||||
eis_touch_up(touch);
|
||||
return;
|
||||
}
|
||||
|
||||
device->send_frame_event = true;
|
||||
|
|
@ -1290,6 +1448,27 @@ eis_touch_up(struct eis_touch *touch)
|
|||
eis_touchscreen_event_up(device->touchscreen, touch->tracking_id);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
eis_touch_cancel(struct eis_touch *touch)
|
||||
{
|
||||
struct eis_device *device = eis_touch_get_device(touch);
|
||||
|
||||
if (touch->state != TOUCH_IS_DOWN) {
|
||||
log_bug_client(eis_device_get_context(device),
|
||||
"%s: touch %u is not currently down", __func__, touch->tracking_id);
|
||||
return;
|
||||
}
|
||||
|
||||
touch->state = TOUCH_IS_UP;
|
||||
device->send_frame_event = true;
|
||||
|
||||
struct eis_client *client = eis_device_get_client(device);
|
||||
if (client->interface_versions.ei_touchscreen >= EIS_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION)
|
||||
eis_touchscreen_event_cancel(device->touchscreen, touch->tracking_id);
|
||||
else
|
||||
eis_touchscreen_event_up(device->touchscreen, touch->tracking_id);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
eis_device_frame(struct eis_device *device, uint64_t time)
|
||||
{
|
||||
|
|
@ -1319,6 +1498,9 @@ eis_device_closed_by_client(struct eis_device *device)
|
|||
if (!eis_client_is_sender(eis_device_get_client(device)))
|
||||
eis_queue_device_stop_emulating_event(device);
|
||||
_fallthrough_;
|
||||
case EIS_DEVICE_STATE_AWAITING_READY:
|
||||
eis_queue_device_ready_event(device);
|
||||
_fallthrough_;
|
||||
case EIS_DEVICE_STATE_NEW:
|
||||
case EIS_DEVICE_STATE_PAUSED:
|
||||
case EIS_DEVICE_STATE_RESUMED:
|
||||
|
|
@ -1333,11 +1515,22 @@ eis_device_pause(struct eis_device *device)
|
|||
{
|
||||
struct eis_client *client = eis_device_get_client(device);
|
||||
|
||||
if (device->state != EIS_DEVICE_STATE_RESUMED)
|
||||
return;
|
||||
switch (device->state) {
|
||||
case EIS_DEVICE_STATE_RESUMED:
|
||||
case EIS_DEVICE_STATE_EMULATING:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
device->state = EIS_DEVICE_STATE_PAUSED;
|
||||
eis_device_event_paused(device, eis_client_get_next_serial(client));
|
||||
|
||||
memset(device->key_button_state.down, 0, sizeof(device->key_button_state.down));
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) {
|
||||
device->touch_state.down[i] = UINT64_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
_public_ void
|
||||
|
|
@ -1345,6 +1538,12 @@ eis_device_resume(struct eis_device *device)
|
|||
{
|
||||
struct eis_client *client = eis_device_get_client(device);
|
||||
|
||||
if (device->state == EIS_DEVICE_STATE_AWAITING_READY) {
|
||||
log_bug_client(eis_client_get_context(client),
|
||||
"Attempting to resume a device before DEVICE_READY");
|
||||
return;
|
||||
}
|
||||
|
||||
if (device->state != EIS_DEVICE_STATE_PAUSED)
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,19 @@
|
|||
|
||||
#include "libeis.h"
|
||||
|
||||
#include "util-bits.h"
|
||||
#include "util-object.h"
|
||||
#include "util-list.h"
|
||||
#include "brei-shared.h"
|
||||
|
||||
#define KEY_MAX 0x2ffU
|
||||
#define KEY_CNT (KEY_MAX + 1)
|
||||
|
||||
#define EIS_MAX_TOUCHES 16
|
||||
|
||||
enum eis_device_state {
|
||||
EIS_DEVICE_STATE_NEW,
|
||||
EIS_DEVICE_STATE_AWAITING_READY,
|
||||
EIS_DEVICE_STATE_PAUSED,
|
||||
EIS_DEVICE_STATE_RESUMED,
|
||||
EIS_DEVICE_STATE_EMULATING,
|
||||
|
|
@ -74,6 +81,13 @@ struct eis_device {
|
|||
bool x_is_cancelled, y_is_cancelled;
|
||||
} scroll_state;
|
||||
|
||||
struct {
|
||||
unsigned char down[NCHARS(KEY_CNT)];
|
||||
} key_button_state;
|
||||
|
||||
struct {
|
||||
uint64_t down[EIS_MAX_TOUCHES]; /* touch id */
|
||||
} touch_state;
|
||||
};
|
||||
|
||||
struct eis_touch {
|
||||
|
|
@ -108,7 +122,6 @@ struct eis_xkb_modifiers {
|
|||
};
|
||||
|
||||
OBJECT_DECLARE_GETTER(eis_device, id, object_id_t);
|
||||
OBJECT_DECLARE_GETTER(eis_device, context, struct eis *);
|
||||
OBJECT_DECLARE_GETTER(eis_device, proto_object, const struct brei_object *);
|
||||
OBJECT_DECLARE_GETTER(eis_device, interface, const struct eis_device_interface *);
|
||||
OBJECT_DECLARE_GETTER(eis_device, pointer_interface, const struct eis_pointer_interface *);
|
||||
|
|
@ -118,6 +131,21 @@ OBJECT_DECLARE_GETTER(eis_device, button_interface, const struct eis_button_inte
|
|||
OBJECT_DECLARE_GETTER(eis_device, keyboard_interface, const struct eis_keyboard_interface *);
|
||||
OBJECT_DECLARE_GETTER(eis_device, touchscreen_interface, const struct eis_touchscreen_interface *);
|
||||
|
||||
static inline bool
|
||||
eis_device_update_key_button_state(struct eis_device *device, uint32_t key_btn, uint32_t state)
|
||||
{
|
||||
if (state) {
|
||||
if (bit_is_set(device->key_button_state.down, key_btn))
|
||||
return false;
|
||||
set_bit(device->key_button_state.down, key_btn);
|
||||
} else {
|
||||
if (!bit_is_set(device->key_button_state.down, key_btn))
|
||||
return false;
|
||||
clear_bit(device->key_button_state.down, key_btn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
eis_device_set_client_keymap(struct eis_device *device,
|
||||
enum eis_keymap_type type,
|
||||
|
|
|
|||
|
|
@ -41,9 +41,11 @@ eis_event_destroy(struct eis_event *event)
|
|||
case EIS_EVENT_CLIENT_CONNECT:
|
||||
case EIS_EVENT_CLIENT_DISCONNECT:
|
||||
case EIS_EVENT_SEAT_BIND:
|
||||
case EIS_EVENT_SEAT_DEVICE_REQUESTED:
|
||||
case EIS_EVENT_DEVICE_CLOSED:
|
||||
case EIS_EVENT_DEVICE_START_EMULATING:
|
||||
case EIS_EVENT_DEVICE_STOP_EMULATING:
|
||||
case EIS_EVENT_DEVICE_READY:
|
||||
case EIS_EVENT_BUTTON_BUTTON:
|
||||
case EIS_EVENT_POINTER_MOTION:
|
||||
case EIS_EVENT_POINTER_MOTION_ABSOLUTE:
|
||||
|
|
@ -58,6 +60,15 @@ eis_event_destroy(struct eis_event *event)
|
|||
case EIS_EVENT_FRAME:
|
||||
handled = true;
|
||||
break;
|
||||
case EIS_EVENT_PONG:
|
||||
eis_ping_unref(event->pong.ping);
|
||||
handled = true;
|
||||
break;
|
||||
case EIS_EVENT_SYNC:
|
||||
eis_sync_event_send_done(event);
|
||||
eis_callback_unref(event->sync.callback);
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
|
|
@ -109,9 +120,8 @@ eis_event_new_for_device(struct eis_device *device)
|
|||
return e;
|
||||
}
|
||||
|
||||
/* this one is not public */
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_REF(eis_event);
|
||||
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_event);
|
||||
_public_
|
||||
|
|
@ -157,7 +167,8 @@ check_event_type(struct eis_event *event,
|
|||
|
||||
if (!rc)
|
||||
log_bug_client(eis_event_get_context(event),
|
||||
"Invalid event type %u passed to %s()",
|
||||
"Invalid event type %s (%u) passed to %s()",
|
||||
eis_event_type_to_string(type),
|
||||
type, function_name);
|
||||
|
||||
return rc;
|
||||
|
|
@ -187,10 +198,21 @@ eis_event_get_time(struct eis_event *event)
|
|||
return event->timestamp;
|
||||
}
|
||||
|
||||
_public_ struct eis_ping *
|
||||
eis_event_pong_get_ping(struct eis_event *event)
|
||||
{
|
||||
require_event_type(event, NULL, EIS_EVENT_PONG);
|
||||
|
||||
return event->pong.ping;
|
||||
}
|
||||
|
||||
_public_ bool
|
||||
eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capability cap)
|
||||
{
|
||||
require_event_type(event, false, EIS_EVENT_SEAT_BIND);
|
||||
require_event_type(event,
|
||||
false,
|
||||
EIS_EVENT_SEAT_BIND,
|
||||
EIS_EVENT_SEAT_DEVICE_REQUESTED);
|
||||
|
||||
switch (cap) {
|
||||
case EIS_DEVICE_CAP_POINTER:
|
||||
|
|
@ -404,3 +426,11 @@ eis_event_touch_get_y(struct eis_event *event)
|
|||
|
||||
return event->touch.y;
|
||||
}
|
||||
|
||||
_public_ bool
|
||||
eis_event_touch_get_is_cancel(struct eis_event *event)
|
||||
{
|
||||
require_event_type(event, false, EIS_EVENT_TOUCH_UP);
|
||||
|
||||
return event->touch.is_cancel;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,17 @@ struct eis_event {
|
|||
struct {
|
||||
uint32_t touchid;
|
||||
double x, y;
|
||||
bool is_cancel;
|
||||
} touch;
|
||||
struct {
|
||||
uint32_t sequence;
|
||||
} start_emulating;
|
||||
struct {
|
||||
struct eis_ping *ping;
|
||||
} pong;
|
||||
struct {
|
||||
struct eis_callback *callback;
|
||||
} sync;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -76,6 +83,3 @@ eis_event_new_for_device(struct eis_device *device);
|
|||
|
||||
struct eis *
|
||||
eis_event_get_context(struct eis_event *event);
|
||||
|
||||
struct eis_event*
|
||||
eis_event_ref(struct eis_event *event);
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@ eis_handshake_get_id(struct eis_handshake *setup)
|
|||
}
|
||||
|
||||
static void
|
||||
pong(struct eis_connection *connection, void *user_data)
|
||||
on_pong(struct eis_connection_ping_callback *callback)
|
||||
{
|
||||
struct eis_client *client = eis_connection_get_client(connection);
|
||||
struct eis_client *client = eis_connection_ping_callback_get_client(callback);
|
||||
eis_queue_connect_event(client);
|
||||
}
|
||||
|
||||
|
|
@ -118,10 +118,27 @@ client_msg_finish(struct eis_handshake *setup)
|
|||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
|
||||
"Missing versions for required interfaces");
|
||||
|
||||
/* ei_callback needs a client-created object, so tell the client
|
||||
* about our version */
|
||||
eis_handshake_event_interface_version(setup, EIS_CALLBACK_INTERFACE_NAME,
|
||||
setup->client_versions.ei_callback);
|
||||
/* ei_callback needs a client-created object, so we must tell the client
|
||||
* about our version. But for convenience and to make sure this all works
|
||||
* send all our versions down the wire */
|
||||
#define SEND_INTERFACE_VERSION(upper_name_, lower_name_) \
|
||||
if (setup->client_versions.lower_name_) \
|
||||
eis_handshake_event_interface_version(setup, upper_name_ ##_INTERFACE_NAME, \
|
||||
setup->client_versions.lower_name_); \
|
||||
|
||||
SEND_INTERFACE_VERSION(EIS_CALLBACK, ei_callback);
|
||||
SEND_INTERFACE_VERSION(EIS_CONNECTION, ei_connection);
|
||||
SEND_INTERFACE_VERSION(EIS_PINGPONG, ei_pingpong);
|
||||
SEND_INTERFACE_VERSION(EIS_SEAT, ei_seat);
|
||||
SEND_INTERFACE_VERSION(EIS_DEVICE, ei_device);
|
||||
SEND_INTERFACE_VERSION(EIS_POINTER, ei_pointer);
|
||||
SEND_INTERFACE_VERSION(EIS_POINTER_ABSOLUTE, ei_pointer_absolute);
|
||||
SEND_INTERFACE_VERSION(EIS_BUTTON, ei_button);
|
||||
SEND_INTERFACE_VERSION(EIS_SCROLL, ei_scroll);
|
||||
SEND_INTERFACE_VERSION(EIS_KEYBOARD, ei_keyboard);
|
||||
SEND_INTERFACE_VERSION(EIS_TOUCHSCREEN, ei_touchscreen);
|
||||
|
||||
#undef SEND_INTERFACE_VERSION
|
||||
|
||||
eis_client_setup_done(client, setup->name, setup->is_sender, &setup->client_versions);
|
||||
|
||||
|
|
@ -137,9 +154,13 @@ client_msg_finish(struct eis_handshake *setup)
|
|||
setup->client_versions.ei_device == 0) {
|
||||
eis_client_disconnect(client);
|
||||
} else {
|
||||
_unref_(eis_connection_ping_callback) *cb = eis_connection_ping_callback_new(client->connection,
|
||||
on_pong,
|
||||
NULL,
|
||||
NULL);
|
||||
/* Force a ping/pong. This isn't necessary but it doesn't hurt much here
|
||||
* and it ensures that any client implementation doesn't have that part missing */
|
||||
eis_connection_ping(client->connection, pong, NULL);
|
||||
eis_connection_ping(client->connection, cb);
|
||||
}
|
||||
|
||||
client->setup = eis_handshake_unref(setup);
|
||||
|
|
@ -256,7 +277,7 @@ eis_handshake_get_interface(struct eis_handshake *setup) {
|
|||
|
||||
struct eis_handshake *
|
||||
eis_handshake_new(struct eis_client *client,
|
||||
const struct eis_client_interface_versions *versions)
|
||||
const struct eis_client_interface_versions *versions)
|
||||
{
|
||||
struct eis_handshake *setup = eis_handshake_create(&client->object);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 keyboard WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 keyboard WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
124
src/libeis-ping.c
Normal file
124
src/libeis-ping.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/* SPDX-License-Identifier: MIT */
|
||||
/*
|
||||
* Copyright © 2024 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "util-mem.h"
|
||||
#include "util-macros.h"
|
||||
#include "util-object.h"
|
||||
#include "util-list.h"
|
||||
#include "brei-shared.h"
|
||||
|
||||
#include "libeis-private.h"
|
||||
#include "libeis-connection.h"
|
||||
|
||||
struct eis_ping {
|
||||
struct object object;
|
||||
uint64_t id;
|
||||
void *user_data;
|
||||
|
||||
struct eis_client *client;
|
||||
bool is_pending;
|
||||
bool is_done;
|
||||
};
|
||||
|
||||
static void
|
||||
eis_ping_destroy(struct eis_ping *ping)
|
||||
{
|
||||
if (!ping->is_pending)
|
||||
eis_client_unref(ping->client);
|
||||
}
|
||||
|
||||
static
|
||||
OBJECT_IMPLEMENT_CREATE(eis_ping);
|
||||
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_REF(eis_ping);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_ping);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(eis_ping, id, uint64_t);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(eis_ping, user_data, void*);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_SETTER(eis_ping, user_data, void*);
|
||||
static
|
||||
OBJECT_IMPLEMENT_GETTER(eis_ping, client, struct eis_client*);
|
||||
|
||||
_public_ struct eis_ping *
|
||||
eis_client_new_ping(struct eis_client *client)
|
||||
{
|
||||
static uint64_t id = 0;
|
||||
|
||||
struct eis_ping *ping = eis_ping_create(NULL);
|
||||
ping->id = ++id;
|
||||
/* Ref our context while it's pending (i.e. only the caller has the ref).
|
||||
* Once it's pending we no longer need the ref.
|
||||
*/
|
||||
ping->client = eis_client_ref(client);
|
||||
ping->is_pending = false;
|
||||
ping->is_done = false;
|
||||
|
||||
return ping;
|
||||
}
|
||||
|
||||
static void
|
||||
on_pong(struct eis_connection_ping_callback *callback)
|
||||
{
|
||||
struct eis_ping *ping = eis_connection_ping_callback_get_user_data(callback);
|
||||
ping->is_done = true;
|
||||
|
||||
struct eis_client *client = eis_connection_ping_callback_get_client(callback);
|
||||
eis_queue_pong_event(client, ping);
|
||||
/* eis_ping ref is removed in on_destroy */
|
||||
}
|
||||
|
||||
static void
|
||||
on_destroy(struct eis_connection_ping_callback *callback)
|
||||
{
|
||||
/* This is only called if we never receisved a pong */
|
||||
_unref_(eis_ping) *ping = eis_connection_ping_callback_get_user_data(callback);
|
||||
|
||||
/* We never got a pong because we got disconnected. Queue a fake pong event */
|
||||
if (!ping->is_done) {
|
||||
struct eis_client *client = eis_connection_ping_callback_get_client(callback);
|
||||
eis_queue_pong_event(client, ping);
|
||||
}
|
||||
}
|
||||
|
||||
_public_ void
|
||||
eis_ping(struct eis_ping *ping)
|
||||
{
|
||||
struct eis_client *client = eis_ping_get_client(ping);
|
||||
|
||||
eis_client_unref(client);
|
||||
ping->client = client;
|
||||
ping->is_pending = true;
|
||||
|
||||
_unref_(eis_connection_ping_callback) *cb = eis_connection_ping_callback_new(client->connection,
|
||||
on_pong,
|
||||
on_destroy,
|
||||
eis_ping_ref(ping));
|
||||
eis_connection_ping(client->connection, cb);
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 pointer WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 pointer WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ struct eis {
|
|||
struct sink *sink;
|
||||
struct list clients;
|
||||
|
||||
uint32_t flags;
|
||||
|
||||
struct eis_backend_interface backend_interface;
|
||||
void *backend;
|
||||
struct list event_queue;
|
||||
|
|
@ -72,6 +74,8 @@ struct eis {
|
|||
enum eis_log_priority priority;
|
||||
} log;
|
||||
|
||||
eis_clock_now_func clock_now;
|
||||
|
||||
const struct eis_proto_requests *requests;
|
||||
};
|
||||
|
||||
|
|
@ -90,12 +94,24 @@ eis_queue_connect_event(struct eis_client *client);
|
|||
void
|
||||
eis_queue_disconnect_event(struct eis_client *client);
|
||||
|
||||
void
|
||||
eis_queue_pong_event(struct eis_client *client, struct eis_ping *ping);
|
||||
|
||||
void
|
||||
eis_queue_sync_event(struct eis_client *client, object_id_t newid, uint32_t version);
|
||||
|
||||
void
|
||||
eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities);
|
||||
|
||||
void
|
||||
eis_queue_seat_device_requested_event(struct eis_seat *seat, uint32_t capabilities);
|
||||
|
||||
void
|
||||
eis_queue_device_closed_event(struct eis_device *device);
|
||||
|
||||
void
|
||||
eis_queue_device_ready_event(struct eis_device *device);
|
||||
|
||||
void
|
||||
eis_queue_frame_event(struct eis_device *device, uint64_t time);
|
||||
|
||||
|
|
@ -144,6 +160,12 @@ eis_queue_touch_motion_event(struct eis_device *device, uint32_t touchid,
|
|||
void
|
||||
eis_queue_touch_up_event(struct eis_device *device, uint32_t touchid);
|
||||
|
||||
void
|
||||
eis_queue_touch_cancel_event(struct eis_device *device, uint32_t touchid);
|
||||
|
||||
void
|
||||
eis_sync_event_send_done(struct eis_event *e);
|
||||
|
||||
_printf_(6, 7) void
|
||||
eis_log_msg(struct eis *eis,
|
||||
enum eis_log_priority priority,
|
||||
|
|
@ -169,3 +191,14 @@ eis_log_msg_va(struct eis *eis,
|
|||
eis_log_msg((T_), EIS_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪳 libeis bug: " __VA_ARGS__)
|
||||
#define log_bug_client(T_, ...) \
|
||||
eis_log_msg((T_), EIS_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪲 Bug: " __VA_ARGS__)
|
||||
|
||||
#define DISCONNECT_IF_INVALID_VERSION(eis_client_, intf_, id_, version_) do { \
|
||||
struct eis_client *_client = (eis_client_); \
|
||||
uint32_t _version = (version_); \
|
||||
uint64_t _id = (id_); \
|
||||
if (_client->interface_versions.intf_ < _version) { \
|
||||
struct eis *_eis = eis_client_get_context(_client); \
|
||||
log_bug(_eis, "Received invalid version %u for object id %#" PRIx64 ". Disconnecting", _version, _id); \
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Received invalid version %u for object id %#" PRIx64 ".", _version, _id); \
|
||||
} \
|
||||
} while(0)
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "util-strings.h"
|
||||
#include "libeis-private.h"
|
||||
|
||||
static void
|
||||
eis_region_destroy(struct eis_region *region)
|
||||
{
|
||||
free(region->mapping_id);
|
||||
list_remove(®ion->link);
|
||||
if (!region->added_to_device)
|
||||
eis_device_unref(region->device);
|
||||
|
|
@ -50,6 +52,10 @@ _public_
|
|||
OBJECT_IMPLEMENT_GETTER(eis_region, width, uint32_t);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(eis_region, height, uint32_t);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(eis_region, physical_scale, double);
|
||||
_public_
|
||||
OBJECT_IMPLEMENT_GETTER(eis_region, mapping_id, const char *);
|
||||
|
||||
static
|
||||
OBJECT_IMPLEMENT_CREATE(eis_region);
|
||||
|
|
@ -107,6 +113,22 @@ eis_region_set_physical_scale(struct eis_region *region, double scale)
|
|||
region->physical_scale = scale;
|
||||
}
|
||||
|
||||
_public_ void
|
||||
eis_region_set_mapping_id(struct eis_region *region, const char *mapping_id)
|
||||
{
|
||||
if (region->added_to_device)
|
||||
return;
|
||||
|
||||
if (mapping_id == NULL) {
|
||||
struct eis_device *device = region->device;
|
||||
log_bug_client(eis_device_get_context(device),
|
||||
"%s: a region's mapping_id must not be NULL", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
region->mapping_id = xstrdup(mapping_id);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
eis_region_add(struct eis_region *region)
|
||||
{
|
||||
|
|
@ -130,9 +152,54 @@ eis_region_add(struct eis_region *region)
|
|||
eis_device_unref(region->device);
|
||||
}
|
||||
|
||||
bool
|
||||
_public_ bool
|
||||
eis_region_contains(struct eis_region *r, double x, double y)
|
||||
{
|
||||
return (x >= r->x && x < r->x + r->width &&
|
||||
y >= r->y && y < r->y + r->height);
|
||||
}
|
||||
|
||||
#ifdef _enable_tests_
|
||||
#include "util-munit.h"
|
||||
MUNIT_TEST(test_region_setters)
|
||||
{
|
||||
struct eis_region r = {0};
|
||||
|
||||
eis_region_set_size(&r, 1, 2);
|
||||
eis_region_set_offset(&r, 3, 4);
|
||||
eis_region_set_physical_scale(&r, 5.6);
|
||||
|
||||
munit_assert_int(eis_region_get_width(&r), ==, 1);
|
||||
munit_assert_int(eis_region_get_height(&r), ==, 2);
|
||||
munit_assert_int(eis_region_get_x(&r), ==, 3);
|
||||
munit_assert_int(eis_region_get_y(&r), ==, 4);
|
||||
munit_assert_double(eis_region_get_physical_scale(&r), ==, 5.6);
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_region_contains)
|
||||
{
|
||||
struct eis_region r = {0};
|
||||
|
||||
eis_region_set_size(&r, 100, 200);
|
||||
eis_region_set_offset(&r, 300, 400);
|
||||
|
||||
munit_assert_true(eis_region_contains(&r, 300, 400));
|
||||
munit_assert_true(eis_region_contains(&r, 399.9, 599.9));
|
||||
|
||||
munit_assert_false(eis_region_contains(&r, 299.9, 400));
|
||||
munit_assert_false(eis_region_contains(&r, 300, 399.9));
|
||||
|
||||
munit_assert_false(eis_region_contains(&r, 400.1, 400));
|
||||
munit_assert_false(eis_region_contains(&r, 400, 399.9));
|
||||
|
||||
munit_assert_false(eis_region_contains(&r, 299.9, 599.9));
|
||||
munit_assert_false(eis_region_contains(&r, 300, 600.1));
|
||||
|
||||
munit_assert_false(eis_region_contains(&r, 400, 599.9));
|
||||
munit_assert_false(eis_region_contains(&r, 399, 600));
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -38,7 +38,5 @@ struct eis_region {
|
|||
uint32_t x, y;
|
||||
uint32_t width, height;
|
||||
double physical_scale;
|
||||
char *mapping_id;
|
||||
};
|
||||
|
||||
bool
|
||||
eis_region_contains(struct eis_region *r, double x, double y);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 scroll WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 scroll WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -86,15 +86,11 @@ client_msg_release(struct eis_seat *seat)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_bind(struct eis_seat *seat, uint64_t caps)
|
||||
static inline uint32_t
|
||||
convert_capabilities(uint64_t caps)
|
||||
{
|
||||
uint32_t capabilities = 0;
|
||||
|
||||
if (caps & ~seat->capabilities.proto_mask)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE,
|
||||
"Invalid capabilities %#" PRIx64, caps);
|
||||
|
||||
/* Convert from protocol capabilities to our C API capabilities */
|
||||
if (caps & bit(EIS_POINTER_INTERFACE_INDEX))
|
||||
capabilities |= EIS_DEVICE_CAP_POINTER;
|
||||
|
|
@ -109,14 +105,41 @@ client_msg_bind(struct eis_seat *seat, uint64_t caps)
|
|||
if (caps & bit(EIS_SCROLL_INTERFACE_INDEX))
|
||||
capabilities |= EIS_DEVICE_CAP_SCROLL;
|
||||
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_bind(struct eis_seat *seat, uint64_t caps)
|
||||
{
|
||||
if (caps & ~seat->capabilities.proto_mask)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE,
|
||||
"Invalid capabilities %#" PRIx64, caps);
|
||||
|
||||
uint32_t capabilities = convert_capabilities(caps);
|
||||
|
||||
eis_seat_bind(seat, capabilities);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct brei_result *
|
||||
client_msg_request_device(struct eis_seat *seat, uint64_t caps)
|
||||
{
|
||||
if (caps == 0 || caps & ~seat->capabilities.proto_mask)
|
||||
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE,
|
||||
"Invalid capabilities %#" PRIx64, caps);
|
||||
|
||||
uint32_t capabilities = convert_capabilities(caps);
|
||||
|
||||
eis_queue_seat_device_requested_event(seat, capabilities);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct eis_seat_interface interface = {
|
||||
.release = client_msg_release,
|
||||
.bind = client_msg_bind,
|
||||
.request_device = client_msg_request_device,
|
||||
};
|
||||
|
||||
const struct eis_seat_interface *
|
||||
|
|
@ -125,6 +148,12 @@ eis_seat_get_interface(struct eis_seat *seat)
|
|||
return &interface;
|
||||
}
|
||||
|
||||
_public_ struct eis *
|
||||
eis_seat_get_context(struct eis_seat *seat)
|
||||
{
|
||||
return eis_client_get_context(eis_seat_get_client(seat));
|
||||
}
|
||||
|
||||
_public_ struct eis_seat *
|
||||
eis_client_new_seat(struct eis_client *client, const char *name)
|
||||
{
|
||||
|
|
@ -184,14 +213,16 @@ eis_seat_add(struct eis_seat *seat)
|
|||
mask_add(seat->capabilities.proto_mask, mask);
|
||||
}
|
||||
|
||||
if (seat->capabilities.c_mask & (EIS_DEVICE_CAP_POINTER|EIS_DEVICE_CAP_POINTER_ABSOLUTE) &&
|
||||
(client->interface_versions.ei_pointer > 0 || client->interface_versions.ei_pointer_absolute > 0)) {
|
||||
if (seat->capabilities.c_mask & EIS_DEVICE_CAP_SCROLL &&
|
||||
client->interface_versions.ei_scroll > 0) {
|
||||
uint64_t mask = bit(EIS_SCROLL_INTERFACE_INDEX);
|
||||
eis_seat_event_capability(seat, mask,
|
||||
EIS_SCROLL_INTERFACE_NAME);
|
||||
mask_add(seat->capabilities.proto_mask, mask);
|
||||
|
||||
mask = bit(EIS_BUTTON_INTERFACE_INDEX);
|
||||
}
|
||||
if (seat->capabilities.c_mask & EIS_DEVICE_CAP_BUTTON &&
|
||||
client->interface_versions.ei_button > 0) {
|
||||
uint64_t mask = bit(EIS_BUTTON_INTERFACE_INDEX);
|
||||
eis_seat_event_capability(seat, mask,
|
||||
EIS_BUTTON_INTERFACE_NAME);
|
||||
mask_add(seat->capabilities.proto_mask, mask);
|
||||
|
|
@ -235,9 +266,12 @@ eis_seat_bind(struct eis_seat *seat, uint32_t caps)
|
|||
}
|
||||
|
||||
caps &= seat->capabilities.c_mask;
|
||||
|
||||
seat->state = EIS_SEAT_STATE_BOUND;
|
||||
eis_queue_seat_bind_event(seat, caps);
|
||||
|
||||
uint32_t old_caps = seat->capabilities.bound;
|
||||
seat->capabilities.bound = caps;
|
||||
if (old_caps != caps)
|
||||
eis_queue_seat_bind_event(seat, caps);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ struct eis_seat {
|
|||
struct {
|
||||
uint32_t c_mask; /* this is the C API bitmask */
|
||||
uint64_t proto_mask; /* the protocol mask */
|
||||
|
||||
uint32_t bound; /* C API bitmask */
|
||||
} capabilities;
|
||||
|
||||
struct list devices;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,28 @@
|
|||
#include "libeis.h"
|
||||
#include "libeis-private.h"
|
||||
|
||||
#if defined(__DragonFly__) || defined(__FreeBSD__)
|
||||
#include <sys/ucred.h>
|
||||
#define CRED_T xucred
|
||||
#define CRED_LVL SOL_LOCAL
|
||||
#define CRED_OPT LOCAL_PEERCRED
|
||||
#define CRED_PID cr_pid
|
||||
#elif defined(__NetBSD__)
|
||||
#define CRED_T unpcbid
|
||||
#define CRED_LVL SOL_LOCAL
|
||||
#define CRED_OPT LOCAL_PEEREID
|
||||
#define CRED_PID unp_pid
|
||||
#else
|
||||
#if defined(__OpenBSD__)
|
||||
#define CRED_T sockpeercred
|
||||
#else
|
||||
#define CRED_T ucred
|
||||
#endif
|
||||
#define CRED_LVL SOL_SOCKET
|
||||
#define CRED_OPT SO_PEERCRED
|
||||
#define CRED_PID pid
|
||||
#endif
|
||||
|
||||
struct eis_socket {
|
||||
struct object object;
|
||||
struct source *listener;
|
||||
|
|
@ -180,3 +202,20 @@ eis_setup_backend_socket(struct eis *eis, const char *socketpath)
|
|||
|
||||
return rc;
|
||||
}
|
||||
|
||||
_public_ pid_t
|
||||
eis_backend_socket_get_client_pid(struct eis_client* client)
|
||||
{
|
||||
struct eis *eis = eis_client_get_context(client);
|
||||
if (eis->backend_interface.destroy != interface_socket_destroy) {
|
||||
log_bug_client(eis, "Not a socket backend");
|
||||
return -EINVAL;
|
||||
}
|
||||
struct CRED_T ucred;
|
||||
socklen_t len = sizeof(ucred);
|
||||
int rc = getsockopt(source_get_fd(client->source), CRED_LVL, CRED_OPT, &ucred, &len);
|
||||
if (rc < 0) {
|
||||
return -errno;
|
||||
}
|
||||
return ucred.CRED_PID;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 touchscreen WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* 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 touchscreen WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
|
|
|||
125
src/libeis.c
125
src/libeis.c
|
|
@ -29,6 +29,7 @@
|
|||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "util-bits.h"
|
||||
#include "util-macros.h"
|
||||
#include "util-object.h"
|
||||
#include "util-sources.h"
|
||||
|
|
@ -44,6 +45,9 @@ _Static_assert(sizeof(enum eis_keymap_type) == sizeof(int), "Invalid enum size")
|
|||
_Static_assert(sizeof(enum eis_event_type) == sizeof(int), "Invalid enum size");
|
||||
_Static_assert(sizeof(enum eis_log_priority) == sizeof(int), "Invalid enum size");
|
||||
|
||||
DEFINE_UNREF_CLEANUP_FUNC(brei_result);
|
||||
DEFINE_UNREF_CLEANUP_FUNC(eis_callback);
|
||||
|
||||
static void
|
||||
eis_destroy(struct eis *eis)
|
||||
{
|
||||
|
|
@ -92,6 +96,22 @@ eis_new(void *user_data)
|
|||
return steal(&eis);
|
||||
}
|
||||
|
||||
_public_ int
|
||||
eis_set_flag(struct eis *eis, enum eis_flag flag)
|
||||
{
|
||||
if (eis->backend) {
|
||||
log_bug_client(eis, "%s must be called before setting up a backend", __func__);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (flag > EIS_FLAG_DEVICE_READY)
|
||||
return -EINVAL;
|
||||
|
||||
flag_set(eis->flags, flag);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
_public_ int
|
||||
eis_get_fd(struct eis *eis)
|
||||
{
|
||||
|
|
@ -117,7 +137,11 @@ eis_event_type_to_string(enum eis_event_type type)
|
|||
CASE_RETURN_STRING(EIS_EVENT_CLIENT_CONNECT);
|
||||
CASE_RETURN_STRING(EIS_EVENT_CLIENT_DISCONNECT);
|
||||
CASE_RETURN_STRING(EIS_EVENT_SEAT_BIND);
|
||||
CASE_RETURN_STRING(EIS_EVENT_SEAT_DEVICE_REQUESTED);
|
||||
CASE_RETURN_STRING(EIS_EVENT_DEVICE_CLOSED);
|
||||
CASE_RETURN_STRING(EIS_EVENT_DEVICE_READY);
|
||||
CASE_RETURN_STRING(EIS_EVENT_PONG);
|
||||
CASE_RETURN_STRING(EIS_EVENT_SYNC);
|
||||
CASE_RETURN_STRING(EIS_EVENT_DEVICE_START_EMULATING);
|
||||
CASE_RETURN_STRING(EIS_EVENT_DEVICE_STOP_EMULATING);
|
||||
CASE_RETURN_STRING(EIS_EVENT_POINTER_MOTION);
|
||||
|
|
@ -193,8 +217,10 @@ eis_queue_event(struct eis_event *event)
|
|||
break;
|
||||
case EIS_EVENT_FRAME: {
|
||||
/* silently discard empty frames */
|
||||
if (list_empty(&device->pending_event_queue))
|
||||
if (list_empty(&device->pending_event_queue)) {
|
||||
eis_event_unref(event);
|
||||
return;
|
||||
}
|
||||
|
||||
struct eis_event *pending;
|
||||
list_for_each_safe(pending, &device->pending_event_queue, link) {
|
||||
|
|
@ -243,6 +269,16 @@ eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities)
|
|||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
void
|
||||
eis_queue_seat_device_requested_event(struct eis_seat *seat,
|
||||
uint32_t capabilities)
|
||||
{
|
||||
struct eis_event *e = eis_event_new_for_seat(seat);
|
||||
e->type = EIS_EVENT_SEAT_DEVICE_REQUESTED;
|
||||
e->bind.capabilities = capabilities;
|
||||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
void
|
||||
eis_queue_device_closed_event(struct eis_device *device)
|
||||
{
|
||||
|
|
@ -251,6 +287,50 @@ eis_queue_device_closed_event(struct eis_device *device)
|
|||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
void
|
||||
eis_queue_device_ready_event(struct eis_device *device)
|
||||
{
|
||||
struct eis_event *e = eis_event_new_for_device(device);
|
||||
e->type = EIS_EVENT_DEVICE_READY;
|
||||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
void
|
||||
eis_sync_event_send_done(struct eis_event *e)
|
||||
{
|
||||
_unref_(eis_callback) *callback = steal(&e->sync.callback);
|
||||
log_debug(eis_event_get_context(e) ,
|
||||
"object %#" PRIx64 ": connection sync done",
|
||||
eis_callback_get_id(callback));
|
||||
|
||||
int rc = eis_callback_event_done(callback, 0);
|
||||
_unref_(brei_result) *result = brei_result_new_from_neg_errno(rc);
|
||||
if (result) {
|
||||
struct eis_client *client = eis_event_get_client(e);
|
||||
eis_client_disconnect_with_reason(client,
|
||||
brei_result_get_reason(result),
|
||||
brei_result_get_explanation(result));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
eis_queue_sync_event(struct eis_client *client, object_id_t newid, uint32_t version)
|
||||
{
|
||||
struct eis_event *e = eis_event_new_for_client(client);
|
||||
e->type = EIS_EVENT_SYNC;
|
||||
e->sync.callback = eis_callback_new(client, newid, version);
|
||||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
void
|
||||
eis_queue_pong_event(struct eis_client *client, struct eis_ping *ping)
|
||||
{
|
||||
struct eis_event *e = eis_event_new_for_client(client);
|
||||
e->type = EIS_EVENT_PONG;
|
||||
e->pong.ping = eis_ping_ref(ping);
|
||||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
void
|
||||
eis_queue_frame_event(struct eis_device *device, uint64_t time)
|
||||
{
|
||||
|
|
@ -369,7 +449,7 @@ eis_queue_touch_down_event(struct eis_device *device, uint32_t touchid,
|
|||
{
|
||||
struct eis_event *e = eis_event_new_for_device(device);
|
||||
e->type = EIS_EVENT_TOUCH_DOWN;
|
||||
e->touch.touchid = touchid,
|
||||
e->touch.touchid = touchid;
|
||||
e->touch.x = x;
|
||||
e->touch.y = y;
|
||||
eis_queue_event(e);
|
||||
|
|
@ -381,7 +461,7 @@ eis_queue_touch_motion_event(struct eis_device *device, uint32_t touchid,
|
|||
{
|
||||
struct eis_event *e = eis_event_new_for_device(device);
|
||||
e->type = EIS_EVENT_TOUCH_MOTION;
|
||||
e->touch.touchid = touchid,
|
||||
e->touch.touchid = touchid;
|
||||
e->touch.x = x;
|
||||
e->touch.y = y;
|
||||
eis_queue_event(e);
|
||||
|
|
@ -392,7 +472,18 @@ eis_queue_touch_up_event(struct eis_device *device, uint32_t touchid)
|
|||
{
|
||||
struct eis_event *e = eis_event_new_for_device(device);
|
||||
e->type = EIS_EVENT_TOUCH_UP;
|
||||
e->touch.touchid = touchid,
|
||||
e->touch.touchid = touchid;
|
||||
e->touch.is_cancel = false;
|
||||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
void
|
||||
eis_queue_touch_cancel_event(struct eis_device *device, uint32_t touchid)
|
||||
{
|
||||
struct eis_event *e = eis_event_new_for_device(device);
|
||||
e->type = EIS_EVENT_TOUCH_UP;
|
||||
e->touch.touchid = touchid;
|
||||
e->touch.is_cancel = true;
|
||||
eis_queue_event(e);
|
||||
}
|
||||
|
||||
|
|
@ -424,18 +515,30 @@ eis_add_client(struct eis *eis, struct eis_client *client)
|
|||
list_append(&eis->clients, &client->link);
|
||||
}
|
||||
|
||||
_public_ void
|
||||
eis_clock_set_now_func(struct eis *eis, eis_clock_now_func func)
|
||||
{
|
||||
eis->clock_now = func;
|
||||
}
|
||||
|
||||
_public_ uint64_t
|
||||
eis_now(struct eis *eis)
|
||||
{
|
||||
uint64_t ts = 0;
|
||||
int rc = now(&ts);
|
||||
|
||||
if (rc < 0) {
|
||||
/* We should probably disconnect here but the chances of this
|
||||
* happening are so slim it's not worth worrying about. Plus,
|
||||
* if this fails we're likely to be inside ei_device_frame()
|
||||
* so we should flush a frame event before disconnecting and... */
|
||||
log_error(eis, "clock_gettime failed: %s", strerror(-rc));
|
||||
if (eis->clock_now)
|
||||
ts = eis->clock_now(eis);
|
||||
else {
|
||||
int rc = now(&ts);
|
||||
|
||||
if (rc < 0) {
|
||||
/* We should probably disconnect here but the chances of this
|
||||
* happening are so slim it's not worth worrying about. Plus,
|
||||
* if this fails we're likely to be inside ei_device_frame()
|
||||
* so we should flush a frame event before disconnecting and... */
|
||||
log_error(eis, "clock_gettime failed: %s", strerror(-rc));
|
||||
}
|
||||
}
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
|
|
|||
445
src/libeis.h
445
src/libeis.h
|
|
@ -32,6 +32,7 @@ extern "C" {
|
|||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/**
|
||||
* @defgroup libeis 🍦 EIS - The server API
|
||||
|
|
@ -147,6 +148,16 @@ struct eis_keymap;
|
|||
*/
|
||||
struct eis_touch;
|
||||
|
||||
/**
|
||||
* @struct eis_ping
|
||||
*
|
||||
* A callback struct returned by eis_new_ping() to handle
|
||||
* roundtrips to the client.
|
||||
*
|
||||
* @see eis_new_ping
|
||||
*/
|
||||
struct eis_ping;
|
||||
|
||||
/**
|
||||
* @struct eis_region
|
||||
* @ingroup libeis-region
|
||||
|
|
@ -227,13 +238,19 @@ enum eis_keymap_type {
|
|||
|
||||
/**
|
||||
* @enum eis_event_type
|
||||
*
|
||||
* This enum is not exhaustive, future versions of this library may add
|
||||
* new event types.
|
||||
*
|
||||
* Unknown events must be released by the caller with eis_event_unref(),
|
||||
* see eis_get_event().
|
||||
*/
|
||||
enum eis_event_type {
|
||||
/**
|
||||
* A client has connected. This is the first event from any new
|
||||
* client.
|
||||
* The server is expected to either call eis_event_client_connect() or
|
||||
* eis_event_client_disconnect().
|
||||
* The server is expected to either call eis_client_connect() or
|
||||
* eis_client_disconnect().
|
||||
*/
|
||||
EIS_EVENT_CLIENT_CONNECT = 1,
|
||||
/**
|
||||
|
|
@ -255,6 +272,47 @@ enum eis_event_type {
|
|||
*/
|
||||
EIS_EVENT_DEVICE_CLOSED,
|
||||
|
||||
/**
|
||||
* The client has completed configuration of the device (if any) and
|
||||
* the caller may call eis_device_resume().
|
||||
*
|
||||
* libei guarantees that this event even if the client
|
||||
* does not support the underlying protocol.
|
||||
*
|
||||
* This event is only generated if the caller has called
|
||||
* eis_set_flag() with the @ref EIS_FLAG_DEVICE_READY flag.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
EIS_EVENT_DEVICE_READY,
|
||||
|
||||
/**
|
||||
* The client requested a device with the given capabilities
|
||||
* on this seat.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
EIS_EVENT_SEAT_DEVICE_REQUESTED,
|
||||
|
||||
/**
|
||||
* Returned in response to eis_ping().
|
||||
*/
|
||||
EIS_EVENT_PONG = 90,
|
||||
|
||||
/**
|
||||
* This event represents a synchronization request (ping) from the client
|
||||
* implementation. The corresponding reply (pong) will be sent when it
|
||||
* is unref'd. It has no other API.
|
||||
*
|
||||
* The caller must ensure that any state changes triggered by messages
|
||||
* received prior to this event have been resolved and communicated to the
|
||||
* client prior to calling eis_event_unref(). E.g., if the preceding frame
|
||||
* included a key event that caused shift to become logically depressed, the
|
||||
* caller should ensure that the updated modifier state has been sent before
|
||||
* calling unref.
|
||||
*/
|
||||
EIS_EVENT_SYNC,
|
||||
|
||||
/**
|
||||
* "Hardware" frame event. This event **must** be sent by the client
|
||||
* and notifies the server that the previous set of events belong to
|
||||
|
|
@ -300,16 +358,16 @@ enum eis_event_type {
|
|||
* An absolute motion event with absolute position within the device's
|
||||
* regions or size, depending on the device type.
|
||||
*/
|
||||
EIS_EVENT_POINTER_MOTION_ABSOLUTE,
|
||||
EIS_EVENT_POINTER_MOTION_ABSOLUTE = 400,
|
||||
/**
|
||||
* A button press or release event
|
||||
*/
|
||||
EIS_EVENT_BUTTON_BUTTON,
|
||||
EIS_EVENT_BUTTON_BUTTON = 500,
|
||||
/**
|
||||
* A vertical and/or horizontal scroll event with logical-pixels
|
||||
* or mm precision, depending on the device type.
|
||||
*/
|
||||
EIS_EVENT_SCROLL_DELTA,
|
||||
EIS_EVENT_SCROLL_DELTA = 600,
|
||||
/**
|
||||
* An ongoing scroll sequence stopped.
|
||||
*/
|
||||
|
|
@ -327,7 +385,7 @@ enum eis_event_type {
|
|||
/**
|
||||
* A key press or release event
|
||||
*/
|
||||
EIS_EVENT_KEYBOARD_KEY = 400,
|
||||
EIS_EVENT_KEYBOARD_KEY = 700,
|
||||
|
||||
/**
|
||||
* Event for a single touch set down on the device's logical surface.
|
||||
|
|
@ -335,7 +393,7 @@ enum eis_event_type {
|
|||
* between. On multitouch capable devices, several touchs eqeuences
|
||||
* may be active at any time.
|
||||
*/
|
||||
EIS_EVENT_TOUCH_DOWN = 500,
|
||||
EIS_EVENT_TOUCH_DOWN = 800,
|
||||
/**
|
||||
* Event for a single touch released from the device's logical
|
||||
* surface.
|
||||
|
|
@ -362,6 +420,53 @@ eis_event_type_to_string(enum eis_event_type);
|
|||
struct eis *
|
||||
eis_new(void *user_data);
|
||||
|
||||
/**
|
||||
* Context flags to enable EIS-specific behaviors.
|
||||
*
|
||||
* These flags must be set to take advantage of newer versions
|
||||
* of the ei protocol. See the documentation for each flag
|
||||
* for details.
|
||||
*
|
||||
* @see eis_set_flag
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
enum eis_flag {
|
||||
/**
|
||||
* If set, libeis will announce ei_device protocol
|
||||
* version 3 or later. A compatible client will send
|
||||
* ei_device.ready in response to ei_device.done. See
|
||||
* the protocol documentation for details.
|
||||
*
|
||||
* If set, after eis_device_add() an EIS implementation must wait
|
||||
* until the @ref EIS_EVENT_DEVICE_READY has been received for
|
||||
* that device before calling eis_device_resume().
|
||||
*/
|
||||
EIS_FLAG_DEVICE_READY = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the behavior of the context according to the
|
||||
* given flag. See @ref eis_flag for documentation on
|
||||
* each indidividual flag.
|
||||
*
|
||||
* This function must be called before any of eis_setup_backend_fd()
|
||||
* or eis_setup_backend_socket().
|
||||
*
|
||||
* This function may be called multiple times with different flags.
|
||||
* Some flags may be mutually exclusive, consult the documentation
|
||||
* on each individual flag for details.
|
||||
*
|
||||
* @param eis The EIS context
|
||||
* @param flag **One** flag (not a bitmask) to set on this context
|
||||
*
|
||||
* @return 0 on success or a negative errno on failure
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
int
|
||||
eis_set_flag(struct eis *eis, enum eis_flag flag);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-log
|
||||
*/
|
||||
|
|
@ -405,7 +510,7 @@ eis_log_context_get_func(struct eis_log_context *ctx);
|
|||
* messages with a log level equal or greater than than the one set in
|
||||
* eis_log_set_priority().
|
||||
*
|
||||
* @param eis The EIs context
|
||||
* @param eis The EIS context
|
||||
* @param priority The log priority
|
||||
* @param message The log message as a null-terminated string
|
||||
* @param ctx A log message context for this message
|
||||
|
|
@ -439,6 +544,24 @@ eis_log_set_priority(struct eis *eis, enum eis_log_priority priority);
|
|||
enum eis_log_priority
|
||||
eis_log_get_priority(const struct eis *eis);
|
||||
|
||||
/**
|
||||
* Optional override function for eis_now().
|
||||
*
|
||||
* By default eis_now() returns the current timestamp in CLOCK_MONOTONIC. This
|
||||
* may be overridden by a caller to provide a different timestamp.
|
||||
*
|
||||
* There is rarely a need to override this function. It exists for the libeis-internal test suite.
|
||||
*/
|
||||
typedef uint64_t (*eis_clock_now_func)(struct eis *eis);
|
||||
|
||||
/**
|
||||
* Override the function that returns the current time eis_now().
|
||||
*
|
||||
* There is rarely a need to override this function. It exists for the libeis-internal test suite.
|
||||
*/
|
||||
void
|
||||
eis_clock_set_now_func(struct eis *, eis_clock_now_func func);
|
||||
|
||||
struct eis *
|
||||
eis_ref(struct eis *eis);
|
||||
|
||||
|
|
@ -452,14 +575,12 @@ void
|
|||
eis_set_user_data(struct eis *eis, void *user_data);
|
||||
|
||||
/**
|
||||
* Initialize the context with a UNIX socket name.
|
||||
* If the path does not start with / it is relative to $XDG_RUNTIME_DIR.
|
||||
*/
|
||||
int
|
||||
eis_setup_backend_socket(struct eis *ctx, const char *path);
|
||||
|
||||
/**
|
||||
* Initialize the context that can take pre-configured sockets.
|
||||
* Initialize the context that can take pre-configured socket file descriptors,
|
||||
* see eis_backend_fd_add_client().
|
||||
*
|
||||
* This is the preferred backend to use for EIS implementations as it keeps
|
||||
* the file descriptors private for each client and is not subject to race
|
||||
* conditions or unauthorized clients attempting to connect.
|
||||
*/
|
||||
int
|
||||
eis_setup_backend_fd(struct eis *ctx);
|
||||
|
|
@ -472,6 +593,30 @@ eis_setup_backend_fd(struct eis *ctx);
|
|||
int
|
||||
eis_backend_fd_add_client(struct eis *ctx);
|
||||
|
||||
/**
|
||||
* Initialize the context with a UNIX socket name.
|
||||
* If the path does not start with / it is relative to $XDG_RUNTIME_DIR.
|
||||
*
|
||||
* The socket will be accessible by any client with permissions to do so and
|
||||
* it is up to the EIS implementation to authenticate clients. In almost
|
||||
* all cases, the EIS implementation should use eis_setup_backend_fd() instead.
|
||||
* This backend is primarily useful for testing and debugging.
|
||||
*/
|
||||
int
|
||||
eis_setup_backend_socket(struct eis *ctx, const char *path);
|
||||
|
||||
|
||||
/**
|
||||
* Return the pid of the client.
|
||||
* The pid is retrieved via SO_PEERCRED.
|
||||
* This function should only be called if the context was set up via
|
||||
* eis_setup_backend_socket.
|
||||
*
|
||||
* @return The PID of the client or a negative errno on failure
|
||||
*/
|
||||
pid_t
|
||||
eis_backend_socket_get_client_pid(struct eis_client *client);
|
||||
|
||||
/**
|
||||
* libeis keeps a single file descriptor for all events. This fd should be
|
||||
* monitored for events by the caller's mainloop, e.g. using select(). When
|
||||
|
|
@ -493,12 +638,84 @@ eis_get_fd(struct eis *eis);
|
|||
void
|
||||
eis_dispatch(struct eis *eis);
|
||||
|
||||
/**
|
||||
* Return a unique, increasing id for this struct. This ID is assigned at
|
||||
* struct creation and constant for the lifetime of the struct. The ID
|
||||
* does not exist at the protocol level.
|
||||
*
|
||||
* This is a convenience API to make it easier to put this struct into
|
||||
* e.g. a hashtable without having to use the pointer value itself.
|
||||
*
|
||||
* The ID increases by an unspecified amount.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
uint64_t
|
||||
eis_ping_get_id(struct eis_ping *ping);
|
||||
|
||||
/**
|
||||
* Increase the refcount of this struct by one. Use eis_ping_unref() to decrease
|
||||
* the refcount.
|
||||
*
|
||||
* @return the argument passed into the function
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
struct eis_ping *
|
||||
eis_ping_ref(struct eis_ping *eis_ping);
|
||||
|
||||
/**
|
||||
* Decrease the refcount of this struct by one. When the refcount reaches
|
||||
* zero, the context disconnects from the server and all allocated resources
|
||||
* are released.
|
||||
*
|
||||
* @return always NULL
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
struct eis_ping *
|
||||
eis_ping_unref(struct eis_ping *eis_ping);
|
||||
|
||||
/**
|
||||
* Set a custom data pointer for this struct. libeis will not look at or
|
||||
* modify the pointer. Use eis_ping_get_user_data() to retrieve a previously set
|
||||
* user data.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
void
|
||||
eis_ping_set_user_data(struct eis_ping *eis_ping, void *user_data);
|
||||
|
||||
/**
|
||||
* Return the custom data pointer for this struct. libeis will not look at or
|
||||
* modify the pointer. Use eis_ping_set_user_data() to change the user data.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
void *
|
||||
eis_ping_get_user_data(struct eis_ping *eis_ping);
|
||||
|
||||
/**
|
||||
* Issue a roundtrip request to the client, resulting in
|
||||
* an @ref EIS_EVENT_PONG event when this roundtrip has been processed. Events
|
||||
* are processed in-order by the client implementation so this
|
||||
* function can be used as synchronization point between events.
|
||||
*
|
||||
* If the client is disconnected before the roundtrip is complete,
|
||||
* libeis will emulate a @ref EIS_EVENT_PONG event before
|
||||
* @ref EIS_EVENT_CLIENT_DISCONNECT.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
void
|
||||
eis_ping(struct eis_ping *ping);
|
||||
|
||||
/**
|
||||
* Returns the next event in the internal event queue (or NULL) and removes
|
||||
* it from the queue.
|
||||
*
|
||||
* The returned event is refcounted, use eis_event_unref() to drop the
|
||||
* reference.
|
||||
* reference even if the event type is unknown to the caller.
|
||||
*
|
||||
* You must not call this function while holding a reference to an event
|
||||
* returned by eis_peek_event().
|
||||
|
|
@ -526,13 +743,24 @@ struct eis_event *
|
|||
eis_peek_event(struct eis *eis);
|
||||
|
||||
/**
|
||||
* Release resources associated with this event. This function always
|
||||
* returns NULL.
|
||||
* Increase the refcount of this struct by one. Use eis_event_unref() to decrease
|
||||
* the refcount. This function always returns the same event that the reference
|
||||
* was added to.
|
||||
*
|
||||
* The caller cannot increase the refcount of an event. Events should be
|
||||
* considered transient data and not be held longer than required.
|
||||
* eis_event_unref() is provided for consistency (as opposed to, say,
|
||||
* eis_event_free()).
|
||||
* Events should be considered transient data and not be held longer than
|
||||
* required.
|
||||
*/
|
||||
struct eis_event *
|
||||
eis_event_ref(struct eis_event *event);
|
||||
|
||||
/**
|
||||
* Decrease the refcount of this struct by one. When the refcount reaches
|
||||
* zero, all allocated resources for this struct are released.
|
||||
*
|
||||
* Events should be considered transient data and not be held longer than
|
||||
* required.
|
||||
*
|
||||
* @return always NULL
|
||||
*/
|
||||
struct eis_event *
|
||||
eis_event_unref(struct eis_event *event);
|
||||
|
|
@ -547,11 +775,24 @@ struct eis_seat *
|
|||
eis_event_get_seat(struct eis_event *event);
|
||||
|
||||
/**
|
||||
* For an event of type @ref EIS_EVENT_SEAT_BIND, return the capabilities
|
||||
* requested by the client.
|
||||
* Returns the associated @ref eis_ping struct with this event.
|
||||
*
|
||||
* This is the set of *all* capabilities bound by the client as of this event,
|
||||
* not just the changed ones.
|
||||
* For events of type other than @ref EIS_EVENT_PONG this function
|
||||
* returns NULL.
|
||||
*
|
||||
* This does not increase the refcount of the eis_pong. Use eis_pong_ref()
|
||||
* to ekeep a reference beyond the immediate scope.
|
||||
*/
|
||||
struct eis_ping *
|
||||
eis_event_pong_get_ping(struct eis_event *event);
|
||||
|
||||
/**
|
||||
* For an event of type @ref EIS_EVENT_SEAT_BIND or @ref
|
||||
* EIS_EVENT_SEAT_DEVICE_REQUESTED, return the capabilities requested by the
|
||||
* client.
|
||||
*
|
||||
* For events of type @ref EIS_EVENT_SEAT_BIND this is the set of *all*
|
||||
* capabilities bound by the client as of this event, not just the changed ones.
|
||||
*/
|
||||
bool
|
||||
eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capability cap);
|
||||
|
|
@ -577,7 +818,17 @@ eis_event_get_device(struct eis_event *event);
|
|||
uint64_t
|
||||
eis_event_get_time(struct eis_event *event);
|
||||
|
||||
|
||||
/**
|
||||
* Create a new eis_ping object to trigger a round trip to the client.
|
||||
* See eis_ping() for details.
|
||||
*
|
||||
* The returned @ref eis_ping is refcounted, use eis_ping_unref() to
|
||||
* drop the reference.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
struct eis_ping *
|
||||
eis_client_new_ping(struct eis_client *client);
|
||||
|
||||
struct eis_client *
|
||||
eis_client_ref(struct eis_client *client);
|
||||
|
|
@ -723,6 +974,18 @@ eis_seat_add(struct eis_seat *seat);
|
|||
void
|
||||
eis_seat_remove(struct eis_seat *seat);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-seat
|
||||
*/
|
||||
struct eis *
|
||||
eis_seat_get_context(struct eis_seat *seat);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-device
|
||||
*/
|
||||
struct eis *
|
||||
eis_device_get_context(struct eis_device *device);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-device
|
||||
*/
|
||||
|
|
@ -908,6 +1171,53 @@ eis_region_set_offset(struct eis_region *region, uint32_t x, uint32_t y);
|
|||
void
|
||||
eis_region_set_physical_scale(struct eis_region *region, double scale);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-region
|
||||
*
|
||||
* Attach a unique identifier representing an external resource to this region.
|
||||
* libeis does not look at or modify the value, it is passed through to the
|
||||
* client if the client supports ei_device interface version 2 or later. Because
|
||||
* the ID is assigned by the caller libei makes no guarantee that the ID is
|
||||
* indeed unique and/or corresponds to any particular format.
|
||||
*
|
||||
* This ID can be used by the caller to identify an external resource
|
||||
* that has a relationship with this region. For example, a caller may have
|
||||
* a data stream with the video data that this region represents.
|
||||
* By attaching the same identifier to the data stream and this region a client
|
||||
* (who needs to be aware of this approach) can pair the video data stream
|
||||
* with the region. Note that in this example use-case, if the stream is resized
|
||||
* there may be a transition period where two regions have the same identifier -
|
||||
* the old region and the new region with the updated size. A client must be
|
||||
* able to handle the case where two mapping_ids are identical.
|
||||
*
|
||||
* This function has no effect if called after eis_device_add()
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
void
|
||||
eis_region_set_mapping_id(struct eis_region *region, const char *mapping_id);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-region
|
||||
*
|
||||
* Get the unique ID for this region previously set by this
|
||||
* caller, if any, or NULL if the client does not support region mapping id
|
||||
* or no region ID has been set.
|
||||
*
|
||||
* Region IDs require the client to support the ei_device interface
|
||||
* version 2 or later. This function can be used to detect support
|
||||
* for this interface: after eis_region_add() this region's ID is set
|
||||
* to NULL if the client does not support that interface version.
|
||||
*
|
||||
* In other words, if a caller sets a region ID with
|
||||
* eis_region_set_mapping_id() and that region ID is returned after eis_region_add(),
|
||||
* the client has been notified of the region ID.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
const char *
|
||||
eis_region_get_mapping_id(struct eis_region *region);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-region
|
||||
*
|
||||
|
|
@ -941,6 +1251,17 @@ eis_region_add(struct eis_region *region);
|
|||
struct eis_region *
|
||||
eis_device_get_region(struct eis_device *device, size_t index);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-device
|
||||
*
|
||||
* Return the region that contains the given point x/y (in desktop-wide
|
||||
* coordinates) or NULL if the coordinates are outside all regions.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
struct eis_region *
|
||||
eis_device_get_region_at(struct eis_device *device, double x, double y);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-region
|
||||
*/
|
||||
|
|
@ -989,6 +1310,20 @@ eis_region_get_width(struct eis_region *region);
|
|||
uint32_t
|
||||
eis_region_get_height(struct eis_region *region);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-region
|
||||
*/
|
||||
double
|
||||
eis_region_get_physical_scale(struct eis_region *region);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-region
|
||||
*
|
||||
* @see ei_region_contains
|
||||
*/
|
||||
bool
|
||||
eis_region_contains(struct eis_region *region, double x, double y);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-device
|
||||
*
|
||||
|
|
@ -1023,11 +1358,12 @@ eis_device_remove(struct eis_device *device);
|
|||
* a number of events from a device after it has been paused and must
|
||||
* update its internal state accordingly.
|
||||
*
|
||||
* Pause/resume should only be used for short-term event delaying, a client
|
||||
* will expect that the device's state has not changed between pause and
|
||||
* resume. Where a device's state changes on the EIS implementation side (e.g.
|
||||
* buttons or keys are forcibly released), the device should be removed and
|
||||
* re-added as new device.
|
||||
* Pausing a device resets the logical state of the device to neutral.
|
||||
* This includes:
|
||||
* - any buttons or keys logically down are released
|
||||
* - any modifiers logically down are released
|
||||
* - any touches logically down are released
|
||||
* No events will be sent for these releases back to a neutral state.
|
||||
*
|
||||
* @param device A connected device
|
||||
*/
|
||||
|
|
@ -1158,6 +1494,22 @@ eis_device_keyboard_get_keymap(struct eis_device *device);
|
|||
* @ingroup libeis-device
|
||||
*
|
||||
* Notify the client of the current XKB modifier state.
|
||||
*
|
||||
* This should be called every time the modifier state or current
|
||||
* effective group changes.
|
||||
*
|
||||
* When the state changes due to an incoming
|
||||
* EIS_EVENT_KEYBOARD_KEY (for sender clients), this method should be
|
||||
* called when the corresponding EIS_EVENT_FRAME is processed, before
|
||||
* processing any subsequent events.
|
||||
*
|
||||
* When the state changes due to a key press with a receiver client, this
|
||||
* method should be called immediately after the corresponding call to
|
||||
* eis_device_frame(). If the change impacts multiple keyboards, this
|
||||
* method should be called for all of them.
|
||||
*
|
||||
* For changes caused by other factors, this method should be called for
|
||||
* all affected keyboards at the point the change occurs.
|
||||
*/
|
||||
void
|
||||
eis_device_keyboard_send_xkb_modifiers(struct eis_device *device,
|
||||
|
|
@ -1288,6 +1640,14 @@ eis_touch_motion(struct eis_touch *touch, double x, double y);
|
|||
void
|
||||
eis_touch_up(struct eis_touch *touch);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-receiver
|
||||
*
|
||||
* see @ref ei_touch_cancel
|
||||
*/
|
||||
void
|
||||
eis_touch_cancel(struct eis_touch *touch);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-receiver
|
||||
*
|
||||
|
|
@ -1506,15 +1866,28 @@ eis_event_touch_get_x(struct eis_event *event);
|
|||
double
|
||||
eis_event_touch_get_y(struct eis_event *event);
|
||||
|
||||
/**
|
||||
* @ingroup libeis-sender
|
||||
*
|
||||
* For an event of type @ref EIS_EVENT_TOUCH_UP
|
||||
* return true if the touch was cancelled instead
|
||||
* of logically released.
|
||||
*
|
||||
* Support for touch cancellation requires the EIS implementation and client to
|
||||
* support version 2 or later of the ei_touchscreen protocol interface.
|
||||
*/
|
||||
bool
|
||||
eis_event_touch_get_is_cancel(struct eis_event *event);
|
||||
|
||||
/**
|
||||
* @returns a timestamp for the current time to pass into
|
||||
* eis_device_frame().
|
||||
*
|
||||
* In the current implementation, the returned timestamp is CLOCK_MONOTONIC
|
||||
* for compatibility with evdev and libinput.
|
||||
* By default, the returned timestamp is CLOCK_MONOTONIC for compatibility with
|
||||
* evdev and libinput. This can be overridden with eis_clock_set_now_func().
|
||||
*/
|
||||
uint64_t
|
||||
eis_now(struct eis *ei);
|
||||
eis_now(struct eis *eis);
|
||||
|
||||
/**
|
||||
* @}
|
||||
|
|
|
|||
|
|
@ -294,14 +294,14 @@ sender_name(sd_bus *bus)
|
|||
_cleanup_free_ char *sender = NULL;
|
||||
const char *name = NULL;
|
||||
|
||||
if (sd_bus_get_unique_name(bus, &name) != 0)
|
||||
if ((sd_bus_get_unique_name(bus, &name) != 0) || strlen(name) < 1)
|
||||
return NULL;
|
||||
|
||||
sender = xstrdup(name + 1); /* drop initial : */
|
||||
name += 1; /* drop initial : */
|
||||
sender = xalloc(strlen(name));
|
||||
|
||||
for (unsigned i = 0; sender[i]; i++) {
|
||||
if (sender[i] == '.')
|
||||
sender[i] = '_';
|
||||
for (unsigned i = 0; name[i]; i++) {
|
||||
sender[i] = name[i] == '.' ? '_' : name[i];
|
||||
}
|
||||
|
||||
return steal(&sender);
|
||||
|
|
@ -327,22 +327,61 @@ xdp_session_path(char *sender_name, char *token)
|
|||
return xaprintf("/org/freedesktop/portal/desktop/session/%s/%s", sender_name, token);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
connect_to_eis_returned(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
|
||||
{
|
||||
int eisfd;
|
||||
struct oeffis *oeffis = userdata;
|
||||
int rc = sd_bus_message_get_errno(m);
|
||||
|
||||
if (rc > 0) {
|
||||
oeffis_disconnect(oeffis, "Error calling ConnectToEIS: %s", strerror(rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sd_bus_message_read(m, "h", &eisfd);
|
||||
if (rc < 0) {
|
||||
oeffis_disconnect(oeffis, "Unable to get fd from portal: %s", strerror(-rc));
|
||||
return -rc;
|
||||
}
|
||||
|
||||
/* the fd is owned by the message */
|
||||
rc = xerrno(xdup(eisfd));
|
||||
if (rc < 0) {
|
||||
oeffis_disconnect(oeffis, "Failed to dup fd: %s", strerror(-rc));
|
||||
return -rc;
|
||||
|
||||
} else {
|
||||
eisfd = rc;
|
||||
int flags = fcntl(eisfd, F_GETFL, 0);
|
||||
fcntl(eisfd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
log_debug("Got fd %d from portal", eisfd);
|
||||
|
||||
rc = oeffis_set_eis_fd(oeffis, eisfd);
|
||||
if (rc < 0) {
|
||||
oeffis_disconnect(oeffis, "Failed to set the fd: %s", strerror(-rc));
|
||||
return -rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
portal_connect_to_eis(struct oeffis *oeffis)
|
||||
{
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_unref_(sd_bus_message) *response = NULL;
|
||||
sd_bus *bus = oeffis->bus;
|
||||
int eisfd;
|
||||
|
||||
int rc = 0;
|
||||
with_signals_blocked(SIGALRM) {
|
||||
rc = sd_bus_call_method(bus, oeffis->busname,
|
||||
rc = sd_bus_call_method_async(bus, NULL, oeffis->busname,
|
||||
"/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.RemoteDesktop",
|
||||
"ConnectToEIS",
|
||||
&error,
|
||||
&response,
|
||||
&connect_to_eis_returned,
|
||||
oeffis,
|
||||
"oa{sv}",
|
||||
oeffis->session_path,
|
||||
0);
|
||||
|
|
@ -352,29 +391,6 @@ portal_connect_to_eis(struct oeffis *oeffis)
|
|||
oeffis_disconnect(oeffis, "Failed to call ConnectToEIS: %s", strerror(-rc));
|
||||
return;
|
||||
}
|
||||
|
||||
rc = sd_bus_message_read(response, "h", &eisfd);
|
||||
if (rc < 0) {
|
||||
oeffis_disconnect(oeffis, "Unable to get fd from portal: %s", strerror(-rc));
|
||||
return;
|
||||
}
|
||||
|
||||
/* the fd is owned by the message */
|
||||
rc = xerrno(xdup(eisfd));
|
||||
if (rc < 0) {
|
||||
oeffis_disconnect(oeffis, "Failed to dup fd: %s", strerror(-rc));
|
||||
return;
|
||||
} else {
|
||||
eisfd = rc;
|
||||
int flags = fcntl(eisfd, F_GETFL, 0);
|
||||
fcntl(eisfd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
log_debug("Got fd %d from portal\n", eisfd);
|
||||
|
||||
rc = oeffis_set_eis_fd(oeffis, eisfd);
|
||||
if (rc < 0)
|
||||
oeffis_disconnect(oeffis, "Failed to set the fd: %s", strerror(-rc));
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
@ -448,7 +464,7 @@ portal_start_response_received(sd_bus_message *m, void *userdata, sd_bus_error *
|
|||
return 0;
|
||||
}
|
||||
|
||||
log_debug("Portal Start reponse is %u\n", response);
|
||||
log_debug("Portal Start response is %u", response);
|
||||
if (response != 0) {
|
||||
oeffis_disconnect(oeffis, "Portal denied Start");
|
||||
return 0;
|
||||
|
|
@ -525,7 +541,7 @@ portal_select_devices_response_received(sd_bus_message *m, void *userdata, sd_bu
|
|||
return 0;
|
||||
}
|
||||
|
||||
log_debug("Portal SelectDevices reponse is %u\n", response);
|
||||
log_debug("Portal SelectDevices response is %u", response);
|
||||
if (response != 0) {
|
||||
oeffis_disconnect(oeffis, "Portal denied SelectDevices");
|
||||
return 0;
|
||||
|
|
@ -560,7 +576,7 @@ portal_select_devices(struct oeffis *oeffis)
|
|||
&response,
|
||||
"oa{sv}",
|
||||
oeffis->session_path,
|
||||
2,
|
||||
oeffis->devices == OEFFIS_DEVICE_ALL_DEVICES ? 1 : 2,
|
||||
"handle_token", /* string key */
|
||||
"s", token, /* variant string */
|
||||
"types", /* string key */
|
||||
|
|
@ -598,7 +614,7 @@ portal_create_session_response_received(sd_bus_message *m, void *userdata, sd_bu
|
|||
return 0;
|
||||
}
|
||||
|
||||
log_debug("Portal CreateSession reponse is %u\n", response);
|
||||
log_debug("Portal CreateSession response is %u", response);
|
||||
|
||||
const char *session_handle = NULL;
|
||||
if (response == 0) {
|
||||
|
|
@ -666,7 +682,7 @@ portal_init(struct oeffis *oeffis, const char *busname)
|
|||
oeffis_disconnect(oeffis, "RemoteDesktop.version is %u, we need 2", version);
|
||||
return;
|
||||
}
|
||||
log_debug("RemoteDesktop.version is %u\n", version);
|
||||
log_debug("RemoteDesktop.version is %u", version);
|
||||
|
||||
_cleanup_free_ char *token = NULL;
|
||||
_unref_(sd_bus_slot) *request_slot = NULL;
|
||||
|
|
@ -716,7 +732,7 @@ portal_init(struct oeffis *oeffis, const char *busname)
|
|||
return;
|
||||
}
|
||||
|
||||
log_debug("Portal Response object is %s\n", path);
|
||||
log_debug("Portal Response object is %s", path);
|
||||
|
||||
_unref_(source) *s = source_new(sd_bus_get_fd(bus), dbus_dispatch, oeffis);
|
||||
source_never_close_fd(s); /* the bus object handles the fd */
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ extern "C" {
|
|||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* @addtogroup oeffis 🚌 liboeffis - An XDG RemoteDesktop portal wrapper API
|
||||
* @addtogroup liboeffis 🚌 liboeffis - An XDG RemoteDesktop portal wrapper API
|
||||
*
|
||||
* liboeffis is a helper library for applications that do not want to or cannot
|
||||
* interact with the XDG RemoteDesktop DBus portal directly.
|
||||
|
|
@ -143,6 +143,17 @@ oeffis_get_eis_fd(struct oeffis *oeffis);
|
|||
* bitmask in the XDG RemoteDesktop portal.
|
||||
*/
|
||||
enum oeffis_device {
|
||||
/**
|
||||
* Request all devices. Note that this is not a simple mask of the
|
||||
* other enum values here but it relies on the RemoteDesktop portal
|
||||
* stack to use the default "all" value. The exact set of devices
|
||||
* is thus dependent on the implementation - running against an
|
||||
* older portal stack may mean a subset of the devices here, running
|
||||
* against a portal stack more modern than liboeffis may mean
|
||||
* selecting for devices unknown to liboeffis at compile time.
|
||||
*
|
||||
* If in doubt, use an explicit mask of desired devices instead.
|
||||
*/
|
||||
OEFFIS_DEVICE_ALL_DEVICES = 0,
|
||||
OEFFIS_DEVICE_KEYBOARD = (1 << 0),
|
||||
OEFFIS_DEVICE_POINTER = (1 << 1),
|
||||
|
|
|
|||
284
src/meson.build
284
src/meson.build
|
|
@ -4,6 +4,7 @@ src_libutil = files(
|
|||
'util-list.c',
|
||||
'util-logger.c',
|
||||
'util-memfile.c',
|
||||
'util-memmap.c',
|
||||
'util-sources.c',
|
||||
'util-strings.c',
|
||||
)
|
||||
|
|
@ -12,6 +13,7 @@ lib_util = static_library('util',
|
|||
src_libutil,
|
||||
include_directories: [inc_builddir],
|
||||
dependencies: [dep_math, dep_epoll],
|
||||
gnu_symbol_visibility: 'hidden',
|
||||
)
|
||||
|
||||
dep_libutil = declare_dependency(link_with: lib_util)
|
||||
|
|
@ -23,127 +25,168 @@ brei_proto_h_template = files('brei-proto.h.tmpl')
|
|||
brei_proto_headers = custom_target('brei-proto-headers',
|
||||
input: protocol_xml,
|
||||
output: ['brei-proto.h'],
|
||||
command: [scanner, '--component=brei', '--output=@OUTPUT@', '@INPUT@', brei_proto_h_template])
|
||||
|
||||
ei_proto_headers = custom_target('ei-proto-headers',
|
||||
input: protocol_xml,
|
||||
output: ['ei-proto.h'],
|
||||
command: [scanner, '--component=ei', '--output=@OUTPUT@', '@INPUT@', proto_h_template])
|
||||
ei_proto_sources = custom_target('ei-proto-sources',
|
||||
input: protocol_xml,
|
||||
output: ['ei-proto.c'],
|
||||
command: [scanner, '--component=ei', '--output=@OUTPUT@', '@INPUT@',
|
||||
'--jinja-extra-data={"headerfile": "ei-proto.h"}', proto_c_template])
|
||||
src_libei = files(
|
||||
'brei-shared.c',
|
||||
'libei.c',
|
||||
'libei-button.c',
|
||||
'libei-callback.c',
|
||||
'libei-connection.c',
|
||||
'libei-device.c',
|
||||
'libei-event.c',
|
||||
'libei-fd.c',
|
||||
'libei-handshake.c',
|
||||
'libei-keyboard.c',
|
||||
'libei-log.c',
|
||||
'libei-pingpong.c',
|
||||
'libei-pointer-absolute.c',
|
||||
'libei-pointer.c',
|
||||
'libei-region.c',
|
||||
'libei-region.c',
|
||||
'libei-scroll.c',
|
||||
'libei-seat.c',
|
||||
'libei-socket.c',
|
||||
'libei-touchscreen.c',
|
||||
) + [brei_proto_headers, ei_proto_headers, ei_proto_sources]
|
||||
|
||||
deps_libei = [
|
||||
dep_libutil,
|
||||
]
|
||||
|
||||
lib_libei = library('ei',
|
||||
src_libei,
|
||||
dependencies: deps_libei,
|
||||
include_directories: [inc_builddir],
|
||||
gnu_symbol_visibility: 'hidden',
|
||||
version: soname,
|
||||
install: true
|
||||
)
|
||||
libei_headers = files('libei.h')
|
||||
install_headers(libei_headers, subdir: libei_api_dir)
|
||||
|
||||
dep_libei = declare_dependency(link_with: lib_libei,
|
||||
include_directories: [inc_src])
|
||||
meson.override_dependency('libei', dep_libei)
|
||||
|
||||
pkgconfig.generate(lib_libei,
|
||||
filebase: 'libei-@0@'.format(libei_api_version),
|
||||
name: 'libEI',
|
||||
description: 'Emulated Input client library',
|
||||
subdirs: libei_api_dir,
|
||||
version: meson.project_version(),
|
||||
libraries: lib_libei,
|
||||
command: [
|
||||
scanner,
|
||||
'--component=brei',
|
||||
'--output=@OUTPUT@',
|
||||
'@INPUT@',
|
||||
brei_proto_h_template
|
||||
],
|
||||
)
|
||||
|
||||
eis_proto_headers = custom_target('eis-proto-headers',
|
||||
input: protocol_xml,
|
||||
output: ['eis-proto.h'],
|
||||
command: [scanner, '--component=eis', '--output=@OUTPUT@', '@INPUT@', proto_h_template])
|
||||
eis_proto_sources = custom_target('eis-proto-sources',
|
||||
input: protocol_xml,
|
||||
output: ['eis-proto.c'],
|
||||
command: [scanner, '--component=eis', '--output=@OUTPUT@', '@INPUT@',
|
||||
'--jinja-extra-data={"headerfile": "eis-proto.h"}', proto_c_template])
|
||||
if build_libei
|
||||
ei_proto_headers = custom_target('ei-proto-headers',
|
||||
input: protocol_xml,
|
||||
output: ['ei-proto.h'],
|
||||
command: [
|
||||
scanner,
|
||||
'--component=ei',
|
||||
'--output=@OUTPUT@',
|
||||
'@INPUT@',
|
||||
proto_h_template,
|
||||
],
|
||||
)
|
||||
ei_proto_sources = custom_target('ei-proto-sources',
|
||||
input: protocol_xml,
|
||||
output: ['ei-proto.c'],
|
||||
command: [
|
||||
scanner,
|
||||
'--component=ei',
|
||||
'--output=@OUTPUT@',
|
||||
'@INPUT@',
|
||||
'--jinja-extra-data={"headerfile": "ei-proto.h"}',
|
||||
proto_c_template
|
||||
],
|
||||
)
|
||||
src_libei = files(
|
||||
'brei-shared.c',
|
||||
'libei.c',
|
||||
'libei-button.c',
|
||||
'libei-callback.c',
|
||||
'libei-connection.c',
|
||||
'libei-device.c',
|
||||
'libei-event.c',
|
||||
'libei-fd.c',
|
||||
'libei-handshake.c',
|
||||
'libei-keyboard.c',
|
||||
'libei-log.c',
|
||||
'libei-pingpong.c',
|
||||
'libei-ping.c',
|
||||
'libei-pointer-absolute.c',
|
||||
'libei-pointer.c',
|
||||
'libei-region.c',
|
||||
'libei-region.c',
|
||||
'libei-scroll.c',
|
||||
'libei-seat.c',
|
||||
'libei-socket.c',
|
||||
'libei-touchscreen.c',
|
||||
) + [brei_proto_headers, ei_proto_headers, ei_proto_sources]
|
||||
|
||||
src_libeis = files(
|
||||
'brei-shared.c',
|
||||
'libeis-button.c',
|
||||
'libeis-callback.c',
|
||||
'libeis-client.c',
|
||||
'libeis-connection.c',
|
||||
'libeis-device.c',
|
||||
'libeis-event.c',
|
||||
'libeis-fd.c',
|
||||
'libeis-handshake.c',
|
||||
'libeis-keyboard.c',
|
||||
'libeis-log.c',
|
||||
'libeis-pingpong.c',
|
||||
'libeis-pointer-absolute.c',
|
||||
'libeis-pointer.c',
|
||||
'libeis-region.c',
|
||||
'libeis-scroll.c',
|
||||
'libeis-seat.c',
|
||||
'libeis-socket.c',
|
||||
'libeis-touchscreen.c',
|
||||
'libeis.c',
|
||||
) + [brei_proto_headers, eis_proto_headers, eis_proto_sources]
|
||||
deps_libei = [
|
||||
dep_libutil,
|
||||
]
|
||||
|
||||
lib_libeis = library('eis',
|
||||
src_libeis,
|
||||
dependencies: [dep_libutil],
|
||||
include_directories: [inc_builddir],
|
||||
gnu_symbol_visibility: 'hidden',
|
||||
version: soname,
|
||||
install: true
|
||||
)
|
||||
libeis_headers = files('libeis.h')
|
||||
install_headers(libeis_headers, subdir: libei_api_dir)
|
||||
lib_libei = library('ei',
|
||||
src_libei,
|
||||
dependencies: deps_libei,
|
||||
include_directories: [inc_builddir],
|
||||
gnu_symbol_visibility: 'hidden',
|
||||
version: soname,
|
||||
install: true,
|
||||
)
|
||||
libei_headers = files('libei.h')
|
||||
install_headers(libei_headers, subdir: libei_api_dir)
|
||||
|
||||
dep_libeis = declare_dependency(link_with: lib_libeis,
|
||||
include_directories: [inc_src])
|
||||
meson.override_dependency('libeis', dep_libeis)
|
||||
dep_libei = declare_dependency(link_with: lib_libei,
|
||||
include_directories: [inc_src],
|
||||
)
|
||||
meson.override_dependency('libei-@0@'.format(libei_api_version), dep_libei)
|
||||
|
||||
pkgconfig.generate(lib_libeis,
|
||||
filebase: 'libeis-@0@'.format(libei_api_version),
|
||||
name: 'libEIS',
|
||||
description: 'Emulated Input server library',
|
||||
subdirs: libei_api_dir,
|
||||
version: meson.project_version(),
|
||||
libraries: lib_libeis,
|
||||
)
|
||||
pkgconfig.generate(lib_libei,
|
||||
filebase: 'libei-@0@'.format(libei_api_version),
|
||||
name: 'libEI',
|
||||
description: 'Emulated Input client library',
|
||||
subdirs: libei_api_dir,
|
||||
version: meson.project_version(),
|
||||
libraries: lib_libei,
|
||||
)
|
||||
endif
|
||||
|
||||
if build_libeis
|
||||
eis_proto_headers = custom_target('eis-proto-headers',
|
||||
input: protocol_xml,
|
||||
output: ['eis-proto.h'],
|
||||
command: [
|
||||
scanner,
|
||||
'--component=eis',
|
||||
'--output=@OUTPUT@',
|
||||
'@INPUT@',
|
||||
proto_h_template
|
||||
],
|
||||
)
|
||||
eis_proto_sources = custom_target('eis-proto-sources',
|
||||
input: protocol_xml,
|
||||
output: ['eis-proto.c'],
|
||||
command: [
|
||||
scanner,
|
||||
'--component=eis',
|
||||
'--output=@OUTPUT@',
|
||||
'@INPUT@',
|
||||
'--jinja-extra-data={"headerfile": "eis-proto.h"}',
|
||||
proto_c_template
|
||||
],
|
||||
)
|
||||
|
||||
src_libeis = files(
|
||||
'brei-shared.c',
|
||||
'libeis-button.c',
|
||||
'libeis-callback.c',
|
||||
'libeis-client.c',
|
||||
'libeis-connection.c',
|
||||
'libeis-device.c',
|
||||
'libeis-event.c',
|
||||
'libeis-fd.c',
|
||||
'libeis-handshake.c',
|
||||
'libeis-keyboard.c',
|
||||
'libeis-log.c',
|
||||
'libeis-pingpong.c',
|
||||
'libeis-ping.c',
|
||||
'libeis-pointer-absolute.c',
|
||||
'libeis-pointer.c',
|
||||
'libeis-region.c',
|
||||
'libeis-scroll.c',
|
||||
'libeis-seat.c',
|
||||
'libeis-socket.c',
|
||||
'libeis-touchscreen.c',
|
||||
'libeis.c',
|
||||
) + [brei_proto_headers, eis_proto_headers, eis_proto_sources]
|
||||
|
||||
lib_libeis = library('eis',
|
||||
src_libeis,
|
||||
dependencies: [dep_libutil],
|
||||
include_directories: [inc_builddir],
|
||||
gnu_symbol_visibility: 'hidden',
|
||||
version: soname,
|
||||
install: true,
|
||||
)
|
||||
libeis_headers = files('libeis.h')
|
||||
install_headers(libeis_headers, subdir: libei_api_dir)
|
||||
|
||||
dep_libeis = declare_dependency(link_with: lib_libeis,
|
||||
include_directories: [inc_src],
|
||||
)
|
||||
meson.override_dependency('libeis-@0@'.format(libei_api_version), dep_libeis)
|
||||
|
||||
pkgconfig.generate(lib_libeis,
|
||||
filebase: 'libeis-@0@'.format(libei_api_version),
|
||||
name: 'libEIS',
|
||||
description: 'Emulated Input server library',
|
||||
subdirs: libei_api_dir,
|
||||
version: meson.project_version(),
|
||||
libraries: lib_libeis,
|
||||
)
|
||||
endif
|
||||
|
||||
build_oeffis = dep_sdbus.found()
|
||||
summary({'liboeffis': build_oeffis}, section: 'Conditional Features')
|
||||
if build_oeffis
|
||||
src_liboeffis = files('liboeffis.c')
|
||||
deps_liboeffis = [dep_libutil, dep_sdbus]
|
||||
|
|
@ -154,14 +197,15 @@ if build_oeffis
|
|||
dependencies: deps_liboeffis,
|
||||
gnu_symbol_visibility: 'hidden',
|
||||
version: soname,
|
||||
install: true
|
||||
install: true,
|
||||
)
|
||||
liboeffis_headers = files('liboeffis.h')
|
||||
install_headers(liboeffis_headers, subdir: libei_api_dir)
|
||||
|
||||
dep_liboeffis = declare_dependency(link_with: lib_liboeffis,
|
||||
include_directories: [inc_src])
|
||||
meson.override_dependency('liboeffis', dep_liboeffis)
|
||||
include_directories: [inc_src],
|
||||
)
|
||||
meson.override_dependency('liboeffis-@0@'.format(libei_api_version), dep_liboeffis)
|
||||
|
||||
pkgconfig.generate(lib_liboeffis,
|
||||
filebase: 'liboeffis-@0@'.format(libei_api_version),
|
||||
|
|
@ -173,3 +217,9 @@ if build_oeffis
|
|||
)
|
||||
|
||||
endif
|
||||
|
||||
summary({
|
||||
'liboeffis': build_oeffis,
|
||||
'libei': build_libei,
|
||||
'libeis': build_libeis,
|
||||
}, section: 'Conditional Features', bool_yn: true)
|
||||
|
|
|
|||
194
src/util-io.c
194
src/util-io.c
|
|
@ -27,6 +27,8 @@
|
|||
#include "util-macros.h"
|
||||
#include "util-io.h"
|
||||
|
||||
_Static_assert(EAGAIN == EWOULDBLOCK, "Currently not supported, please file a bug");
|
||||
|
||||
int
|
||||
xread_with_fds(int fd, void *buf, size_t count, int **fds)
|
||||
{
|
||||
|
|
@ -47,14 +49,12 @@ xread_with_fds(int fd, void *buf, size_t count, int **fds)
|
|||
.msg_controllen = sizeof(control),
|
||||
};
|
||||
|
||||
int received = 0;
|
||||
with_signals_blocked(SIGALRM)
|
||||
received = xerrno(recvmsg(fd, &msg, 0));
|
||||
int received = xerrno(SYSCALL(recvmsg(fd, &msg, 0)));
|
||||
|
||||
if (received > 0) {
|
||||
*fds = NULL;
|
||||
|
||||
_cleanup_free_ int *fd_return = calloc(MAX_FDS + 1, sizeof(int));
|
||||
_cleanup_free_ int *fd_return = xalloc(MAX_FDS + 1 * sizeof(int));
|
||||
size_t idx = 0;
|
||||
|
||||
for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; hdr = CMSG_NXTHDR(&msg, hdr)) {
|
||||
|
|
@ -113,17 +113,14 @@ xsend_with_fd(int fd, const void *buf, size_t len, int *fds)
|
|||
header->cmsg_type = SCM_RIGHTS;
|
||||
memcpy(CMSG_DATA(CMSG_FIRSTHDR(&msg)), fds, nfds * sizeof(int));
|
||||
|
||||
with_signals_blocked(SIGALRM)
|
||||
return xerrno(sendmsg(fd, &msg, MSG_NOSIGNAL));
|
||||
|
||||
abort(); // can't be reached
|
||||
return xerrno(SYSCALL(sendmsg(fd, &msg, MSG_NOSIGNAL)));
|
||||
}
|
||||
|
||||
/* consider this struct opaque */
|
||||
struct iobuf {
|
||||
size_t sz;
|
||||
size_t len;
|
||||
char *data;
|
||||
uint8_t *data;
|
||||
int fds[32];
|
||||
};
|
||||
|
||||
|
|
@ -131,7 +128,7 @@ struct iobuf *
|
|||
iobuf_new(size_t size)
|
||||
{
|
||||
struct iobuf *buf = malloc(sizeof(*buf));
|
||||
char *data = malloc(size);
|
||||
uint8_t *data = malloc(size);
|
||||
|
||||
assert(buf);
|
||||
assert(data);
|
||||
|
|
@ -182,7 +179,7 @@ iobuf_pop(struct iobuf *buf, size_t nbytes)
|
|||
* The returned pointer only valid in the immediate scope, any iobuf
|
||||
* function may invalidate the pointer.
|
||||
*/
|
||||
const char *
|
||||
const uint8_t *
|
||||
iobuf_data(struct iobuf *buf)
|
||||
{
|
||||
return buf->data;
|
||||
|
|
@ -194,7 +191,7 @@ iobuf_data(struct iobuf *buf)
|
|||
* The returned pointer only valid in the immediate scope, any iobuf
|
||||
* function may invalidate the pointer.
|
||||
*/
|
||||
const char *
|
||||
const uint8_t *
|
||||
iobuf_data_end(struct iobuf *buf)
|
||||
{
|
||||
return buf->data + buf->len;
|
||||
|
|
@ -209,28 +206,36 @@ iobuf_take_fd(struct iobuf *buf)
|
|||
{
|
||||
int fd = buf->fds[0];
|
||||
if (fd != -1)
|
||||
memmove(buf->fds, buf->fds + 1, ARRAY_LENGTH(buf->fds) - 1);
|
||||
memmove(buf->fds, buf->fds + 1, (ARRAY_LENGTH(buf->fds) - 1) * sizeof(*buf->fds));
|
||||
return fd;
|
||||
}
|
||||
|
||||
static inline void
|
||||
iobuf_resize(struct iobuf *buf, size_t to_size)
|
||||
{
|
||||
char *newdata = realloc(buf->data, to_size);
|
||||
uint8_t *newdata = realloc(buf->data, to_size);
|
||||
assert(newdata);
|
||||
|
||||
buf->data = newdata;
|
||||
buf->sz = to_size;
|
||||
}
|
||||
|
||||
static inline void
|
||||
iobuf_extend(struct iobuf *buf, size_t extra)
|
||||
{
|
||||
size_t newsize = buf->len + extra;
|
||||
if (newsize > buf->sz)
|
||||
iobuf_resize(buf, newsize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the data bytes from the buffer. The caller must free() the data.
|
||||
* The buffer state is the same as iobuf_new() after this call.
|
||||
*/
|
||||
char *
|
||||
uint8_t *
|
||||
iobuf_take_data(struct iobuf *buf)
|
||||
{
|
||||
char *data = buf->data;
|
||||
uint8_t *data = buf->data;
|
||||
|
||||
buf->data = NULL;
|
||||
buf->len = 0;
|
||||
|
|
@ -244,25 +249,51 @@ iobuf_take_data(struct iobuf *buf)
|
|||
* size it is resized automatically.
|
||||
*/
|
||||
void
|
||||
iobuf_append(struct iobuf *buf, const char *data, size_t len)
|
||||
iobuf_append(struct iobuf *buf, const void *data, size_t len)
|
||||
{
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
if (buf->len + len > buf->sz) {
|
||||
size_t newsize = buf->len + len;
|
||||
iobuf_resize(buf, newsize);
|
||||
}
|
||||
|
||||
iobuf_extend(buf, len);
|
||||
memcpy(buf->data + buf->len, data, len);
|
||||
buf->len += len;
|
||||
}
|
||||
|
||||
void
|
||||
iobuf_append_u32(struct iobuf *buf, uint32_t data)
|
||||
{
|
||||
size_t len = 4;
|
||||
|
||||
iobuf_extend(buf, len);
|
||||
memcpy(buf->data + buf->len, &data, len);
|
||||
buf->len += len;
|
||||
}
|
||||
|
||||
void
|
||||
iobuf_append_u64(struct iobuf *buf, uint64_t data)
|
||||
{
|
||||
size_t len = 8;
|
||||
|
||||
iobuf_extend(buf, len);
|
||||
memcpy(buf->data + buf->len, &data, len);
|
||||
buf->len += len;
|
||||
}
|
||||
|
||||
void
|
||||
iobuf_append_f32(struct iobuf *buf, float data)
|
||||
{
|
||||
size_t len = 4;
|
||||
|
||||
iobuf_extend(buf, len);
|
||||
memcpy(buf->data + buf->len, &data, len);
|
||||
buf->len += len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend the given data to the buffer.
|
||||
*/
|
||||
void
|
||||
iobuf_prepend(struct iobuf *buf, const char *data, size_t len)
|
||||
iobuf_prepend(struct iobuf *buf, const void *data, size_t len)
|
||||
{
|
||||
if (len == 0)
|
||||
return;
|
||||
|
|
@ -281,7 +312,7 @@ int
|
|||
iobuf_append_fd(struct iobuf *buf, int fd)
|
||||
{
|
||||
/* Array must remain terminated by -1 */
|
||||
for (size_t idx = 0; idx < sizeof(buf->fds) - 1; idx ++) {
|
||||
for (size_t idx = 0; idx < ARRAY_LENGTH(buf->fds) - 1; idx ++) {
|
||||
if (buf->fds[idx] == -1) {
|
||||
int f = dup(fd);
|
||||
if (f == -1)
|
||||
|
|
@ -296,7 +327,7 @@ iobuf_append_fd(struct iobuf *buf, int fd)
|
|||
|
||||
/**
|
||||
* Append all available data from the file descriptor to the pointer. The
|
||||
* file descriptor shold be in O_NONBLOCK or this call will block. If the
|
||||
* file descriptor should be in O_NONBLOCK or this call will block. If the
|
||||
* data exceeds the current buffer size it is resized automatically.
|
||||
*
|
||||
* @return The number of bytes read or a negative errno on failure. Zero
|
||||
|
|
@ -325,10 +356,12 @@ iobuf_append_from_fd(struct iobuf *buf, int fd)
|
|||
|
||||
/**
|
||||
* Append all available data from the file descriptor to the pointer. The
|
||||
* file descriptor shold be in O_NONBLOCK or this call will block. If the
|
||||
* file descriptor should be in O_NONBLOCK or this call will block. If the
|
||||
* data exceeds the current buffer size it is resized automatically.
|
||||
*
|
||||
* Any file descriptors passed through the fd are placed
|
||||
* Any file descriptors passed through the fd are placed into the struct
|
||||
* iobuf's file descriptor array and can be retrieved in-order with
|
||||
* iobuf_take_fd().
|
||||
*
|
||||
* @return The number of bytes read or a negative errno on failure. Zero
|
||||
* indicates EOF.
|
||||
|
|
@ -425,6 +458,28 @@ MUNIT_TEST(test_iobuf_cleanup)
|
|||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_iobuf_take_fd)
|
||||
{
|
||||
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(10);
|
||||
const size_t nfds = ARRAY_LENGTH(buf->fds);
|
||||
int *last_fd = &buf->fds[nfds - 1]; /* always -1 */
|
||||
|
||||
for (size_t i = 0; i < nfds - 1; i++) {
|
||||
buf->fds[i] = 10 + i;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nfds - 1; i++) {
|
||||
int fd = iobuf_take_fd(buf);
|
||||
munit_assert_int(fd, ==, 10 + i);
|
||||
munit_assert_int(*last_fd, ==, -1);
|
||||
}
|
||||
|
||||
int fd = iobuf_take_fd(buf);
|
||||
munit_assert_int(fd, ==, -1);
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_iobuf_append_prepend)
|
||||
{
|
||||
/* Test appending data */
|
||||
|
|
@ -440,7 +495,7 @@ MUNIT_TEST(test_iobuf_append_prepend)
|
|||
munit_assert_size(buf->sz, ==, 10);
|
||||
|
||||
/* we don't have a trailing \0 */
|
||||
const char *bufdata = iobuf_data(buf);
|
||||
const uint8_t *bufdata = iobuf_data(buf);
|
||||
munit_assert_char(bufdata[0], ==, 'f');
|
||||
munit_assert_char(bufdata[1], ==, 'o');
|
||||
munit_assert_char(bufdata[2], ==, 'o');
|
||||
|
|
@ -472,7 +527,7 @@ MUNIT_TEST(test_iobuf_append_prepend)
|
|||
munit_assert_size(iobuf_len(buf), ==, expected_size);
|
||||
munit_assert_size(buf->sz, ==, expected_size);
|
||||
/* now we have a trailing \0 */
|
||||
munit_assert_string_equal(iobuf_data(buf), "barfoodata forcing resize");
|
||||
munit_assert_string_equal((const char *)iobuf_data(buf), "barfoodata forcing resize");
|
||||
|
||||
/* and again with prepending */
|
||||
const char prepend_data2[] = "second resize";
|
||||
|
|
@ -481,7 +536,48 @@ MUNIT_TEST(test_iobuf_append_prepend)
|
|||
|
||||
munit_assert_size(iobuf_len(buf), ==, expected_size);
|
||||
munit_assert_size(buf->sz, ==, expected_size);
|
||||
munit_assert_string_equal(iobuf_data(buf), "second resizebarfoodata forcing resize");
|
||||
munit_assert_string_equal((const char *)iobuf_data(buf), "second resizebarfoodata forcing resize");
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_iobuf_append_values)
|
||||
{
|
||||
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(10);
|
||||
|
||||
iobuf_append_u32(buf, -1);
|
||||
size_t expected_size = 4;
|
||||
|
||||
munit_assert_size(buf->len, ==, expected_size);
|
||||
munit_assert_size(iobuf_len(buf), ==, expected_size);
|
||||
munit_assert_size(buf->sz, ==, 10);
|
||||
|
||||
const uint8_t *bufdata = iobuf_data(buf);
|
||||
munit_assert_int(bufdata[0], ==, 0xff);
|
||||
munit_assert_int(bufdata[1], ==, 0xff);
|
||||
munit_assert_int(bufdata[2], ==, 0xff);
|
||||
munit_assert_int(bufdata[3], ==, 0xff);
|
||||
|
||||
free(iobuf_take_data(buf)); /* drops and replaces current buffer */
|
||||
|
||||
iobuf_append_u64(buf, 0xabababababababab);
|
||||
expected_size = 8;
|
||||
|
||||
munit_assert_size(buf->len, ==, expected_size);
|
||||
munit_assert_size(iobuf_len(buf), ==, expected_size);
|
||||
munit_assert_size(buf->sz, ==, 10);
|
||||
|
||||
bufdata = iobuf_data(buf);
|
||||
munit_assert_int((unsigned char)bufdata[0], ==, 0xab);
|
||||
munit_assert_int((unsigned char)bufdata[1], ==, 0xab);
|
||||
munit_assert_int((unsigned char)bufdata[2], ==, 0xab);
|
||||
munit_assert_int((unsigned char)bufdata[3], ==, 0xab);
|
||||
munit_assert_int((unsigned char)bufdata[4], ==, 0xab);
|
||||
munit_assert_int((unsigned char)bufdata[5], ==, 0xab);
|
||||
munit_assert_int((unsigned char)bufdata[6], ==, 0xab);
|
||||
munit_assert_int((unsigned char)bufdata[7], ==, 0xab);
|
||||
|
||||
free(iobuf_take_data(buf));
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
|
@ -500,7 +596,7 @@ MUNIT_TEST(test_iobuf_prepend_empty_buffer)
|
|||
munit_assert_size(buf->sz, ==, 10);
|
||||
|
||||
/* we don't have a trailing \0 */
|
||||
const char *bufdata = iobuf_data(buf);
|
||||
const uint8_t *bufdata = iobuf_data(buf);
|
||||
munit_assert_char(bufdata[0], ==, 'f');
|
||||
munit_assert_char(bufdata[1], ==, 'o');
|
||||
munit_assert_char(bufdata[2], ==, 'o');
|
||||
|
|
@ -519,7 +615,7 @@ MUNIT_TEST(test_iobuf_pop)
|
|||
munit_assert_size(iobuf_len(buf), ==, 3);
|
||||
|
||||
/* we don't have a trailing \0 */
|
||||
const char *bufdata = iobuf_data(buf);
|
||||
const uint8_t *bufdata = iobuf_data(buf);
|
||||
munit_assert_char(bufdata[0], ==, 'b');
|
||||
munit_assert_char(bufdata[1], ==, 'a');
|
||||
munit_assert_char(bufdata[2], ==, 'r');
|
||||
|
|
@ -532,7 +628,7 @@ MUNIT_TEST(test_iobuf_append_short)
|
|||
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(10);
|
||||
|
||||
/* Append only the first few bytes out of a larger data field, i.e.
|
||||
* make sure we honor the lenght parameter */
|
||||
* make sure we honor the length parameter */
|
||||
const char data[] = "foobar";
|
||||
const char nullbyte = '\0';
|
||||
iobuf_append(buf, data, 3);
|
||||
|
|
@ -541,8 +637,7 @@ MUNIT_TEST(test_iobuf_append_short)
|
|||
munit_assert_size(buf->len, ==, 4);
|
||||
munit_assert_size(iobuf_len(buf), ==, 4);
|
||||
munit_assert_size(buf->sz, ==, 10);
|
||||
munit_assert_string_equal(iobuf_data(buf),
|
||||
"foo");
|
||||
munit_assert_string_equal((const char *)iobuf_data(buf), "foo");
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
|
@ -572,7 +667,7 @@ MUNIT_TEST(test_iobuf_append_fd)
|
|||
/* so we can do strcmp */
|
||||
const char nullbyte = '\0';
|
||||
iobuf_append(buf, &nullbyte, 1);
|
||||
munit_assert_string_equal(iobuf_data(buf), "foob");
|
||||
munit_assert_string_equal((const char *)iobuf_data(buf), "foob");
|
||||
|
||||
/* read when there's nothing waiting */
|
||||
int blocking_read = iobuf_append_from_fd(buf, rd);
|
||||
|
|
@ -605,6 +700,30 @@ MUNIT_TEST(test_iobuf_append_fd)
|
|||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_iobuf_append_fd_too_many)
|
||||
{
|
||||
_cleanup_fclose_ FILE *fp = tmpfile();
|
||||
munit_assert_ptr_not_null(fp);
|
||||
int fd = fileno(fp);
|
||||
|
||||
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(20);
|
||||
const size_t nfds = ARRAY_LENGTH(buf->fds);
|
||||
int *last_fd = &buf->fds[nfds - 1]; /* always -1 */
|
||||
int err = 0;
|
||||
size_t count = 0;
|
||||
|
||||
/* 32 fds hardcoded in the struct, last one is always -1 */
|
||||
for (count = 0; err == 0 && count < nfds + 1; count++) {
|
||||
err = iobuf_append_fd(buf, fd);
|
||||
munit_assert_int(*last_fd, ==, -1);
|
||||
}
|
||||
|
||||
munit_assert_int(count, ==, 32);
|
||||
munit_assert_int(err, ==, -ENOMEM);
|
||||
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
MUNIT_TEST(test_iobuf_recv_fd)
|
||||
{
|
||||
int fds[2];
|
||||
|
|
@ -614,6 +733,7 @@ MUNIT_TEST(test_iobuf_recv_fd)
|
|||
_cleanup_close_ int left = fds[0];
|
||||
_cleanup_close_ int right = fds[1];
|
||||
_cleanup_fclose_ FILE *fp = tmpfile();
|
||||
munit_assert_ptr_not_null(fp);
|
||||
|
||||
/* actual message data to be sent */
|
||||
char data[] = "some data\n";
|
||||
|
|
@ -664,7 +784,9 @@ MUNIT_TEST(test_pass_fd)
|
|||
/* Write some data to the file on it's real fd */
|
||||
for (size_t idx = 0; idx < ARRAY_LENGTH(fps); idx++) {
|
||||
_cleanup_free_ char *buf = xaprintf("foo %zu\n", idx);
|
||||
munit_assert_ptr_not_null(buf);
|
||||
FILE *fp = fps[idx];
|
||||
munit_assert_ptr_not_null(fp);
|
||||
fwrite(buf, strlen(buf) + 1, 1, fp);
|
||||
fflush(fp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -63,6 +64,8 @@ signals_block(int signal, ...)
|
|||
}
|
||||
sigprocmask(SIG_BLOCK, &new_mask, &old_mask);
|
||||
|
||||
va_end(sigs);
|
||||
|
||||
return old_mask;
|
||||
}
|
||||
|
||||
|
|
@ -123,8 +126,8 @@ xerrno(int value) {
|
|||
static inline int
|
||||
xclose(int fd) {
|
||||
if (fd != -1) {
|
||||
with_signals_blocked(SIGALRM)
|
||||
close(fd);
|
||||
/* Not SYSCALL(), see libei MR!261#note_2131802 */
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
|
@ -143,10 +146,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FILE *, fclose);
|
|||
static inline int
|
||||
xread(int fd, void *buf, size_t count)
|
||||
{
|
||||
with_signals_blocked(SIGALRM)
|
||||
return xerrno(read(fd, buf, count));
|
||||
|
||||
abort(); // can't be reached
|
||||
return xerrno(SYSCALL(read(fd, buf, count)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -166,9 +166,7 @@ xread_with_fds(int fd, void *buf, size_t count, int **fds);
|
|||
static inline int
|
||||
xwrite(int fd, const void *buf, size_t count)
|
||||
{
|
||||
with_signals_blocked(SIGALRM)
|
||||
return xerrno(write(fd, buf, count));
|
||||
abort(); // can't be reached
|
||||
return xerrno(SYSCALL(write(fd, buf, count)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -179,10 +177,7 @@ xwrite(int fd, const void *buf, size_t count)
|
|||
static inline int
|
||||
xsend(int fd, const void *buf, size_t len)
|
||||
{
|
||||
with_signals_blocked(SIGALRM)
|
||||
return xerrno(send(fd, buf, len, MSG_NOSIGNAL));
|
||||
|
||||
abort(); // can't be reached
|
||||
return xerrno(SYSCALL(send(fd, buf, len, MSG_NOSIGNAL)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -191,10 +186,7 @@ xsend(int fd, const void *buf, size_t len)
|
|||
static inline int
|
||||
xpipe2(int pipefd[2], int flags)
|
||||
{
|
||||
with_signals_blocked(SIGALRM)
|
||||
return pipe2(pipefd, flags);
|
||||
|
||||
abort(); // can't be reached
|
||||
return SYSCALL(pipe2(pipefd, flags));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -203,10 +195,7 @@ xpipe2(int pipefd[2], int flags)
|
|||
static inline int
|
||||
xdup(int fd)
|
||||
{
|
||||
with_signals_blocked(SIGALRM)
|
||||
return dup(fd);
|
||||
|
||||
abort(); // can't be reached
|
||||
return SYSCALL(dup(fd));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -232,17 +221,16 @@ xconnect(const char *path)
|
|||
if (!xsnprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path))
|
||||
return -EINVAL;
|
||||
|
||||
int sockfd = -1;
|
||||
|
||||
with_signals_blocked(SIGALRM) {
|
||||
sockfd = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0);
|
||||
if (sockfd == -1)
|
||||
break;
|
||||
int sockfd = xerrno(SYSCALL(socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0)));
|
||||
if (sockfd < 0)
|
||||
return sockfd;
|
||||
|
||||
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1)
|
||||
break;
|
||||
}
|
||||
return sockfd == -1 ? -errno : sockfd;
|
||||
int rc = xerrno(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)));
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -265,7 +253,7 @@ iobuf_len(struct iobuf *buf);
|
|||
* The returned pointer only valid in the immediate scope, any iobuf
|
||||
* function may invalidate the pointer.
|
||||
*/
|
||||
const char *
|
||||
const uint8_t *
|
||||
iobuf_data(struct iobuf *buf);
|
||||
|
||||
/**
|
||||
|
|
@ -274,7 +262,7 @@ iobuf_data(struct iobuf *buf);
|
|||
* The returned pointer only valid in the immediate scope, any iobuf
|
||||
* function may invalidate the pointer.
|
||||
*/
|
||||
const char *
|
||||
const uint8_t *
|
||||
iobuf_data_end(struct iobuf *buf);
|
||||
|
||||
/**
|
||||
|
|
@ -288,7 +276,7 @@ iobuf_take_fd(struct iobuf *buf);
|
|||
* Remove the data bytes from the buffer. The caller must free() the data.
|
||||
* The buffer state is the same as iobuf_new() after this call.
|
||||
*/
|
||||
char *
|
||||
uint8_t *
|
||||
iobuf_take_data(struct iobuf *buf);
|
||||
|
||||
/**
|
||||
|
|
@ -302,14 +290,35 @@ iobuf_pop(struct iobuf *buf, size_t nbytes);
|
|||
* size it is resized automatically.
|
||||
*/
|
||||
void
|
||||
iobuf_append(struct iobuf *buf, const char *data, size_t len);
|
||||
iobuf_append(struct iobuf *buf, const void *data, size_t len);
|
||||
|
||||
/**
|
||||
* Append one 32-bit value to the buffer. If the data exceeds the current buffer
|
||||
* size it is resized automatically.
|
||||
*/
|
||||
void
|
||||
iobuf_append_u32(struct iobuf *buf, uint32_t data);
|
||||
|
||||
/**
|
||||
* Append one 64-bit value to the buffer. If the data exceeds the current buffer
|
||||
* size it is resized automatically.
|
||||
*/
|
||||
void
|
||||
iobuf_append_u64(struct iobuf *buf, uint64_t data);
|
||||
|
||||
/**
|
||||
* Append one 32-bit float to the buffer. If the data exceeds the current buffer
|
||||
* size it is resized automatically.
|
||||
*/
|
||||
void
|
||||
iobuf_append_f32(struct iobuf *buf, float data);
|
||||
|
||||
/**
|
||||
* Prepend len bytes to the buffer. If the data exceeds the current buffer
|
||||
* size it is resized automatically.
|
||||
*/
|
||||
void
|
||||
iobuf_prepend(struct iobuf *buf, const char *data, size_t len);
|
||||
iobuf_prepend(struct iobuf *buf, const void *data, size_t len);
|
||||
|
||||
/**
|
||||
* Append a file descriptor to the buffer. The file descriptor is dup()ed.
|
||||
|
|
@ -321,7 +330,7 @@ iobuf_append_fd(struct iobuf *buf, int fd);
|
|||
|
||||
/**
|
||||
* Append all available data from the file descriptor to the pointer. The
|
||||
* file descriptor shold be in O_NONBLOCK or this call will block. If the
|
||||
* file descriptor should be in O_NONBLOCK or this call will block. If the
|
||||
* data exceeds the current buffer size it is resized automatically.
|
||||
*
|
||||
* @return The number of bytes read or a negative errno on failure. Zero
|
||||
|
|
@ -332,7 +341,7 @@ iobuf_append_from_fd(struct iobuf *buf, int fd);
|
|||
|
||||
/**
|
||||
* Append all available data from the file descriptor to the pointer. The
|
||||
* file descriptor shold be in O_NONBLOCK or this call will block. If the
|
||||
* file descriptor should be in O_NONBLOCK or this call will block. If the
|
||||
* data exceeds the current buffer size it is resized automatically.
|
||||
*
|
||||
* Any file descriptors passed through the fd are placed
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef HAVE_C23_AUTO
|
||||
#define auto __auto_type
|
||||
#endif
|
||||
|
||||
#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
|
||||
#define ARRAY_FOR_EACH(_arr, _elem) \
|
||||
|
|
@ -42,6 +47,12 @@
|
|||
*/
|
||||
#define CASE_RETURN_STRING(_x) case _x: return #_x
|
||||
|
||||
#define SYSCALL(call) ({ \
|
||||
int rc_; \
|
||||
do { rc_ = call; } while (rc_ == -1 && errno == EINTR); \
|
||||
rc_; })
|
||||
|
||||
|
||||
#define min(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#define max(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
|
|
@ -59,3 +70,17 @@
|
|||
#define run_only_once \
|
||||
static int _once_per_##__func__ = 0; \
|
||||
for (; _once_per_##__func__ == 0; _once_per_##__func__ = 1)
|
||||
|
||||
#define trace(...) \
|
||||
do { \
|
||||
char buf_[1024]; \
|
||||
snprintf(buf_, sizeof(buf_), __VA_ARGS__); \
|
||||
printf("\x1B[0;34m" "%s():%d - " "\x1B[0;31m" "%s" "\x1B[0m" "\n", __func__, __LINE__, buf_); \
|
||||
} while (0)
|
||||
|
||||
#define etrace(...) \
|
||||
do { \
|
||||
char buf_[1024]; \
|
||||
snprintf(buf_, sizeof(buf_), __VA_ARGS__); \
|
||||
fprintf(stderr, "\x1B[0;34m" "%s():%d - " "\x1B[0;31m" "%s" "\x1B[0m" "\n", __func__, __LINE__, buf_); \
|
||||
} while (0)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ xalloc(size_t size)
|
|||
{
|
||||
void *p;
|
||||
|
||||
/* We never need to alloc anything more than 1,5 MB so we can assume
|
||||
/* We never need to alloc anything more than 1.5 MB so we can assume
|
||||
* if we ever get above that something's going wrong */
|
||||
if (size > 1536 * 1024)
|
||||
assert(!"bug: internal malloc size limit exceeded");
|
||||
|
|
|
|||
|
|
@ -66,7 +66,11 @@ memfile_new(const char *data, size_t sz) {
|
|||
return NULL;
|
||||
|
||||
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
|
||||
int rc = posix_fallocate(fd, 0, sz);
|
||||
|
||||
int rc;
|
||||
with_signals_blocked(SIGALRM) {
|
||||
rc = SYSCALL(posix_fallocate(fd, 0, sz));
|
||||
}
|
||||
if (rc < 0)
|
||||
return NULL;
|
||||
|
||||
|
|
@ -74,7 +78,7 @@ memfile_new(const char *data, size_t sz) {
|
|||
memfile->size = sz;
|
||||
fd = -1;
|
||||
|
||||
void *map = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE, memfile->fd, 0);
|
||||
void *map = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_SHARED, memfile->fd, 0);
|
||||
if (map == MAP_FAILED)
|
||||
return NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct memfile;
|
||||
|
||||
#if HAVE_MEMFD_CREATE
|
||||
|
|
|
|||
70
src/util-memmap.c
Normal file
70
src/util-memmap.c
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* SPDX-License-Identifier: MIT */
|
||||
/*
|
||||
* Copyright © 2024 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "util-memmap.h"
|
||||
#include "util-mem.h"
|
||||
|
||||
#include "util-object.h"
|
||||
|
||||
struct memmap {
|
||||
struct object object;
|
||||
void *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static void
|
||||
memmap_destroy(struct memmap *memmap)
|
||||
{
|
||||
if (memmap->data) {
|
||||
munmap(memmap->data, memmap->size);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
OBJECT_IMPLEMENT_CREATE(memmap);
|
||||
OBJECT_IMPLEMENT_UNREF_CLEANUP(memmap);
|
||||
OBJECT_IMPLEMENT_REF(memmap);
|
||||
OBJECT_IMPLEMENT_GETTER(memmap, size, size_t);
|
||||
OBJECT_IMPLEMENT_GETTER(memmap, data, void*);
|
||||
|
||||
struct memmap *
|
||||
memmap_new(int fd, size_t sz) {
|
||||
_unref_(memmap) *memmap = memmap_create(NULL);
|
||||
|
||||
void *map = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (map == MAP_FAILED)
|
||||
return NULL;
|
||||
|
||||
memmap->data = map;
|
||||
memmap->size = sz;
|
||||
|
||||
return steal(&memmap);
|
||||
}
|
||||
46
src/util-memmap.h
Normal file
46
src/util-memmap.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* SPDX-License-Identifier: MIT */
|
||||
/*
|
||||
* Copyright © 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct memmap;
|
||||
|
||||
struct memmap *
|
||||
memmap_new(int fd, size_t sz);
|
||||
|
||||
struct memmap *
|
||||
memmap_ref(struct memmap *memmap);
|
||||
|
||||
struct memmap *
|
||||
memmap_unref(struct memmap *memmap);
|
||||
|
||||
size_t
|
||||
memmap_get_size(struct memmap *memmap);
|
||||
|
||||
void *
|
||||
memmap_get_data(struct memmap *memmap);
|
||||
|
|
@ -34,30 +34,81 @@
|
|||
* __start and __stop point to the start and end of that section. See the
|
||||
* __attribute__(section) documentation.
|
||||
*/
|
||||
extern const struct test_function __start_test_functions_section, __stop_test_functions_section;
|
||||
DECLARE_TEST_SECTION();
|
||||
DECLARE_GLOBAL_SETUP_SECTION();
|
||||
|
||||
/* If the tests don't use MUNIT_GLOBAL_SETUP the the above has no linkage, so
|
||||
* let's always define a noop internal one.
|
||||
*/
|
||||
MUNIT_GLOBAL_SETUP(__internal)
|
||||
{
|
||||
}
|
||||
|
||||
int
|
||||
munit_tests_run(int argc, char **argv)
|
||||
{
|
||||
size_t count = 1; /* For NULL-terminated entry */
|
||||
|
||||
for (const struct test_function *t = &__start_test_functions_section;
|
||||
t < &__stop_test_functions_section;
|
||||
t++)
|
||||
foreach_test(t) {
|
||||
count++;
|
||||
}
|
||||
|
||||
_cleanup_free_ MunitTest *tests = calloc(count, sizeof(*tests));
|
||||
size_t idx = 0;
|
||||
for (const struct test_function *t = &__start_test_functions_section;
|
||||
t < &__stop_test_functions_section;
|
||||
t++) {
|
||||
foreach_test(t) {
|
||||
size_t nparams = 0;
|
||||
MunitParameterEnum *parameters = calloc(MUNIT_TEST_MAX_PARAMS + 1, sizeof(*parameters)); /* null-terminated */
|
||||
|
||||
const char *name = NULL;
|
||||
char **values = NULL;
|
||||
for (size_t i = 0; t->params[i]; i++) {
|
||||
if (t->params[i][0] == '@') {
|
||||
if (name != NULL) {
|
||||
assert(nparams < MUNIT_TEST_MAX_PARAMS);
|
||||
assert(strv_len(values) > 0);
|
||||
/* Not stripping the @! */
|
||||
parameters[nparams].name = xstrdup(name);
|
||||
parameters[nparams].values = steal(&values);
|
||||
++nparams;
|
||||
}
|
||||
name = t->params[i];
|
||||
} else {
|
||||
assert(i > 0); /* First one must be a name */
|
||||
values = strv_append_strdup(values, t->params[i]);
|
||||
}
|
||||
}
|
||||
if (name != NULL) {
|
||||
assert(nparams < MUNIT_TEST_MAX_PARAMS);
|
||||
assert(strv_len(values) > 0);
|
||||
/* Not stripping the @! */
|
||||
parameters[nparams].name = xstrdup(name);
|
||||
parameters[nparams].values = steal(&values);
|
||||
++nparams;
|
||||
}
|
||||
|
||||
MunitTest test = {
|
||||
.name = xaprintf("%s", t->name),
|
||||
.test = t->func,
|
||||
.parameters = parameters,
|
||||
};
|
||||
|
||||
tests[idx++] = test;
|
||||
}
|
||||
|
||||
struct munit_setup setup = {
|
||||
.argc = argc,
|
||||
.argv = argv,
|
||||
.userdata = NULL,
|
||||
};
|
||||
size_t setup_count = 0;
|
||||
foreach_setup(s) {
|
||||
if (!streq(s->name, "__internal")) {
|
||||
assert(setup_count == 0); /* Only one setup func per suite */
|
||||
s->func(&setup);
|
||||
++setup_count;
|
||||
}
|
||||
}
|
||||
|
||||
MunitSuite suite = {
|
||||
"",
|
||||
tests,
|
||||
|
|
@ -70,10 +121,19 @@ munit_tests_run(int argc, char **argv)
|
|||
const struct rlimit corelimit = { 0, 0 };
|
||||
setrlimit(RLIMIT_CORE, &corelimit);
|
||||
|
||||
int rc = munit_suite_main(&suite, NULL, argc, argv);
|
||||
int rc = munit_suite_main(&suite, setup.userdata, setup.argc, setup.argv);
|
||||
|
||||
for (idx = 0; idx < count; idx++) {
|
||||
MunitParameterEnum *parameters = tests[idx].parameters;
|
||||
while (parameters && parameters->name) {
|
||||
free(parameters->name);
|
||||
strv_free(parameters->values);
|
||||
parameters++;
|
||||
}
|
||||
|
||||
for (idx = 0; idx < count; idx++)
|
||||
free(tests[idx].name);
|
||||
free(tests[idx].parameters);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue