pax_global_header00006660000000000000000000000064145600533600014514gustar00rootroot0000000000000052 comment=08f1d41085a6ae4bac7bc52abe2955d3354342cb libei-1.2.1/000077500000000000000000000000001456005336000126015ustar00rootroot00000000000000libei-1.2.1/.editorconfig000066400000000000000000000007021456005336000152550ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true [meson.build] indent_style = space indent_size = 4 tab_width = 4 [*.md] indent_style = space tab_width = 4 [*.{c,h,tmpl}] indent_style = tab tab_width = 8 [*.proto] indent_style = space indent_size = 2 [*.py] indent_style = space indent_size = 4 charset = utf-8 [*.{yaml,yml,xml}] indent_style = space indent_size = 2 charset = utf-8 libei-1.2.1/.gitignore000066400000000000000000000000141456005336000145640ustar00rootroot00000000000000subprojects libei-1.2.1/.gitlab-ci.yml000066400000000000000000000253321456005336000152420ustar00rootroot00000000000000# vim: set expandtab shiftwidth=2 tabstop=8 textwidth=0 filetype=yaml: ######################################## # # # THIS FILE IS GENERATED, DO NOT EDIT # # # ######################################## .templates_sha: &template_sha 9f0eb526291fe74651fe1430cbd2397f4c0a819b # see https://docs.gitlab.com/ee/ci/yaml/#includefile include: - project: 'freedesktop/ci-templates' ref: *template_sha file: - '/templates/debian.yml' - '/templates/fedora.yml' - '/templates/ci-fairy.yml' stages: - prep # prep work like rebuilding the container images if there is a change - build # for actually building and testing things in a container - distro # distribs test - deploy workflow: rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' - if: $CI_PIPELINE_SOURCE == 'push' variables: ############################################################################### # This is the list of packages required to build libei with the default # # configuration. # # # # Run dnf install/apt-get install/.. with the list of packages for your # # distribution # # # # 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 python3-pyyaml hugo 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 python3-yaml ' DEBIAN_PIP_PACKAGES: 'meson ninja structlog strenum ' ############################ end of package lists ############################# # these tags should be updated each time the list of packages is updated # 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-12-13.0' DEBIAN_TAG: '2023-12-13.0' FDO_UPSTREAM_REPO: libinput/libei MESON_BUILDDIR: "builddir" NINJA_ARGS: '' MESON_ARGS: '' MESON_TEST_ARGS: '' GIT_DEPTH: 1 .policy: retry: max: 2 when: - runner_system_failure - stuck_or_timeout_failure # cancel run when a newer version is pushed to the branch interruptible: true ################################################################# # # # prep stage # # # ################################################################# fail-if-fork-is-not-public: stage: prep script: - | if [ $CI_PROJECT_VISIBILITY != "public" ]; then echo "*************************************************************************************" echo "Project visibility must be set to 'public'" echo "Change this in $CI_PROJECT_URL/edit under 'Visibility, project features, permissions'" echo "*************************************************************************************" exit 1 fi except: - main@libinput/libei # Re-generate the CI script and make sure it's the one currently checked in # If this job fails, re-generate the gitlab-ci.yml script, see # $SRCDIR/.gitlab-ci/generate-gitlab-ci.py # check-ci-script: extends: - .fdo.ci-fairy stage: prep script: - ci-fairy generate-template - git diff --exit-code && exit 0 || true - echo "Committed gitlab-ci.yml differs from generated gitlab-ci.yml. Please verify" - exit 1 # # Verify that commit messages are as expected, signed-off, etc. # check-commit: extends: - .fdo.ci-fairy stage: prep script: - ci-fairy check-commits --junit-xml=results.xml except: - main@libinput/libei variables: GIT_DEPTH: 100 artifacts: reports: junit: results.xml # # Verify that the merge request has the allow-collaboration checkbox ticked # check-merge-request: extends: - .fdo.ci-fairy stage: deploy script: - ci-fairy check-merge-request --require-allow-collaboration --junit-xml=results.xml artifacts: when: on_failure reports: junit: results.xml allow_failure: true # Format anything python with python-black # python-black: extends: - .fdo.ci-fairy stage: prep before_script: - python3 -m venv venv - source venv/bin/activate - pip3 install black script: - black --check --diff . proto/ei-scanner # Lint with Ruff # python-ruff: extends: - .fdo.ci-fairy stage: prep before_script: - python3 -m venv venv - source venv/bin/activate - pip3 install ruff script: - ruff check --ignore E741,E501 . proto/ei-scanner # # Build distribution-specific images used by the jobs in the build stage # fedora:39@container-prep: extends: - .fdo.container-build@fedora - .policy stage: prep variables: GIT_STRATEGY: none FDO_DISTRIBUTION_VERSION: '39' FDO_DISTRIBUTION_PACKAGES: $FEDORA_PACKAGES FDO_DISTRIBUTION_TAG: $FEDORA_TAG FDO_DISTRIBUTION_EXEC: 'pip install $FEDORA_PIP_PACKAGES' debian:bullseye@container-prep: extends: - .fdo.container-build@debian - .policy stage: prep variables: GIT_STRATEGY: none FDO_DISTRIBUTION_VERSION: 'bullseye' FDO_DISTRIBUTION_PACKAGES: $DEBIAN_PACKAGES FDO_DISTRIBUTION_TAG: $DEBIAN_TAG FDO_DISTRIBUTION_EXEC: 'pip install $DEBIAN_PIP_PACKAGES' ################################################################# # # # build stage # # # ################################################################# .build@template: extends: - .policy stage: build script: - .gitlab-ci/meson-build.sh --run-test artifacts: name: "meson-logs-$CI_JOB_NAME" when: always expire_in: 1 week paths: - $MESON_BUILDDIR/meson-logs reports: junit: $MESON_BUILDDIR/meson-logs/.junit.xml dependencies: [] # # Fedora # .fedora-build@template: extends: - .fdo.distribution-image@fedora - .build@template variables: MESON_TEST_ARGS: '--no-suite=python' FDO_DISTRIBUTION_VERSION: '39' FDO_DISTRIBUTION_TAG: $FEDORA_TAG needs: - "fedora:39@container-prep" default-build-release@fedora:39: stage: distro extends: - .fedora-build@template variables: MESON_ARGS: "-Dbuildtype=release" CFLAGS: "-Werror -Wno-error=vla-parameter" # munit triggers -Wvla-parameter build-no-libxkcommon-nodeps@fedora:39: extends: - .fedora-build@template before_script: - dnf remove -y libxkcommon-devel build-no-doxygen@fedora:39: extends: - .fedora-build@template variables: MESON_ARGS: "-Ddocumentation=[]" before_script: - dnf remove -y doxygen hugo valgrind@fedora:39: extends: - .fedora-build@template variables: MESON_TEST_ARGS: '--setup=valgrind' before_script: - dnf install -y valgrind pytest@fedora:39: extends: - .fedora-build@template variables: MESON_TEST_ARGS: '--suite=python' werror@fedora:39: extends: - .fedora-build@template variables: MESON_ARGS: '-Dwerror=true' allow_failure: true abicheck@fedora:39: extends: - .fedora-build@template before_script: - git clone --depth=1 https://gitlab.freedesktop.org/hadess/check-abi - | pushd check-abi meson setup _build meson compile -C _build meson install -C _build popd 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:39: 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.56.0" - .gitlab-ci/meson-build.sh --run-test ################################################################# # # # distro stage # # # ################################################################# fedora:39@default-build: stage: distro extends: - .build@template - .fdo.distribution-image@fedora variables: FDO_DISTRIBUTION_VERSION: '39' FDO_DISTRIBUTION_TAG: $FEDORA_TAG needs: - "fedora:39@container-prep" fedora:39@doc-build: stage: distro extends: - .build@template - .fdo.distribution-image@fedora variables: FDO_DISTRIBUTION_VERSION: '39' 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 artifacts: paths: - public debian:bullseye@default-build: stage: distro extends: - .build@template - .fdo.distribution-image@debian variables: FDO_DISTRIBUTION_VERSION: 'bullseye' FDO_DISTRIBUTION_TAG: $DEBIAN_TAG needs: - "debian:bullseye@container-prep" pages: stage: deploy extends: - .build@template - .fdo.distribution-image@fedora variables: FDO_DISTRIBUTION_VERSION: '39' FDO_DISTRIBUTION_TAG: $FEDORA_TAG MESON_ARGS: "-Ddocumentation=protocol,api" script: - echo "Nothing to do" dependencies: - "fedora:39@doc-build" needs: - "fedora:39@doc-build" only: refs: - main artifacts: paths: - public libei-1.2.1/.gitlab-ci/000077500000000000000000000000001456005336000145125ustar00rootroot00000000000000libei-1.2.1/.gitlab-ci/check-event-values.py000077500000000000000000000036471456005336000205720ustar00rootroot00000000000000#!/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 #include @@@ 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) libei-1.2.1/.gitlab-ci/ci.template000066400000000000000000000270221456005336000166450ustar00rootroot00000000000000# vim: set expandtab shiftwidth=2 tabstop=8 textwidth=0 filetype=yaml: {# You're looking at the template here, so you can ignore the below warning. This is the right file to edit #} ######################################## # # # THIS FILE IS GENERATED, DO NOT EDIT # # # ######################################## .templates_sha: &template_sha 9f0eb526291fe74651fe1430cbd2397f4c0a819b # see https://docs.gitlab.com/ee/ci/yaml/#includefile include: - project: 'freedesktop/ci-templates' ref: *template_sha file: {% for distro in distributions|sort(attribute="name") %} {% if not distro.does_not_have_ci_templates %} - '/templates/{{distro.name}}.yml' {% endif %} {% endfor %} - '/templates/ci-fairy.yml' stages: - prep # prep work like rebuilding the container images if there is a change - build # for actually building and testing things in a container - distro # distribs test - deploy workflow: rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' - if: $CI_PIPELINE_SOURCE == 'push' variables: ############################################################################### # This is the list of packages required to build libei with the default # # configuration. # # # # Run dnf install/apt-get install/.. with the list of packages for your # # distribution # # # # See the documentation here: # # https://wayland.freedesktop.org/libinput/doc/latest/building_libinput.html # ############################################################################### {% for distro in distributions %} {{distro.name.upper()}}_PACKAGES: '{% for p in distro.packages %}{{p}} {% endfor %}' {{distro.name.upper()}}_PIP_PACKAGES: '{% for p in distro.pips %}{{p}} {% endfor %}' {% endfor %} ############################ end of package lists ############################# # these tags should be updated each time the list of packages is updated # changing these will force rebuilding the associated image # Note: these tags have no meaning and are not tied to a particular # libinput version {% for distro in distributions %} {{"%-13s"| format(distro.name.upper() + '_TAG:')}}'{{distro.tag}}' {% endfor %} FDO_UPSTREAM_REPO: libinput/libei MESON_BUILDDIR: "builddir" NINJA_ARGS: '' MESON_ARGS: '' MESON_TEST_ARGS: '' GIT_DEPTH: 1 .policy: retry: max: 2 when: - runner_system_failure - stuck_or_timeout_failure # cancel run when a newer version is pushed to the branch interruptible: true ################################################################# # # # prep stage # # # ################################################################# fail-if-fork-is-not-public: stage: prep script: - | if [ $CI_PROJECT_VISIBILITY != "public" ]; then echo "*************************************************************************************" echo "Project visibility must be set to 'public'" echo "Change this in $CI_PROJECT_URL/edit under 'Visibility, project features, permissions'" echo "*************************************************************************************" exit 1 fi except: - main@libinput/libei # Re-generate the CI script and make sure it's the one currently checked in # If this job fails, re-generate the gitlab-ci.yml script, see # $SRCDIR/.gitlab-ci/generate-gitlab-ci.py # check-ci-script: extends: - .fdo.ci-fairy stage: prep script: - ci-fairy generate-template - git diff --exit-code && exit 0 || true - echo "Committed gitlab-ci.yml differs from generated gitlab-ci.yml. Please verify" - exit 1 # # Verify that commit messages are as expected, signed-off, etc. # check-commit: extends: - .fdo.ci-fairy stage: prep script: - ci-fairy check-commits --junit-xml=results.xml except: - main@libinput/libei variables: GIT_DEPTH: 100 artifacts: reports: junit: results.xml # # Verify that the merge request has the allow-collaboration checkbox ticked # check-merge-request: extends: - .fdo.ci-fairy stage: deploy script: - ci-fairy check-merge-request --require-allow-collaboration --junit-xml=results.xml artifacts: when: on_failure reports: junit: results.xml allow_failure: true # Format anything python with python-black # python-black: extends: - .fdo.ci-fairy stage: prep before_script: - python3 -m venv venv - source venv/bin/activate - pip3 install black script: - black --check --diff . proto/ei-scanner # Lint with Ruff # python-ruff: extends: - .fdo.ci-fairy stage: prep before_script: - python3 -m venv venv - source venv/bin/activate - pip3 install ruff script: - ruff check --ignore E741,E501 . proto/ei-scanner # # Build distribution-specific images used by the jobs in the build stage # {% for distro in distributions %} {% for version in distro.versions %} {{distro.name}}:{{version}}@container-prep: extends: - .fdo.container-build@{{distro.name}} - .policy stage: prep variables: GIT_STRATEGY: none FDO_DISTRIBUTION_VERSION: '{{version}}' FDO_DISTRIBUTION_PACKAGES: ${{distro.name.upper()}}_PACKAGES FDO_DISTRIBUTION_TAG: ${{distro.name.upper()}}_TAG FDO_DISTRIBUTION_EXEC: 'pip install ${{distro.name.upper()}}_PIP_PACKAGES' {% endfor %} {% endfor %} ################################################################# # # # build stage # # # ################################################################# .build@template: extends: - .policy stage: build script: - .gitlab-ci/meson-build.sh --run-test artifacts: name: "meson-logs-$CI_JOB_NAME" when: always expire_in: 1 week paths: - $MESON_BUILDDIR/meson-logs reports: junit: $MESON_BUILDDIR/meson-logs/.junit.xml dependencies: [] # # Fedora # {% for distro in distributions if distro.use_for_custom_build_tests %} {% 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" default-build-release@{{distro.name}}:{{version}}: stage: distro extends: - .{{distro.name}}-build@template variables: MESON_ARGS: "-Dbuildtype=release" CFLAGS: "-Werror -Wno-error=vla-parameter" # munit triggers -Wvla-parameter build-no-libxkcommon-nodeps@{{distro.name}}:{{version}}: extends: - .{{distro.name}}-build@template before_script: - dnf remove -y libxkcommon-devel build-no-doxygen@{{distro.name}}:{{version}}: extends: - .{{distro.name}}-build@template variables: MESON_ARGS: "-Ddocumentation=[]" before_script: - dnf remove -y doxygen hugo valgrind@{{distro.name}}:{{version}}: extends: - .{{distro.name}}-build@template variables: MESON_TEST_ARGS: '--setup=valgrind' before_script: - dnf install -y valgrind pytest@{{distro.name}}:{{version}}: extends: - .{{distro.name}}-build@template variables: MESON_TEST_ARGS: '--suite=python' werror@{{distro.name}}:{{version}}: extends: - .{{distro.name}}-build@template variables: MESON_ARGS: '-Dwerror=true' allow_failure: true abicheck@{{distro.name}}:{{version}}: extends: - .{{distro.name}}-build@template before_script: - git clone --depth=1 https://gitlab.freedesktop.org/hadess/check-abi - | pushd check-abi meson setup _build meson compile -C _build meson install -C _build popd 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 %} ################################################################# # # # distro stage # # # ################################################################# {% for distro in distributions %} {% for version in distro.versions %} {{distro.name}}:{{version}}@default-build: stage: distro extends: - .build@template - .fdo.distribution-image@{{distro.name}} variables: FDO_DISTRIBUTION_VERSION: '{{version}}' FDO_DISTRIBUTION_TAG: ${{distro.name.upper()}}_TAG {# Where we have extra_variables defined, add them to the list #} {% if distro.build is defined and distro.build.extra_variables is defined %} {% for var in distro.build.extra_variables %} {{var}} {% endfor %} {% endif %} 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: - .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 %} pages: stage: deploy extends: - .build@template - .fdo.distribution-image@{{pages.distro}} variables: FDO_DISTRIBUTION_VERSION: '{{pages.version}}' FDO_DISTRIBUTION_TAG: ${{pages.distro.upper()}}_TAG MESON_ARGS: "-Ddocumentation=protocol,api" script: - echo "Nothing to do" dependencies: - "{{pages.distro}}:{{pages.version}}@doc-build" needs: - "{{pages.distro}}:{{pages.version}}@doc-build" only: refs: - main artifacts: paths: - public libei-1.2.1/.gitlab-ci/config.yml000066400000000000000000000026471456005336000165130ustar00rootroot00000000000000# This file contains the configuration for the gitlab ci. # See the .gitlab-ci/generate-gitlab-ci.py file for more info # # We're happy to rebuild all containers when one changes. .default_tag: &default_tag '2023-12-13.0' last_abi_break: abe85e051e7029bfd2e7913ab980a9e0042b6d0d minimum_meson_version: 0.56.0 distributions: - name: fedora tag: *default_tag versions: - '39' # update the pages job when bumping the version use_for_custom_build_tests: true 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 - python3-pyyaml - hugo # for documentation only - libabigail # for abidiff only pips: - meson - ninja - structlog - strenum - name: debian tag: *default_tag use_for_minimal_meson_test: true versions: - 'bullseye' packages: - git - gcc - g++ - pkg-config - libsystemd-dev - libxkbcommon-dev - libxml2 - doxygen - python3-attr - python3-pytest - python3-dbusmock - python3-jinja2 - python3-pip - python3-yaml pips: - meson - ninja - structlog - strenum pages: distro: fedora version: 39 libei-1.2.1/.gitlab-ci/meson-build.sh000077500000000000000000000036401456005336000172720ustar00rootroot00000000000000#!/usr/bin/env bash # # This script is sourced from here: # https://gitlab.freedesktop.org/whot/meson-helper # # SPDX-License-Identifier: MIT set -x if [[ -f .meson_environment ]]; then . .meson_environment fi # If test args are set, we assume we want to run the tests MESON_RUN_TEST="$MESON_TEST_ARGS" while [[ $# -gt 0 ]]; do case $1 in --skip-setup) shift MESON_SKIP_SETUP="1" ;; --skip-build) shift MESON_SKIP_BUILD="1" ;; --skip-test) shift MESON_RUN_TEST="" ;; --run-test) shift MESON_RUN_TEST="1" ;; *) echo "Unknow commandline argument $1" exit 1 ;; esac done if [[ -z "$MESON_BUILDDIR" ]]; then echo "\$MESON_BUILDDIR undefined." exit 1 fi # emulate a few gitlab variables to make it easier to # run and debug locally. if [[ -z "$CI_JOB_ID" ]] || [[ -z "$CI_JOB_NAME" ]]; then echo "Missing \$CI_JOB_ID or \$CI_JOB_NAME". CI_PROJECT_NAME=$(basename "$PWD") CI_JOB_ID=$(date +%s) CI_JOB_NAME="$CI_PROJECT_NAME-job-local" echo "Simulating gitlab environment: " echo " CI_JOB_ID=$CI_JOB_ID" echo " CI_JOB_NAME=$CI_JOB_NAME" fi if [[ -n "$FDO_CI_CONCURRENT" ]]; then jobcount="-j$FDO_CI_CONCURRENT" export MESON_TESTTHREADS="$FDO_CI_CONCURRENT" fi echo "*************************************************" echo "builddir: $MESON_BUILDDIR" echo "meson args: $MESON_ARGS" echo "ninja args: $NINJA_ARGS" echo "meson test args: $MESON_TEST_ARGS" echo "job count: ${jobcount-0}" echo "*************************************************" set -e if [[ -z "$MESON_SKIP_SETUP" ]]; then rm -rf "$MESON_BUILDDIR" meson setup "$MESON_BUILDDIR" $MESON_ARGS fi meson configure "$MESON_BUILDDIR" if [[ -z "$MESON_SKIP_BUILD" ]]; then if [[ -n "$NINJA_ARGS" ]]; then ninja_args="--ninja-args $NINJA_ARGS" fi meson compile -v -C "$MESON_BUILDDIR" $jobcount $ninja_args fi if [[ -n "$MESON_RUN_TEST" ]]; then meson test -C "$MESON_BUILDDIR" $MESON_TEST_ARGS --print-errorlogs fi libei-1.2.1/.pre-commit-config.yaml000066400000000000000000000013301456005336000170570ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.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: 23.11.0 hooks: - id: black args: ['--check', '--diff', '.', 'proto/ei-scanner'] - repo: https://github.com/charliermarsh/ruff-pre-commit rev: v0.1.7 hooks: - id: ruff args: ['--ignore=E741,E501', '.', 'proto/ei-scanner'] - repo: local hooks: - id: ci-fairy-generate-template name: ci-fairy-generate-template entry: bash -c 'command -v ci-fairy && ci-fairy generate-template || true' language: system libei-1.2.1/COPYING000066400000000000000000000021001456005336000136250ustar00rootroot00000000000000Copyright © 2020 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. libei-1.2.1/README.md000066400000000000000000000401401456005336000140570ustar00rootroot00000000000000libei ===== **libei** is a library for Emulated Input, primarily aimed at the Wayland stack. It provides three parts: - 🥚 EI (Emulated Input) for the client side (`libei`) - 🍦 EIS (Emulated Input Server) for the server side (`libeis`) - 🚌 oeffis is an optional helper library for DBus communication with the XDG RemoteDesktop portal (`liboeffis`) The communication between EI and EIS happens over a UNIX socket via a custom binary protocol. See the [EI protocol documentation](https://libinput.pages.freedesktop.org/libei/) for details. For the purpose of this document, **libei** refers to the project, `libei`/`libeis` to the libraries provided. Documentation ------------- The protocol documentation is available [here](https://libinput.pages.freedesktop.org/libei/) The C library API documentation is available here: - [libei](https://libinput.pages.freedesktop.org/libei/api/group__libei.html) - [libeis](https://libinput.pages.freedesktop.org/libei/api/group__libeis.html) - [liboffis](https://libinput.pages.freedesktop.org/libei/api/group__liboeffis.html) Overview -------- In the Wayland stack, the EIS server component is part of the compositor, the EI client component is part of the Wayland client. ``` +--------------------+ +------------------+ | Wayland compositor |---wayland---| Wayland client B | +--------------------+\ +------------------+ | libinput | libeis | \_wayland______ +----------+---------+ \ | | +-------+------------------+ /dev/input/ +---brei----| libei | Wayland client A | +-------+------------------+ ``` The use-cases **libei** attempts to solve are: - on-demand input device emulation, e.g. `xdotool` or more generically the XTEST extension - input forwarding, e.g. `synergy`, for both client-emulated input as well as the forwarding of physical or logical devices. **libei** provides three benefits: - separation - distinction - control **libei** provides **separation** of emulated input from normal input. Emulated input is a distinct channel for the compositor and can thus be handled accordingly. For example, the compositor may show a warning sign in the task bar while emulated input is active. The second benefit is **distinction**. Each **libei** client has its own input device set, the server is always aware of which client is requesting input at any time. It is possible for the server to treat input from different emulated input devices differently. The server is in **control** of emulated input - it can filter input or discard at will. For example, if the current focus window is a password prompt, the server can simply discard any emulated input. If the screen is locked, the server can pause all emulated input devices. Sender vs receiver contexts --------------------------- As of version 0.3, libei allows a ``libei`` context to be either a sender or a receiver. In the "sender" mode, the ``libei`` context gets a list of devices from the EIS implementation and can emulate events on these devices. The devices usually represent virtual devices, e.g. a generic relative pointer corresponding to the cursor or per-screen absolute input devices. In the "receiver" mode, the ``libei`` context gets a list of devices from the EIS implementation and *receives* events from those. This allows for input capture, provided the EIS implementation supports it. The devices can correspond to virtual devices, e.g. a generic relative pointer corresponding to the cursor. Or they may be representations of physical devices, e.g. a tablet device getting forwarded. A ``libei`` context may only be in one mode. It is up to the EIS implementation to accept a sender or receiver ``libei`` context. Why not $foo? ------------- We start from the baseline of: "there is no emulated input in Wayland (the protocol)". There is emulated input in X through XTEST but it provides neither separation, distinction nor control in a useful manner. There are however many X clients that require XTEST to work. There are several suggestions that overlap with **libei**, with the main proposals being: - a Wayland protocol for virtual input - a (compositor-specific) DBus interface for virtual input Emulated input is not specifically Wayland-y. Clients that emulate input generally don't care about Wayland itself. It's not needed to emulate events on their own surfaces and Wayland does not provide global state. The only connection to Wayland is merely that input events are *received* through the Wayland protocol. So a Wayland protocol for emulating input is not a great fit, it merely ticks the convenient box of "we already have IPC through the wayland protocol, why not just do it there". DBus is the most prevalent generic IPC channel on the Linux desktop but it's not available in some compositors. Any other specific side-channel requires an IPC mechanism to be implemented in the sender and receiver. The current situation looks like that neither proposal will be universally available. Wayland clients (including Xwayland) would need to support any combination of methods. **libei** side-steps this issue by making the *communication* itself a an implementation detail and providing different *negotiation* backends. A client can attempt to establish a **libei** context through a Flatpak Portal first and fall back onto a public DBus interface and then fall back onto e.g. a named UNIX socket. All with a few lines of code only. There is only one spot the client has to care about this, the actual emulation of input is identical regardless of backend. High-level summary ------------------ Simple demo implementations for server and client are available in the [`tools/`](https://gitlab.freedesktop.org/libinput/libei/-/tree/main/tools) directory. The server starts a `libeis` context (which can be integrated with flatpak portals) and uses the `libeis` file descriptor to monitor for client requests. A client starts a `libei` context and connects to the server - either directly, via DBus or via a portal. The server (or the portal) approves or denies the client. After successful authentication the server sends one or more seats (a logical group of devices) to the client; the client can request the creation of devices with capabilities `pointer`, `keyboard` or `touch`, etc. in those seats. The client triggers input events on these devices, the server receives those as events through `libeis` and can forward them as if they were regular input events. The server has control of the client stream. If the stream is paused, events from the client are discarded. If the stream is resumed, the server will receive the events (but may discard them anyway depending on local state). The above caters for the `xdotool` use-case. For a `synergy` use-case, the setup requires: - `synergy-client` on host A capturing mouse and keyboard events via an unspecified protocol - `synergy-server` on host B requesting a mouse/keyboard capability device from the compositor - when `synergy-client` receives events via from compositor A it forwards those to the remote `synergy-server` which sends them via `libei` to the compositor B. **libei** does not provide a method for deciding when events should be captured, it merely provides the transport layer for events once that decision has been made. Differences between XTest vs libei ---------------------------------- **libei** functionality is a superset of XTest's input emulation which consists of a single request, `XTestFakeInput`. This request allows emulation of button, key and motion events, including X Input 1.x events (but not XI2). So **libei** can be a drop-in replacement since it supports the same functionality and more. However, XTest is an X protocol extension and users of XTest usually obtain more information out-of-band ("out-of-band" here means "not through XTest but instead other X protocol requests"). One example is `xdotool` which does window focus and modifier mangling (see below). Window focus notification is not available to a pure **libei** client and would have to be obtained or handled on a separate channel, e.g. X or Wayland. Having said that, a Wayland client does not usually have acess to query or modifiy the window focus. Modifiers in `xdotool` are handled by obtaining the modifier mask from the X server, identifying any difference to the intended mask and emulating key events to change the modifier state to the intended one. For example, if capslock is on, xdotool would send a capslock key event first (thus disabling capslock) and then the actual key sequence. This is followed by another capslock key event to restore the modifier mask. This is not possible for a pure **libei** client as the modifier state is maintained by the windowing system (if any). A client can obtain the modifier state on Wayland on `wl_keyboard.enter` but when the client is in-focus, there is rarely a need to emulate events. Overall, it is best to think of **libei** devices as virtual equivalents to a hardware device. Open questions -------------- ### Flatpak integration Where flatpak portals are in use, `libei` can communicate with the portal through a custom backend. The above diagram modified for Flatpak would be: ``` +--------------------+ | Wayland compositor |_ +--------------------+ \ | libinput | libeis | \_wayland______ +----------+---------+ \ | [eis-0.socket] \ /dev/input/ / \\ +-------+------------------+ | ======>| libei | Wayland client A | | after +-------+------------------+ initial| handover / connection| / initial request | / dbus[org.freedesktop.portal.RemoteDesktop.ConnectToEIS] +--------------------+ | xdg-desktop-portal | +--------------------+ ``` The current approach works so that - the compositor starts an `libeis` socket backend at `$XDG_RUNTIME_DIR/eis-0` - `xdg-desktop-portal` provides `org.freedesktop.portal.RemoteDesktop.ConnectToEIS` - a client connects to the `xdg-desktop-portal` to request emulated input - `xdg-desktop-portal` authenticates a client and opens the initial connection to the `libeis` socket. - `xdg-desktop-portal` hands over the file descriptor to the client which can initialize a `libei` context - from then on, `libei` and `libeis` talk directly to each other, the portal has no further influence. This describes the **current** implementation. Changes to this approach are likely, e.g. the portal **may** control pauseing/resuming devices (in addition to the server). The UI for this is not yet sorted. ### Authentication Sandboxing is addressed via flatpak portals but a further level is likely desirable, esp. outside flatpak. The simplest solution is the client announcing the name so the UI can be adjusted accordingly. API wise-maybe an opaque key/value system so the exact auth can be left to the implementation. ### Capability monitoring For the use-case of fowarding input (e.g. `synergy`) we need a method of capturing input as well as forwarding input. An initial idea was for **libei** to provide capability monitoring, i.e. a client requests all events from a specific capability. As with input emulation same benefits would apply - input can only be forwarded if the compositor explicitly does so. However, this fails in the details. For example, for `synergy` we need capability monitoring started by triggers, e.g. the client requests a pointer capability monitoring when the real pointer hits the screen edge. Or in response to a keyboard shortcut. Some of the capabilities are distinctively display server-specific, for example the concept of a seat and a device is different between X and Wayland. At this point, no implementation of capability monitoring is planned for **libei**. ### Keyboard layouts The emulated input may require a specific keyboard layout, for example for softtokens (usually: constant layout "us") or for the `synergy` case where the remote keyboard should have the same keymap as the local one, even where the remote host is configured otherwise. In **libei**, the server informs the client about the keymap it expects and it is up to the client to provide the correct keyboard events. Modifier state handling, group handling, etc. is still a private implementation so even where the server supports individual keymaps. So it remains to be seen if this approach is sufficient. ### Xwayland and XTEST There are PoC implementations of using `libei` within Xwayland and connecting it to a `libeis` context in the compositor (PoC with Weston). This allows Xwayland to intercept XTEST events and route those through the compositor instead. ``` +--------------------+ +------------------+ | Wayland compositor |---wayland---| Wayland client B | +--------------------+\ +------------------+ | libinput | libeis | \_wayland______ +----------+---------+ \ | | +-------+------------------+ /dev/input/ +---brei----| libei | Xwayland | +-------+------------------+ | | XTEST | +-----------+ | X client | +-----------+ ``` Of course, Xwayland is just another Wayland client, so the connection between libei and libeis could be handled through a portal. ### Short-lived applications **libei** is not designed for short-lived fire-and-forget-type applications 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. 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. ### uinput vs libei uinput is a Linux kernel module that allows creating `/dev/input/event`-compatible devices. Unlike XTest it is independent of a windowing system but requires write access to `/dev/uinput`, usually limited to root. uinput devices are effectively identical to physical devices and will thus work on the tty and in any windowing system. From the POV of a ``libei`` client, uinput is a server implementation detail. The client does not need to know that the manager employs uinput to create the devices, it merely connects to an available EIS instance. ``` +---------+ | server |___auth channel__________ +---------+ \ | libeis |- +---------------------+ +---------+ \____brei___| libei | application | | +---------------------+ /dev/uinput/ ``` Note that the server would likely need some authentication channel to verify which client is allowed to emulate input devices at the kernel level (polkit? config files?). This however is out of scope for **libei**. An example uinput server is implemented in the `eis-server-demo` in the with libei repository. # liboeffis In a Wayland and/or sandboxed environment, emulating input events requires going through the XDG RemoteDesktop portal. This portal is available on DBus but communication with DBus is often cumbersome, especially for small tools. `liboeffis` is an optional library that provides the communication with the portal, sufficient to start a RemoteDesktop session and retrieve the file descriptor to the EIS implementation. libei-1.2.1/doc/000077500000000000000000000000001456005336000133465ustar00rootroot00000000000000libei-1.2.1/doc/api/000077500000000000000000000000001456005336000141175ustar00rootroot00000000000000libei-1.2.1/doc/api/doxygen-awesome.css000066400000000000000000000755711456005336000177630ustar00rootroot00000000000000/** Doxygen Awesome https://github.com/jothepro/doxygen-awesome-css MIT License Copyright (c) 2021 jothepro 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 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. */ :root { /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ --primary-color: #1982d2; --primary-dark-color: #00559f; --primary-light-color: #7aabd6; --primary-lighter-color: #cae1f1; --primary-lightest-color: #e9f1f8; /* page base colors */ --page-background-color: white; --page-foreground-color: #2c3e50; --page-secondary-foreground-color: #67727e; /* color for all separators on the website: hr, borders, ... */ --separator-color: #dedede; /* border radius for all rounded components. Will affect many components, like dropdowns, memitems, codeblocks, ... */ --border-radius-large: 8px; --border-radius-small: 4px; --border-radius-medium: 6px; /* default spacings. Most compontest reference these values for spacing, to provide uniform spacing on the page. */ --spacing-small: 5px; --spacing-medium: 10px; --spacing-large: 16px; /* default box shadow used for raising an element above the normal content. Used in dropdowns, Searchresult, ... */ --box-shadow: 0 2px 10px 0 rgba(0,0,0,.1); --odd-color: rgba(0,0,0,.03); /* font-families. will affect all text on the website * font-family: the normal font for text, headlines, menus * font-family-monospace: used for preformatted text in memtitle, code, fragments */ --font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; --font-family-monospace: source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace; /* font sizes */ --page-font-size: 15.6px; --navigation-font-size: 14.4px; --code-font-size: 14.4px; /* affects code, fragment */ --title-font-size: 22px; /* content text properties. These only affect the page content, not the navigation or any other ui elements */ --content-line-height: 27px; /* The content is centered and constraint in it's width. To make the content fill the whole page, set the variable to auto.*/ --content-maxwidth: 900px; /* colors for various content boxes: @warning, @note, @deprecated @bug */ --warning-color: #fca49b; --warning-color-dark: #b61825; --warning-color-darker: #75070f; --note-color: rgba(255,229,100,.3); --note-color-dark: #c39900; --note-color-darker: #8d7400; --deprecated-color: rgb(214, 216, 224); --deprecated-color-dark: #5b6269; --deprecated-color-darker: #43454a; --bug-color: rgb(246, 208, 178); --bug-color-dark: #a53a00; --bug-color-darker: #5b1d00; --invariant-color: #b7f8d0; --invariant-color-dark: #00ba44; --invariant-color-darker: #008622; /* blockquote colors */ --blockquote-background: #f5f5f5; --blockquote-foreground: #727272; /* table colors */ --tablehead-background: #f1f1f1; --tablehead-foreground: var(--page-foreground-color); /* menu-display: block | none * Visibility of the top navigation on screens >= 768px. On smaller screen the menu is always visible. * `GENERATE_TREEVIEW` MUST be enabled! */ --menu-display: block; --menu-focus-foreground: var(--page-background-color); --menu-focus-background: var(--primary-color); --menu-selected-background: rgba(0,0,0,.05); --header-background: var(--page-background-color); --header-foreground: var(--page-foreground-color); /* searchbar colors */ --searchbar-background: var(--side-nav-background); --searchbar-foreground: var(--page-foreground-color); /* searchbar size * (`searchbar-width` is only applied on screens >= 768px. * on smaller screens the searchbar will always fill the entire screen width) */ --searchbar-height: 33px; --searchbar-width: 210px; /* code block colors */ --code-background: #f5f5f5; --code-foreground: var(--page-foreground-color); /* fragment colors */ --fragment-background: #282c34; --fragment-foreground: #ffffff; --fragment-keyword: #cc99cd; --fragment-keywordtype: #ab99cd; --fragment-keywordflow: #e08000; --fragment-token: #7ec699; --fragment-comment: #999999; --fragment-link: #98c0e3; --fragment-preprocessor: #65cabe; --fragment-linenumber-color: #cccccc; --fragment-linenumber-background: #35393c; --fragment-linenumber-border: #1f1f1f; --fragment-lineheight: 20px; /* sidebar navigation (treeview) colors */ --side-nav-background: #fbfbfb; --side-nav-foreground: var(--page-foreground-color); --side-nav-arrow-color: var(--page-background-color); /* height of an item in any tree / collapsable table */ --tree-item-height: 30px; } @media screen and (max-width: 767px) { :root { --page-font-size: 16px; --navigation-font-size: 16px; --code-font-size: 15px; /* affects code, fragment */ --title-font-size: 22px; } } @media (prefers-color-scheme: dark) { :root { --primary-color: #00559f; --primary-dark-color: #1982d2; --primary-light-color: #4779ac; --primary-lighter-color: #191e21; --primary-lightest-color: #191a1c; --box-shadow: 0 2px 10px 0 rgba(0,0,0,.35); --odd-color: rgba(0,0,0,.1); --menu-selected-background: rgba(0,0,0,.4); --page-background-color: #1C1D1F; --page-foreground-color: #d2dbde; --page-secondary-foreground-color: #859399; --separator-color: #000000; --side-nav-background: #252628; --code-background: #2a2c2f; --tablehead-background: #2a2c2f; --blockquote-background: #1f2022; --blockquote-foreground: #77848a; --warning-color: #b61825; --warning-color-dark: #510a02; --warning-color-darker: #f5b1aa; --note-color: rgb(255, 183, 0); --note-color-dark: #9f7300; --note-color-darker: #fff6df; --deprecated-color: rgb(88, 90, 96); --deprecated-color-dark: #262e37; --deprecated-color-darker: #a0a5b0; --bug-color: rgb(248, 113, 0); --bug-color-dark: #812a00; --bug-color-darker: #ffd3be; } } body { color: var(--page-foreground-color); background-color: var(--page-background-color); font-size: var(--page-font-size); } body, table, div, p, dl, #nav-tree .label, .title, .sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, .SelectItem, #MSearchField, .navpath li.navelem a, .navpath li.navelem a:hover { font-family: var(--font-family); } h1, h2, h3, h4, h5 { margin-top: .9em; font-weight: 600; line-height: initial; } p, div, table, dl { font-size: var(--page-font-size); } a, a.el:visited, a.el:hover, a.el:focus, a.el:active { color: var(--primary-dark-color); } /* Title and top navigation */ #top { background: var(--header-background); border-bottom: 1px solid var(--separator-color); } @media screen and (min-width: 768px) { #top { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; } } #main-nav { flex-grow: 5; padding: var(--spacing-small) var(--spacing-medium); } #titlearea { width: auto; padding: var(--spacing-medium) var(--spacing-large); background: none; color: var(--header-foreground); border-bottom: none; } @media screen and (max-width: 767px) { #titlearea { padding-bottom: var(--spacing-small); } } #titlearea table tbody tr { height: auto !important; } #projectname { font-size: var(--title-font-size); font-weight: 600; } #projectnumber { font-family: inherit; font-size: 60%; } #projectbrief { font-family: inherit; font-size: 80%; } #projectlogo { vertical-align: middle; } #projectlogo img { max-height: calc(var(--title-font-size) * 2); margin-right: var(--spacing-small); } .sm-dox, .tabs, .tabs2, .tabs3 { background: none; padding: 0; } .tabs, .tabs2, .tabs3 { border-bottom: 1px solid var(--separator-color); margin-bottom: -1px; } @media screen and (max-width: 767px) { .sm-dox a span.sub-arrow { background: var(--code-background); } } @media screen and (min-width: 768px) { .sm-dox li, .tablist li { display: var(--menu-display); } .sm-dox a span.sub-arrow { border-color: var(--header-foreground) transparent transparent transparent; } .sm-dox a:hover span.sub-arrow { border-color: var(--menu-focus-foreground) transparent transparent transparent; } .sm-dox ul a span.sub-arrow { border-color: transparent transparent transparent var(--header-foreground); } .sm-dox ul a:hover span.sub-arrow { border-color: transparent transparent transparent var(--menu-focus-foreground); } } .sm-dox ul { background: var(--page-background-color); box-shadow: var(--box-shadow); border: 1px solid var(--separator-color); border-radius: var(--border-radius-medium) !important; padding: var(--spacing-small); animation: ease-out 150ms slideInMenu; } @keyframes slideInMenu { from { opacity: 0; transform: translate(0px, -2px); } to { opacity: 1; transform: translate(0px, 0px); } } .sm-dox ul a { color: var(--page-foreground-color); background: var(--page-background-color); font-size: var(--navigation-font-size); } .sm-dox>li>ul:after { border-bottom-color: var(--page-background-color) !important; } .sm-dox>li>ul:before { border-bottom-color: var(--separator-color) !important; } .sm-dox ul a:hover, .sm-dox ul a:active, .sm-dox ul a:focus { font-size: var(--navigation-font-size); color: var(--menu-focus-foreground); text-shadow: none; background-color: var(--menu-focus-background); border-radius: var(--border-radius-small) !important; } .sm-dox a, .sm-dox a:focus, .tablist li, .tablist li a, .tablist li.current a { text-shadow: none; background: transparent; background-image: none !important; color: var(--header-foreground); font-weight: normal; font-size: var(--navigation-font-size); } .sm-dox a:focus { outline: auto; } .sm-dox a:hover, .sm-dox a:active, .tablist li a:hover { text-shadow: none; font-weight: normal; background: var(--menu-focus-background); color: var(--menu-focus-foreground); border-radius: var(--border-radius-small) !important; font-size: var(--navigation-font-size); } .tablist li.current { border-radius: var(--border-radius-small); background: var(--menu-selected-background); } .tablist li { margin: var(--spacing-small) 0 var(--spacing-small) var(--spacing-small); } .tablist a { padding: 0 var(--spacing-large); } /* Search box */ #MSearchBox { height: var(--searchbar-height); background: var(--searchbar-background); border-radius: var(--searchbar-height); border: 1px solid var(--separator-color); overflow: hidden; width: var(--searchbar-width); position: relative; box-shadow: none; display: block; margin-top: 0; } .left #MSearchSelect { left: 0; } .tabs .left #MSearchSelect { padding-left: 0; } .tabs #MSearchBox { position: absolute; right: var(--spacing-medium); } @media screen and (max-width: 767px) { .tabs #MSearchBox { position: relative; right: 0; margin-left: var(--spacing-medium); margin-top: 0; } } #MSearchSelectWindow, #MSearchResultsWindow { z-index: 9999; } #MSearchBox.MSearchBoxActive { border-color: var(--primary-color); box-shadow: inset 0 0 0 1px var(--primary-color); } #main-menu > li:last-child { margin-right: 0; } @media screen and (max-width: 767px) { #main-menu > li:last-child { height: 50px; } } #MSearchField { font-size: var(--navigation-font-size); height: calc(var(--searchbar-height) - 2px); background: transparent; width: calc(var(--searchbar-width) - 64px); } .MSearchBoxActive #MSearchField { color: var(--searchbar-foreground); } #MSearchSelect { top: calc(calc(var(--searchbar-height) / 2) - 11px); } .left #MSearchSelect { padding-left: 8px; } #MSearchBox span.left, #MSearchBox span.right { background: none; } #MSearchBox span.right { padding-top: calc(calc(var(--searchbar-height) / 2) - 12px); } .tabs #MSearchBox span.right { top: calc(calc(var(--searchbar-height) / 2) - 12px); } @keyframes slideInSearchResults { from { opacity: 0; transform: translate(0, 15px); } to { opacity: 1; transform: translate(0, 20px); } } #MSearchResultsWindow { left: auto !important; right: var(--spacing-medium); border-radius: var(--border-radius-large); border: 1px solid var(--separator-color); transform: translate(0, 20px); box-shadow: var(--box-shadow); animation: ease-out 280ms slideInSearchResults; background: var(--page-background-color); } iframe#MSearchResults { background: var(--page-background-color); margin: 4px; } #MSearchSelectWindow { border: 1px solid var(--separator-color); border-radius: var(--border-radius-medium); box-shadow: var(--box-shadow); background: var(--page-background-color); } #MSearchSelectWindow a.SelectItem { font-size: var(--navigation-font-size); line-height: var(--content-line-height); margin: 0 var(--spacing-small); border-radius: var(--border-radius-small); color: var(--page-foreground-color); } #MSearchSelectWindow a.SelectItem:hover { background: var(--menu-focus-background); color: var(--menu-focus-foreground); } @media screen and (max-width: 767px) { #MSearchBox { margin-top: var(--spacing-medium); margin-bottom: var(--spacing-medium); width: calc(100vw - 30px); } #main-menu > li:last-child { float: none !important; } #MSearchField { width: calc(100vw - 95px); } @keyframes slideInSearchResultsMobile { from { opacity: 0; transform: translate(0, 15px); } to { opacity: 1; transform: translate(0, 20px); } } #MSearchResultsWindow { left: var(--spacing-medium) !important; right: var(--spacing-medium); overflow: auto; transform: translate(0, 20px); animation: ease-out 280ms slideInSearchResultsMobile; } } /* Tree view */ #side-nav { padding: 0 !important; background: var(--side-nav-background); } @media screen and (max-width: 767px) { #side-nav { display: none; } #doc-content { margin-left: 0 !important; height: auto !important; padding-bottom: calc(2 * var(--spacing-large)); } } #nav-tree { background: transparent; } #nav-tree .label { font-size: var(--navigation-font-size); } #nav-tree .item { height: var(--tree-item-height); line-height: var(--tree-item-height); } #nav-sync { top: 12px !important; right: 12px; } #nav-tree .selected { text-shadow: none; background-image: none; background-color: transparent; box-shadow: inset 4px 0 0 0 var(--primary-dark-color); } #nav-tree a { color: var(--side-nav-foreground); } #nav-tree a:focus { outline-style: auto; } .arrow { color: var(--primary-light-color); font-family: serif; height: auto; text-align: right; } #nav-tree .arrow { opacity: 0; } #nav-tree div.item:hover .arrow, #nav-tree a:focus .arrow { opacity: 1; } #nav-tree .selected a { color: var(--primary-dark-color); font-weight: bolder; } .ui-resizable-e { background: var(--separator-color); width: 1px; } /* Contents */ div.header { border-bottom: 1px solid var(--separator-color); background-color: var(--page-background-color); background-image: none; } div.contents, div.header .title, div.header .summary { max-width: var(--content-maxwidth); } div.contents, div.header .title { line-height: initial; margin: calc(var(--spacing-medium) + .2em) auto var(--spacing-medium) auto; } div.header .summary { margin: var(--spacing-medium) auto 0 auto; } div.headertitle { padding: 0; } div.header .title { font-weight: 600; font-size: 210%; padding: var(--spacing-medium) var(--spacing-large); word-break: break-word; } div.header .summary { width: auto; display: block; float: none; padding: 0 var(--spacing-large); } td.memSeparator { border-color: var(--separator-color); } .mdescLeft, .mdescRight, .memItemLeft, .memItemRight, .memTemplItemLeft, .memTemplItemRight, .memTemplParams { background: var(--code-background); } .mdescRight { color: var(--page-secondary-foreground-color); } span.mlabel { background: var(--primary-color); border: none; padding: 4px 9px; border-radius: 12px; margin-right: var(--spacing-medium); } span.mlabel:last-of-type { margin-right: 2px; } div.contents { padding: 0 var(--spacing-large); } div.contents p, div.contents li { line-height: var(--content-line-height); } div.contents div.dyncontent { margin: var(--spacing-medium) 0; } @media (prefers-color-scheme: dark) { div.contents div.dyncontent img { filter: hue-rotate(180deg) invert(); } } h2.groupheader { border-bottom: 1px solid var(--separator-color); color: var(--page-foreground-color); } blockquote { padding: var(--spacing-small) var(--spacing-medium); background: var(--blockquote-background); color: var(--blockquote-foreground); border-left: 2px solid var(--blockquote-foreground); margin: 0; } blockquote p { margin: var(--spacing-small) 0 var(--spacing-medium) 0; } .paramname { color: var(--primary-dark-color); } .glow { text-shadow: 0 0 15px var(--primary-light-color) !important; } .alphachar a { color: var(--page-foreground-color); } /* Table of Contents */ div.toc { background-color: var(--side-nav-background); border: 1px solid var(--separator-color); border-radius: var(--border-radius-medium); box-shadow: var(--box-shadow); padding: 0 var(--spacing-large); margin: 0 0 var(--spacing-medium) var(--spacing-medium); } div.toc h3 { color: var(--side-nav-foreground); font-size: var(--navigation-font-size); margin: var(--spacing-large) 0; } div.toc li { font-size: var(--navigation-font-size); padding: 0; background: none; } div.toc li:before { content: '↓'; font-weight: 800; font-family: var(--font-family); margin-right: var(--spacing-small); color: var(--side-nav-foreground); opacity: .4; } div.toc ul li.level1 { margin: 0; } div.toc ul li.level2, div.toc ul li.level3 { margin-top: 0; } @media screen and (max-width: 767px) { div.toc { float: none; width: auto; margin: 0 0 var(--spacing-medium) 0; } } /* Code & Fragments */ code, div.fragment, pre.fragment { border-radius: var(--border-radius-small); border: none; overflow: hidden; } code { display: inline; background: var(--code-background); color: var(--code-foreground); padding: 2px 6px; word-break: break-word; } div.fragment, pre.fragment { margin: var(--spacing-medium) 0; padding: 14px 16px; background: var(--fragment-background); color: var(--fragment-foreground); overflow-x: auto; } @media screen and (max-width: 767px) { div.fragment, pre.fragment { border-top-right-radius: 0; border-bottom-right-radius: 0; } .contents > div.fragment, .textblock > div.fragment, .textblock > pre.fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-large)); border-radius: 0; } .textblock li > .fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-large)); } .memdoc li > .fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); } .memdoc > div.fragment, .memdoc > pre.fragment, dl dd > div.fragment, dl dd pre.fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); border-radius: 0; } } code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span { font-family: var(--font-family-monospace); font-size: var(--code-font-size) !important; } div.line:after { margin-right: var(--spacing-medium); } div.fragment .line, pre.fragment { white-space: pre; word-wrap: initial; line-height: var(--fragment-lineheight); } div.fragment span.keyword { color: var(--fragment-keyword); } div.fragment span.keywordtype { color: var(--fragment-keywordtype); } div.fragment span.keywordflow { color: var(--fragment-keywordflow); } div.fragment span.stringliteral { color: var(--fragment-token) } div.fragment span.comment { color: var(--fragment-comment); } div.fragment a.code { color: var(--fragment-link); } div.fragment span.preprocessor { color: var(--fragment-preprocessor); } div.fragment span.lineno { display: inline-block; width: 27px; border-right: none; background: var(--fragment-linenumber-background); color: var(--fragment-linenumber-color); } div.fragment span.lineno a { background: none; color: var(--fragment-link); } div.fragment .line:first-child .lineno { box-shadow: -999999px 0px 0 999999px var(--fragment-linenumber-background), -999998px 0px 0 999999px var(--fragment-linenumber-border); } /* dl warning, attention, note, deprecated, bug, ... */ dl.warning, dl.attention, dl.note, dl.deprecated, dl.bug, dl.invariant, dl.pre { padding: var(--spacing-medium); margin: var(--spacing-medium) 0; color: var(--page-background-color); overflow: hidden; margin-left: 0; border-radius: var(--border-radius-small); } dl.section dd { margin-bottom: 2px; } dl.warning, dl.attention { background: var(--warning-color); border-left: 8px solid var(--warning-color-dark); color: var(--warning-color-darker); } dl.warning dt, dl.attention dt { color: var(--warning-color-dark); } dl.note { background: var(--note-color); border-left: 8px solid var(--note-color-dark); color: var(--note-color-darker); } dl.note dt { color: var(--note-color-dark); } dl.bug { background: var(--bug-color); border-left: 8px solid var(--bug-color-dark); color: var(--bug-color-darker); } dl.bug dt a { color: var(--bug-color-dark) !important; } dl.deprecated { background: var(--deprecated-color); border-left: 8px solid var(--deprecated-color-dark); color: var(--deprecated-color-darker); } dl.deprecated dt a { color: var(--deprecated-color-dark) !important; } dl.section dd, dl.bug dd, dl.deprecated dd { margin-inline-start: 0px; } dl.invariant, dl.pre { background: var(--invariant-color); border-left: 8px solid var(--invariant-color-dark); color: var(--invariant-color-darker); } /* memitem */ div.memdoc, div.memproto, h2.memtitle { box-shadow: none; background-image: none; border: none; } div.memdoc { padding: 0 var(--spacing-medium); background: var(--page-background-color); } h2.memtitle, div.memitem { border: 1px solid var(--separator-color); } div.memproto, h2.memtitle { background: var(--code-background); text-shadow: none; } h2.memtitle { font-weight: 500; font-family: monospace, fixed; border-bottom: none; border-top-left-radius: var(--border-radius-medium); border-top-right-radius: var(--border-radius-medium); word-break: break-all; } a:target + h2.memtitle, a:target + h2.memtitle + div.memitem { border-color: var(--primary-light-color); } a:target + h2.memtitle { box-shadow: -3px -3px 3px 0 var(--primary-lightest-color), 3px -3px 3px 0 var(--primary-lightest-color); } a:target + h2.memtitle + div.memitem { box-shadow: 0 0 10px 0 var(--primary-lighter-color); } div.memitem { border-top-right-radius: var(--border-radius-medium); border-bottom-right-radius: var(--border-radius-medium); border-bottom-left-radius: var(--border-radius-medium); overflow: hidden; display: block !important; } div.memdoc { border-radius: 0; } div.memproto { border-radius: 0 var(--border-radius-small) 0 0; overflow: auto; border-bottom: 1px solid var(--separator-color); padding: var(--spacing-medium); margin-bottom: -1px; } div.memtitle { border-top-right-radius: var(--border-radius-medium); border-top-left-radius: var(--border-radius-medium); } div.memproto table.memname { font-family: monospace, fixed; color: var(--page-foreground-color); } table.mlabels, table.mlabels > tbody { display: block; } td.mlabels-left { width: auto; } table.mlabels > tbody > tr:first-child { display: flex; justify-content: space-between; flex-wrap: wrap; } .memname, .memitem span.mlabels { margin: 0 } /* reflist */ dl.reflist { border-radius: var(--border-radius-medium); border: 1px solid var(--separator-color); overflow: hidden; padding: 0; } dl.reflist dt, dl.reflist dd { box-shadow: none; text-shadow: none; background-image: none; border: none; padding: 12px; } dl.reflist dt { border-radius: 0; background: var(--code-background); border-bottom: 1px solid var(--separator-color); color: var(--page-foreground-color) } dl.reflist dd { background: none; } /* Table */ table.markdownTable, table.fieldtable { width: 100%; border: 1px solid var(--separator-color); margin: var(--spacing-medium) 0; } table.fieldtable { box-shadow: none; border-radius: var(--border-radius-small); } th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { background: var(--tablehead-background); color: var(--tablehead-foreground); font-weight: 600; } table.markdownTable td, table.markdownTable th, table.fieldtable dt { border: 1px solid var(--separator-color); padding: var(--spacing-small) var(--spacing-medium); } table.fieldtable th { font-size: var(--page-font-size); font-weight: 600; background-image: none; background-color: var(--tablehead-background); color: var(--tablehead-foreground); border-bottom: 1px solid var(--separator-color); } .fieldtable td.fieldtype, .fieldtable td.fieldname { border-bottom: 1px solid var(--separator-color); border-right: 1px solid var(--separator-color); } .fieldtable td.fielddoc { border-bottom: 1px solid var(--separator-color); } .memberdecls td.glow, .fieldtable tr.glow { background-color: var(--primary-light-color); box-shadow: 0 0 15px var(--primary-lighter-color); } table.memberdecls { display: block; overflow-x: auto; overflow-y: hidden; } /* Horizontal Rule */ hr { margin-top: var(--spacing-large); margin-bottom: var(--spacing-large); border-top:1px solid var(--separator-color); } .contents hr { box-shadow: var(--content-maxwidth) 0 0 0 var(--separator-color), calc(0px - var(--content-maxwidth)) 0 0 0 var(--separator-color); } .contents img { max-width: 100%; } /* Directories */ div.directory { border-top: 1px solid var(--separator-color); border-bottom: 1px solid var(--separator-color); width: auto; } table.directory { font-family: var(--font-family); font-size: var(--page-font-size); font-weight: normal; } .directory td.entry { padding: var(--spacing-small); display: flex; align-items: center; } .directory tr.even { background-color: var(--odd-color); } .icona { width: auto; height: auto; margin: 0 var(--spacing-small); } .icon { background: var(--primary-dark-color); width: 18px; height: 18px; line-height: 18px; } .iconfopen, .icondoc, .iconfclosed { background-position: center; margin-bottom: 0; } .icondoc { filter: saturate(0.2); } @media screen and (max-width: 767px) { div.directory { margin-left: calc(0px - var(--spacing-medium)); margin-right: calc(0px - var(--spacing-medium)); } } @media (prefers-color-scheme: dark) { .iconfopen, .iconfclosed { filter: hue-rotate(180deg) invert(); } } /* Class list */ .classindex dl.odd { background: var(--odd-color); border-radius: var(--border-radius-small); } @media screen and (max-width: 767px) { .classindex { margin: 0 calc(0px - var(--spacing-small)); } } /* Footer and nav-path */ #nav-path { margin-bottom: -1px; width: 100%; } #nav-path ul { background-image: none; background: var(--page-background-color); border: none; border-top: 1px solid var(--separator-color); border-bottom: 1px solid var(--separator-color); font-size: var(--navigation-font-size); } img.footer { width: 60px; } .navpath li.footer { color: var(--page-secondary-foreground-color); } address.footer { margin-bottom: var(--spacing-large); } #nav-path li.navelem { background-image: none; display: flex; align-items: center; } .navpath li.navelem a { text-shadow: none; display: inline-block; color: var(--primary-dark-color) } li.navelem { padding: 0; margin-left: -8px; } li.navelem:first-child { margin-left: var(--spacing-large); } li.navelem:first-child:before { display: none; } #nav-path li.navelem:after { content: ''; border: 5px solid var(--page-background-color); border-bottom-color: transparent; border-right-color: transparent; border-top-color: transparent; transform: scaleY(4.2); z-index: 10; margin-left: 6px; } #nav-path li.navelem:before { content: ''; border: 5px solid var(--separator-color); border-bottom-color: transparent; border-right-color: transparent; border-top-color: transparent; transform: scaleY(3.2); margin-right: var(--spacing-small); } @media (prefers-color-scheme: dark) { #nav-path li.navelem:after { text-shadow: 3px 0 0 var(--separator-color), 8px 0 6px rgba(0,0,0,0.4); } } .navpath li.navelem a:hover { color: var(--primary-color); } libei-1.2.1/doc/api/libei.doxygen.in000066400000000000000000000015411456005336000172100ustar00rootroot00000000000000PROJECT_NAME = @PACKAGE_NAME@ PROJECT_NUMBER = @PACKAGE_VERSION@ PROJECT_BRIEF = "A library for Emulated Input" JAVADOC_AUTOBRIEF = YES TAB_SIZE = 8 OPTIMIZE_OUTPUT_FOR_C = YES EXTRACT_ALL = YES EXTRACT_STATIC = YES MAX_INITIALIZER_LINES = 0 WARNINGS = YES QUIET = YES INPUT = "@builddir@" IMAGE_PATH = "@builddir@" GENERATE_HTML = YES OUTPUT_DIRECTORY = doc HTML_OUTPUT = html SEARCHENGINE = NO USE_MATHJAX = YES GENERATE_LATEX = NO MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES DOTFILE_DIRS = "@builddir@" EXAMPLE_PATH = "@builddir@" SHOW_NAMESPACES = NO GENERATE_TREEVIEW = YES HTML_EXTRA_STYLESHEET = "@builddir@/doxygen-awesome.css" libei-1.2.1/doc/api/mainpage.dox000066400000000000000000000074611456005336000164240ustar00rootroot00000000000000/** @mainpage This is the libei/libeis/liboeffis API reference. For the protocol documentation see [here](https://libinput.pages.freedesktop.org/libei/). libei provides three different libraries, `libei` for clients that need to emulate or capture input events and `libeis` for servers that manage those input events (read: compositors). It also provides the `liboeffis` to abstract connecting to the XDG RemoteDesktop portal. A common setup looks like this: ``` +-----------------------+ +----------------+ physical devices - | libinput | compositor | ----- | Wayland client | +----------+------------+ ^ +----------------+ | libeis | | +------------+ Wayland events || || <-- EI events || +-------------+ | libei | +-------------+ | application | +-------------+ ``` Notably, the process using `libeis` is **in control of all input devices**. A `libei` client can send events but it is up to the EIS implementation to process them. The two libraries are independently and usually a process uses either `libei` or `libeis` but not both. @section sec-ei 🥚 EI - the client implementation The `libei` library is the component used by clients that want to emulate input events. It provides the required methods to connect to an EIS implementation, query for input devices available to the client, and send input. The API documentation for EI is available at @ref libei. @section sec-eis 🍦 EIS - the server implementation The `libeis` library is the component used by processes that handle input events. It provides the required methods to set up seats and input devices, accept `libei` client connections, and receive input events from those clients. The API documentation for EI is available at @ref libeis. @section sec-oeffis 🚌 Oeffis - a 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. It abstracts the DBus connection handling into a small and simple API. The API documentation for EI is available at @ref oeffis. @section examples Examples Please see the [demo programs](https://gitlab.freedesktop.org/libinput/libei/-/tree/main/tools) in the git repository. The `eis-demo-client` is a minimal client that connects to an EIS implementation and sends events. The `eis-demo-server` is a minimal EIS implementation that accepts all requests and prints them to screen. The `oeffis-demo-tool` is a minimal implementation to connect to the XDG RemoteDesktop portal and request an fd that could then be passed to ei_setup_backend_fd(). @section building_against Building against libei, libeis or liboeffis libei, libeis and liboeffis provide [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) files. Software that uses libei, libeis or liboeffis should use pkg-config and the `PKG_CHECK_MODULES` autoconf macro or the `dependency()` function in meson. Otherwise, the most rudimentary way to compile and link a program against libei, libeis or liboeffis is: @verbatim gcc -o myprogram mylibeiclient.c `pkg-config --cflags --libs libei-1.0` gcc -o myprogram myEISimplementation.c `pkg-config --cflags --libs libeis-1.0` gcc -o myprogram myimpl.c `pkg-config --cflags --libs liboeffis-1.0` @endverbatim For further information on using pkgconfig see the pkg-config documentation. @section About Documentation generated from git commit [__GIT_VERSION__](https://gitlab.freedesktop.org/libinput/libei/commit/__GIT_VERSION__) */ libei-1.2.1/doc/api/meson.build000066400000000000000000000024221456005336000162610ustar00rootroot00000000000000if 'api' not in get_option('documentation') subdir_done() endif doxygen = find_program('doxygen', required : false) if not doxygen.found() 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__') src_doxygen = files( # 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 ] endforeach doc_config = configuration_data() 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) custom_target('doxygen', input : [ doxyfile, mainpage ] + src_doxygen + doxyfiles, output : [ 'doc' ], command : [ doxygen, doxyfile ], install : false, depends: [ mainpage ], build_by_default : true) libei-1.2.1/doc/meson.build000066400000000000000000000000411456005336000155030ustar00rootroot00000000000000subdir('api') subdir('protocol') libei-1.2.1/doc/protocol/000077500000000000000000000000001456005336000152075ustar00rootroot00000000000000libei-1.2.1/doc/protocol/_index.md000066400000000000000000000051371456005336000170050ustar00rootroot00000000000000--- title: EI Protocol documentation draft: false archetype: "home" alwaysopen: true --- **libei** is a library for Emulated Input, primarily aimed at the Wayland stack. It uses a typical client/server separation, with the two parts connected via a UNIX socket. In libei parlance, the client-side is called **"EI client"**, the server side, typically a Wayland compositor, is called the **"EIS Implementation"** (Emulated Input Server). These terms are used throughout this documentation. 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: {{< mermaid >}} flowchart LR; libwayland-server --> c1 libwayland-server --> c2 /dev/input/event0 ---> libinput /dev/input/event1 ---> libinput libei -.-> libeis libinput --> inputstack inputstack --> libwayland-server libeis -.-> inputstack subgraph Kernel /dev/input/event0 /dev/input/event1 end subgraph Wayland Compositor libwayland-server inputstack[input stack] libinput libeis end subgraph EI client libei end subgraph Wayland client A c1[libwayland-client] end subgraph Wayland client B c2[libwayland-client] end {{< /mermaid >}} Note how the EI client is roughly equivalent to a physical input device coming from the kernel and its events feed into the normal input stack. However, the events are distinguishable inside the compositor to allow for fine-grained access control on which events may be emulated and when emulation is permitted. Events from the EIS implementation would usually feed into the input stack in the same way as input events from physical devices. To Wayland clients, they are indistinguishable from real devices. The EI client may be a Wayland client itself. ## EI Protocol The ei protocol is a public protocol that may be used directly by clients or EIS implementations. This documentation describes the protocol, its interfaces and how to generate language bindings. 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__liboeffis.html) is an helper library for DBus communication with the XDG RemoteDesktop portal (`liboeffis`) # Documentation {{% children %}} libei-1.2.1/doc/protocol/config.toml000066400000000000000000000013541456005336000173540ustar00rootroot00000000000000# baseURL is required to have the menu.shortcuts resolve correctly, but if # we use the full URL (with trailing libei/) local builds no longer fin. # the files. But since we use relativeURLs everything else should work correctly # even with the wrong baseURL. baseURL = 'https://libinput.pages.freedesktop.org/' languageCode = 'en-us' title = 'ei protocol documentation' theme = 'hugo-theme-relearn' relativeURLs = true [[menu.shortcuts]] name = " git Repository" identifier = "repo" url = "https://gitlab.freedesktop.org/libinput/libei" weight = 10 [[menu.shortcuts]] name = " Report a Bug" identifier = "bugs" url = "https://gitlab.freedesktop.org/libinput/libei/-/issues/" weight = 10 libei-1.2.1/doc/protocol/doc/000077500000000000000000000000001456005336000157545ustar00rootroot00000000000000libei-1.2.1/doc/protocol/doc/_index.md000066400000000000000000000001631456005336000175440ustar00rootroot00000000000000--- title: Protocol documentation draft: false archetype: "home" alwaysopen: true weight: 2 --- {{% children %}} libei-1.2.1/doc/protocol/doc/ei-scanner.md000066400000000000000000000062311456005336000203240ustar00rootroot00000000000000--- title: Code Generation draft: false archetype: "chapter" weight: 5 --- ## ei-scanner 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. Note: these generated protocol bindings are **not** part of libei's API contract. The `ei-scanner` is written in Python but is intended to be useful to anyone that needs generated bindings. It parses the [`protocol.xml`](https://gitlab.freedesktop.org/libinput/libei/-/blob/main/proto/protocol.xml) file and passes its content as data to [Jinja](https://jinja.palletsprojects.com/). See [here](https://gitlab.freedesktop.org/libinput/libei/-/blob/main/src/ei-proto.c.tmpl) for the template that generates the C source for libei and libeis. {{% notice style="primary" %}} If you plan to use the `ei-scanner` to generate language bindings, please get in contact with us through the [issue tracker](https://gitlab.freedesktop.org/libinput/libei/-/issues/). {{% /notice %}} ### Usage ``` $ ei-scanner --help usage: ei-scanner [-h] [--component {ei,eis,brei}] [--output OUTPUT] [--jinja-extra-data JINJA_EXTRA_DATA] [--jinja-extra-data-file JINJA_EXTRA_DATA_FILE] protocol template ei-scanner is a tool to parse the EI protocol description XML and pass the data to a Jinja2 template. That template can then be used to generate protocol bindings for the desired language. typical usages: ei-scanner --component=ei protocol.xml my-template.tpl ei-scanner --component=eis --output=bindings.rs protocol.xml bindings.rs.tpl Elements in the XML file are provided as variables with attributes generally matching the XML file. For example, each interface has requests, events and enums, and each of those has a name. ei-scanner additionally provides the following values to the Jinja2 templates: - interface.incoming and interface.outgoing: maps to the requests/events of the interface, depending on the component. - argument.signature: a single-character signature type mapping from the protocol XML type: uint32 -> "u" int32 -> "i" float -> "f" fd -> "h" new_id -> "n" object -> "o" string -> "s" ei-scanner adds the following Jinja2 filters for convenience: {{foo|c_type}} ... resolves to "struct foo *" {{foo|as_c_arg}} ... resolves to "struct foo *foo" {{foo_bar|camel}} ... resolves to "FooBar" positional arguments: protocol The protocol XML file template The Jinja2 compatible template file options: -h, --help show this help message and exit --component {ei,eis,brei} --output OUTPUT Output file to write to --jinja-extra-data JINJA_EXTRA_DATA Extra data (in JSON format) to pass through to the Jinja template as 'extra' --jinja-extra-data-file JINJA_EXTRA_DATA_FILE Path to file with extra data to pass through to the Jinja template as 'extra' ``` libei-1.2.1/doc/protocol/doc/initial-handshake.md000066400000000000000000000072721456005336000216630ustar00rootroot00000000000000--- title: Initial Connection Handshake draft: false archetype: "chapter" 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 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 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" %}}). Immediately after connecting, the EIS implementation must send the `ei_handshake.handshake_version` event. The client replies with the `ei_handshake.handshake_version` request to negotiate the version of the `ei_handshake` object. Version negotiation for other interfaces is also handled in the `ei_handshake` object. The client announces which interfaces it supports and their respective version, the EIS implementation should subsequently notify the client about the interface version it supports. Any object created by either the client or the EIS implementation must use the lower of client and EIS implementation supported version. The last message sent on the `ei_handshake` object is the `ei_handshake.connection` event. This event carries the object ID and version for the newly created `ei_connection` object. This object remains for the lifetime of the client is only destroyed when the client disconnects. A full example sequence from socket connection to the first event sent by the client is below: {{< mermaid >}} sequenceDiagram participant client participant EIS client->>EIS: connect to socket Note over client, EIS: ei_handshake object 0 EIS-->>client: ei_handshake.handshake_version(N) client->>EIS: ei_handshake.handshake_version(M) client->>EIS: ei_handshake.context_type(sender) client->>EIS: ei_handshake.name(some client) client->>EIS: ei_handshake.interface_version(ei_connection) client->>EIS: ei_handshake.interface_version(ei_callback) client->>EIS: ei_handshake.interface_version(ei_seat) client->>EIS: ei_handshake.interface_version(ei_device) client->>EIS: ei_handshake.interface_version(ei_pointer) client->>EIS: ei_handshake.interface_version(ei_keyboard) client->>EIS: ei_handshake.finish() EIS-->>client: ei_handshake.interface_version(ei_callback) EIS-->>client: ei_handshake.connection(new_id, version) Note over client, EIS: ei_handshake object is destroyed EIS-->>client: ei_connection.seat(new_id, version) EIS-->>client: ei_seat.name(some seat) EIS-->>client: ei_seat.capabilities() EIS-->>client: ei_seat.done() client->>EIS: ei_seat.bind() EIS-->>client: ei_seat.device(new_id, version) EIS-->>client: ei_device.name(some name) EIS-->>client: ei_device.device_type() EIS-->>client: ei_device.capabilities(some name) EIS-->>client: ei_device.interface(new_id, "ei_pointer", version) EIS-->>client: ei_device.interface(new_id, "ei_keyboard", version) EIS-->>client: ei_seat.device(new_id, version) EIS-->>client: ei_device.name(some name) EIS-->>client: ei_device.device_type() EIS-->>client: ei_device.capabilities(some name) EIS-->>client: ei_device.region() EIS-->>client: ei_device.interface(new_id, "ei_touchscreen", version) EIS-->>client: ei_device.resume() EIS-->>client: ei_device.resume() Note over client, EIS: client may emulate now client->>EIS: ei_pointer.start_emulating() client->>EIS: ei_pointer.motion() client->>EIS: ei_pointer.frame() {{< /mermaid >}} libei-1.2.1/doc/protocol/doc/overview.md000066400000000000000000000066651456005336000201610ustar00rootroot00000000000000--- title: Protocol Overview draft: false archetype: "chapter" weight: 1 --- ## Protocol Components The protocol is designed to connect two processes over a UNIX socket - an ei client and an EIS implementation (typically a Wayland compositor). {{< mermaid >}} flowchart LR; subgraph EIS implementation socket[[ei.socket]] end c1[ei client 1] -- ei protocol --> socket c2[ei client 2] -- ei protocol --> socket {{< /mermaid >}} The protocol is asynchronous and object-oriented. Each object on the wire supports zero or more **requests** and zero or more **events**. Requests are messages sent from an ei client to an EIS implementation, events are messages sent from the EIS implementation to the client. {{< mermaid >}} flowchart LR; ei -- request --> eis eis -- event --> ei {{< /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" %}}) 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" %}}) interface. All data on the protocol (e.g. object IDs) is private to that client's connection. The ei protocol is modelled closely after the Wayland protocol, but it is not binary compatible. ## Wire Format The wire format consists of a 3-element header comprising the `object-id` of the object, the length of the message and the opcode representing the message itself. ``` byte: |0 |4 |8 |12 |16 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. - `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 opcode, starting at 0. Requests and events have overlapping opcode ranges, i.e. the first request and the first event both have opcode 0. The header is followed by the message-specific arguments (if any). All arguments are 4 bytes or padded to a multiple of 4 bytes. All integers are in the EIS implementation's native byte order. ## Version negotiation For objects to be created, the EIS implementation and the client must agree on a supported version for each object. This agreement happens during the initial setup in `ei_handshake` - the client notifies the EIS implementation of the highest supported version for an interface, e.g. in the `ei_handshake.interface_version` request - the EIS implementation responds by selecting the highest version the EIS implementation supports but not higher than the client version. It **may** notify the client of that version before `ei_handshake.connection`. An exception to this is the `ei_handshake.handshake_version` request and event where the EIS implementation initializes the version exchange and thus the client picks the version number. In both cases, the version number used is simply `v = min(eis_version, client_version)`. Whenever an object is created, the version number of that object must be sent in the corresponding request or event. libei-1.2.1/doc/protocol/doc/specification.md000066400000000000000000000042211456005336000211150ustar00rootroot00000000000000--- title: Protocol Specification draft: false archetype: "chapter" weight: 3 --- The current protocol specification is available [in XML format here](https://gitlab.freedesktop.org/libinput/libei/-/tree/main/proto/protocol.xml) and the corresponding [XML DTD here](https://gitlab.freedesktop.org/libinput/libei/-/tree/main/proto/protocol.dtd). In that protocol specification: - 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 - an argument with an XML attribute `interface_arg` attribute indicates that an object in the same message is of the interface given in the named argument. - an enum with an XML attribute `bitfield` indicates a single-bit value. - each request, event or enum has an XML attribute `since` to indicate the interface version this request, event or enum was introduced in - each interface has an XML attribute `version` indicate the current version of this interface ## Protocol violations The term "Protocol Violation" is used to indicate that the client or the EIS implementation have sent a message that is not allowed by the protocol specification. A protocol violation must result in immediate disconnection. In the case of the EIS implementation it is permitted to send the `ei_connection.disconnect` event to the client to notify them of the protocol violation. ## Destructors A request or event marked as destructor causes the object to be destroyed by the sender immediately after sending the request or event. On the receiving side the object must thus be treated as defunct and no further interaction with the object is permitted. Note that due to the asynchronous nature of the protocol, some messages may have been sent before the destructor message was received. Both client and EIS implementation must handle this case gracefully. In the EIS implementation for example, it is usually enough to reply with the `ei_connection.invalid_object` event. libei-1.2.1/doc/protocol/doc/types.md000066400000000000000000000101111456005336000174340ustar00rootroot00000000000000--- title: Protocol Types draft: false archetype: "chapter" weight: 2 --- ## Protocol Types The protocol types are described as used in the [protocol specification](https://gitlab.freedesktop.org/libinput/libei/-/tree/main/proto/protocol.xml). All types are encoded in the EIS implementation's native byte order. | Type | Bits |Description | C type | Notes | | ------ | ----- | ---------------------- | ---------- | ------ | | uint32 | 32 | unsigned integer | `uint32_t` | | | int32 | 32 | signed integer | `int32_t` | | | uint64 | 64 | unsigned integer | `uint64_t` | | | 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" %}}) | [^1]: zero bytes in the message itself, transmitted in the overhead ### String encoding Strings are encoded as a length-prefixed zero-terminated UTF-8 string. Each string is prefixed by one 32-bit unsigned integer that specifies the length of the string *including the trailing null byte*. Then follows the string itself, zero-padded to the nearest multiple of 4 bytes. A NULL-string is thus 4 bytes long in the protocol, with a length of zero and no string: ``` [0x00, 0x00, 0x00, 0x00] ``` The empty string (`""`) is 8 bytes long, with a length of 1 and 4 bytes of zeros for the string null byte and 3 bytes of padding. Full (le) encoding: ``` [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] ``` The string "hello" is of length 6 but is zero-padded to 8 bytes and with the 4-byte length field thus takes 12 bytes in total. Full (le) encoding: ``` [0x06, 0x00, 0x00, 0x00, 'h', 'e', 'l', 'l', 'o', '\0', '\0\, '\0'] ``` ### Object IDs Object IDs are unique, monotonically increasing 64-bit integers that must not be re-used by the client or the EIS implementation. Object IDs created by the client start at 1 and must be less than `0xff00000000000000`. Object IDs allocated by the EIS implementation start at and must not be less than `0xff00000000000000`. Where a request or event specifies a `new_id` argument, it is the caller's responsibility to create a new Object ID and map that to the corresponding object. For example, in the `ei_handshake.connection` event, the EIS implementation will allocate an object in the permitted range and send it to the client. Both client and EIS implementation must refer to this object with this allocated ID. Once the object is destroyed, the ID is discarded and must not be re-used. ## Special items in the protocol ### Interface names Some requests and events, for example `ei_handshake.interface_version` require arguments that represent interface names. Interface names on the wire are always in the form "`ei_foo`", i.e. lowercase snake_case spelling, as used in the [protocol specification](https://gitlab.freedesktop.org/libinput/libei/-/tree/main/proto/protocol.xml). ### Serial numbers Some events have a `serial` argument corresponding to a serial number assigned by the EIS implementation. The serial number is a monotonically increasing unsigned 32-bit number (that wraps, clients must handle this). Some requests have a `last_serial` argument that clients must set to the most recently seen EIS serial number. The serial number aims to help a client track failure cases and the EIS implementation to detect errors caused by the protocol's asynchronicity. For example, if the EIS implementation pauses a device at the serial number N and subsequently receives an event with a `last_serial` of N-1, the EIS implementation knows that the events were caused by the client lagging behind and are not a protocol violation. The events can thus be discarded but the client does not need to be disconnected. libei-1.2.1/doc/protocol/generate-protocol-docs.sh000077500000000000000000000056631456005336000221370ustar00rootroot00000000000000#!/usr/bin/env bash # set -e REPO_BASE_DIR="$PWD" OUTDIR="$PWD" SITENAME="ei" # This can be dropped once we update to Fedora 39, the current F38 # version of hugo triggers errors with anything after # hugo-theme-relearn 1250bf30a8c2e3c1bfac285489bc63f2c395b641 # ERROR 2023/10/11 02:57:59 render of "page" failed: "/root/libei/build/doc/protocol/ei/themes/hugo-theme-relearn/layouts/_default/single.html:1:4": execute of template failed: template: _default/single.html:1:4: executing "_default/single.html" at : error calling partial: "/root/libei/build/doc/protocol/ei/themes/hugo-theme-relearn/layouts/partials/_main.hugo:1:4": execute of template failed: template: partials/_main.hugo:1:4: executing "partials/_main.hugo" at : error calling partialCached: "/root/libei/build/doc/protocol/ei/themes/hugo-theme-relearn/layouts/partials/page-meta.hugo:11:123": execute of template failed: template: partials/page-meta.hugo:11:123: executing "partials/page-meta.hugo" at : error calling index: index of untyped nil TEMPLATE_SHA=aa0f4089cb9c7691e7dc3aff003dddcde1ba02f4 while [[ $# -gt 0 ]]; do case "$1" in --verbose | -v) set -x shift ;; --scanner) SCANNER="$2" shift 2; ;; --protocol) PROTOFILE="$2" shift 2 ;; --template-dir) TEMPLATEDIR="$2" shift 2 ;; --output-dir) OUTDIR="$2" shift 2 ;; --git-repo) REPO_BASE_DIR="$2" shift 2; ;; **) echo "Unknown argument: $1" exit 1 ;; esac done if [[ -z "$SCANNER" ]]; then SCANNER="$REPO_BASE_DIR/proto/ei-scanner" fi if [[ -z "$PROTOFILE" ]]; then PROTOFILE="$REPO_BASE_DIR/proto/protocol.xml" fi if [[ -z "$TEMPLATEDIR" ]]; then TEMPLATEDIR="$REPO_BASE_DIR/doc/protocol/" fi SITEDIR="$OUTDIR/$SITENAME" if [[ -e "$SITEDIR" ]]; then echo "$SITEDIR already exists, updating" else hugo new site "$SITEDIR" git clone https://github.com/McShelby/hugo-theme-relearn "$SITEDIR/themes/hugo-theme-relearn" pushd "$SITEDIR/themes/hugo-theme-relearn" > /dev/null || exit 1 git reset --hard $TEMPLATE_SHA popd > /dev/null || exit 1 fi cp "$TEMPLATEDIR/config.toml" "$SITEDIR/" pushd "$TEMPLATEDIR" > /dev/null || exit 1 find . -type f -name "*.md" find . -type f -name "*.md" -exec install -D {} "$SITEDIR/content/{}" \; popd > /dev/null || exit 1 pushd "$SITEDIR" > /dev/null || exit 1 # Generate a list of available interfaces and read that # list line-by-line to generate a separate .md file # for each interface while read -r iface; do $SCANNER --component=ei --jinja-extra-data="{ \"interface\": \"$iface\" }" --output="$SITEDIR/content/interfaces/$iface.md" "$PROTOFILE" "$TEMPLATEDIR/interface.md.tmpl" done < <($SCANNER --component=ei "$PROTOFILE" - < /dev/null || exit 1 libei-1.2.1/doc/protocol/interface.md.tmpl000066400000000000000000000063571456005336000204570ustar00rootroot00000000000000{% for interface in interfaces %} {% if interface.name == extra.interface %} --- title: "{{interface.name}}" draft: false weight: {{loop.index}} --- # {{interface.description.summary|title}} {{interface.description.text|ei_escape_names}} {% if interface.enums %} # Enums {% raw %} {{% notice style="info" %}} Enum names are shown here in uppercase. The exact name depends on the language bindings. {{% /notice %}} {% endraw %} {% for enum in interface.enums %} ## {{interface.name}}.{{enum.name}} {{enum.description.text|ei_escape_names}} | Name | Value | Summary | | ---- | ----- | ------- | {% for entry in enum.entries %} | `{{entry.name|upper}}` | {{entry.value}} | {{entry.summary}} | {% endfor %} {% endfor %} {% endif %} {% if interface.requests %} # Requests {% for request in interface.requests %} ## {{interface.name}}.{{request.name}} {% raw %}{{% badge style="primary" title="Since Version" %}}{% endraw %}{{request.since}}{% raw %}{{% /badge %}}{% endraw %} {% raw %}{{% badge style="secondary" title="Request Opcode" %}}{% endraw %}{{request.opcode}}{% raw %}{{% /badge %}}{% endraw %} ``` {{interface.name}}.{{request.name}}({{request.arguments|join(", ", attribute="name")}}) ``` {% if request.arguments %} | Argument | Type | Summary | | -------- | ---- | ------- | {% for arg in request.arguments %} | {{arg.name}} | `{{arg.protocol_type}}` | {{arg.summary}} | {% endfor %} {% endif %} {% if request.is_destructor %} {% raw %} {{% notice default "Destructor" "skull-crossbones" %}} {% endraw %} Immediately after sending this request, the object is considered destroyed by the EIS implementation. It must no longer be used by the client. {% raw %} {{% /notice %}} {% endraw %} {% endif %} {% if request.context_type %} {% raw %} {{% notice style="note" %}} {% endraw %} This request is only available for clients of `ei_handshake.context_type.{{request.context_type}}`. {% raw %} {{% /notice %}} {% endraw %} {% endif %} {{request.description.text|ei_escape_names}} {% endfor %} {% endif %} {% if interface.events %} # Events {% for event in interface.events %} ## {{interface.name}}.{{event.name}} {% raw %}{{% badge style="primary" title="Since Version" %}}{% endraw %}{{event.since}}{% raw %}{{% /badge %}}{% endraw %} {% raw %}{{% badge style="secondary" title="Event Opcode" %}}{% endraw %}{{event.opcode}}{% raw %}{{% /badge %}}{% endraw %} ``` {{interface.name}}.{{event.name}}({{event.arguments|join(", ", attribute="name")}}) ``` {% if event.arguments %} | Argument| Type | Summary | | ------- | ---- | ------- | {% for arg in event.arguments %} | {{arg.name}} | `{{arg.protocol_type}}` | {{arg.summary}} | {% endfor %} {% endif %} {% if event.is_destructor %} {% raw %} {{% notice default "Destructor" "skull-crossbones" %}} {% endraw %} Immediately after sending this request, the object is considered destroyed by the EIS implementation. It must no longer be used by the client. {% raw %} {{% /notice %}} {% endraw %} {% endif %} {% if event.context_type %} {% raw %} {{% notice style="note" %}} {% endraw %} This event is only available for clients of `ei_handshake.context_type.{{event.context_type}}`. {% raw %} {{% /notice %}} {% endraw %} {% endif %} {{event.description.text|ei_escape_names}} {% endfor %} {% endif %} {% endif %} {% endfor %} libei-1.2.1/doc/protocol/interfaces/000077500000000000000000000000001456005336000173325ustar00rootroot00000000000000libei-1.2.1/doc/protocol/interfaces/_index.md000066400000000000000000000001601456005336000211170ustar00rootroot00000000000000--- title: Protocol Interfaces draft: false archetype: "home" alwaysopen: true weight: 1 --- {{% children %}} libei-1.2.1/doc/protocol/libraries/000077500000000000000000000000001456005336000171635ustar00rootroot00000000000000libei-1.2.1/doc/protocol/libraries/_index.md000066400000000000000000000054231456005336000207570ustar00rootroot00000000000000--- title: "C libraries" draft: false archetype: "home" alwaysopen: true weight: 3 --- The libei project provides several C libraries that abstract the protocol and provide an easy-to-use C API to interact with the respective EI counterpart. ## libei - the client library This library is intended for use by EI clients. This library should be used by processes that need to emulate devices or processes that need to receive input events from logical devices. The interface mirrors the [libinput API](https://gitlab.freedesktop.org/libinput/libinput/). A demo client is available in the [libei repository](https://gitlab.freedesktop.org/libinput/libei/-/blob/main/tools/ei-demo-client.c). The C library API documentation for libei is [here](https://libinput.pages.freedesktop.org/libei/api/group__libei.html). ## libeis - the server library This library is intended for use by EIS implementations. This library should be used by processes that have control over input devices, e.g. Wayland compositors. The interface mirrors the [libinput API](https://gitlab.freedesktop.org/libinput/libinput/). A demo server is available in the [libei repository](https://gitlab.freedesktop.org/libinput/libei/-/blob/main/tools/eis-demo-server.c). The C library API documentation for libeis is [here](https://libinput.pages.freedesktop.org/libei/api/group__libeis.html) ## liboeffis - DBus helper library This library is a helper library for applications that do not want to or cannot interact with the [XDG RemoteDesktop DBus portal](https://github.com/flatpak/xdg-desktop-portal) directly. liboeffis will: - connect to the DBus session bus and the ``org.freedesktop.portal.Desktop`` bus name - Start a ``org.freedesktop.portal.RemoteDesktop`` session, select the devices and invoke ``RemoteDesktop.ConnectToEIS()`` - Provide the returned file descriptor to the caller. This fd can be used by libei to initialize a context. - Close everything in case of error or disconnection The below diagram shows the simplified process: {{< mermaid >}} sequenceDiagram participant libei participant liboeffis participant portal as XDG Desktop Portal participant eis as EIS implementation liboeffis ->> portal: CreateSession portal ->> eis: CreateSession eis -->> portal: fd portal -->> liboeffis: fd liboeffis -->> libei: take fd for libei context libei ->> eis: setup connection libei ->> eis: emulate events {{< /mermaid >}} liboeffis is intentionally kept simple, any more complex needs should be handled by an application talking to DBus directly. A demo tool is available in the [libei repository](https://gitlab.freedesktop.org/libinput/libei/-/blob/main/tools/oeffis-demo-tool.c). The C library API documentation for liboeffis is [here](https://libinput.pages.freedesktop.org/libei/api/group__liboeffis.html). libei-1.2.1/doc/protocol/meson.build000066400000000000000000000012501456005336000173470ustar00rootroot00000000000000if 'protocol' not in get_option('documentation') subdir_done() endif hugo = find_program('hugo', required : false) if not hugo.found() error('Program "hugo" not found or not executable. Try building with -Ddocumentation=false') endif src_script = files('generate-protocol-docs.sh') hugo_script = find_program(src_script) src_hugo = files( 'config.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, ) libei-1.2.1/meson.build000066400000000000000000000105361456005336000147500ustar00rootroot00000000000000project('libei', 'c', version: '1.2.1', license: 'MIT', default_options: [ 'c_std=gnu11', 'warning_level=2' ], meson_version: '>= 0.56.0') libei_version = meson.project_version().split('.') libei_version_major = libei_version[0].to_int() libei_version_minor = libei_version[1].to_int() if libei_version.length() > 2 libei_version_patchlevel = libei_version[2].to_int() else libei_version_patchlevel = 0 endif # Our headers are in libei-1.0 until we hit 2.0 etc. libei's API is # backwards-compatible until the major version bump. libei_api_version = '1.0' 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. soname = meson.project_version() pkgconfig = import('pkgconfig') fs = import('fs') cc = meson.get_compiler('c') cflags =[ '-Wno-unused-parameter', '-Wmissing-prototypes', '-Wno-missing-field-initializers', '-Wstrict-prototypes', '-Wstrict-prototypes', '-Wlogical-op', '-Wpointer-arith', '-Wuninitialized', '-Winit-self', '-Wstrict-prototypes', '-Wimplicit-fallthrough', '-Wredundant-decls', '-Wincompatible-pointer-types', '-Wformat=2', '-Wformat-overflow=2', '-Wformat-signedness', '-Wformat-truncation=2', '-Wmissing-declarations', '-Wshift-overflow=2', '-Wstrict-overflow=2', '-Wswitch', ] if cc.get_id() == 'clang' cflags += [ # clang doesn't think just using _unref_ is a use of the variable # _unref_(foo) *bar = something_that_gives_a_ref # but we make heavy use of that in the test suite for convenience # of events we know must exist but we don't care about specifically '-Wno-unused-variable', ] endif add_project_arguments(cc.get_supported_arguments(cflags), language: 'c') inc_builddir = include_directories('.') inc_src = include_directories('src') config_h = configuration_data() 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 ') config_h.set10('HAVE_MEMFD_CREATE', true) endif dep_math = cc.find_library('m', required: false) dep_epoll = dependency('epoll-shim', required: false) dep_libxkbcommon = dependency('xkbcommon', required: false) config_h.set10('HAVE_LIBXKBCOMMON', dep_libxkbcommon.found()) dep_libevdev = dependency('libevdev', required: false) config_h.set10('HAVE_LIBEVDEV', dep_libevdev.found()) if not get_option('liboeffis').disabled() sd_bus_provider = get_option('sd-bus-provider') if sd_bus_provider == 'auto' message('Trying to find an sd-bus provider...') providers = ['libsystemd', 'libelogind', 'basu'] foreach provider: providers if dependency(provider, required: false).found() message('Using sd-bus provider "@0@"'.format(provider)) sd_bus_provider = provider break endif endforeach if sd_bus_provider == 'auto' error('Failed to find an sd-bus provider, tried @0@'.format(', '.join(providers))) endif summary({'sd-bus-provider': sd_bus_provider}, section: 'Conditional Features') endif dep_sdbus = dependency(sd_bus_provider, required: get_option('liboeffis')) else dep_sdbus = dependency('', required: false) endif 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') configure_file(output: 'config.h', configuration: config_h) subdir('proto') subdir('src') 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', ) endif libei-1.2.1/meson_options.txt000066400000000000000000000007061456005336000162410ustar00rootroot00000000000000option('documentation', type: 'array', value: [], choices: ['api', 'protocol'], description: 'Enable documentation builds') 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') libei-1.2.1/proto/000077500000000000000000000000001456005336000137445ustar00rootroot00000000000000libei-1.2.1/proto/ei-scanner000077500000000000000000001033361456005336000157240ustar00rootroot00000000000000#!/usr/bin/env python3 # # SPDX-License-Identifier: MIT """ EI protocol parser This parser is intended to be generically useful for language bindings other than libei/libeis. If it isn't, please file a bug. When used as ei-scanner, it converts a Jinja2 template with the scanned protocol. Otherwise, use the `parse()` function to parse the protocol and return its structure as a set of Python classes. Opcodes for events and request are assigned in order as they appear in the XML file. """ from typing import Any, Dict, List, Optional, Tuple, Union from pathlib import Path from textwrap import dedent import argparse import attr import jinja2 import jinja2.environment import os import sys import xml.sax import xml.sax.handler """ Mapping of allowed protocol types to the single-character signature strings used in the various code pieces. """ PROTOCOL_TYPES = { "uint32": "u", "int32": "i", "uint64": "t", "int64": "x", "float": "f", "fd": "h", "new_id": "n", "object": "o", "string": "s", } def snake2camel(s: str) -> str: """ Convert snake_case to CamelCase (well, strictly speaking PascalCase """ return s.replace("_", " ").title().replace(" ", "") @attr.s class Description: summary: str = attr.ib(default="") text: str = attr.ib(default="") @attr.s 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() interface_arg: Optional["Argument"] = attr.ib(default=None) """ For an argument with "interface_arg", this field points to the argument that contains the interface name. """ interface_arg_for: Optional["Argument"] = attr.ib(default=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"] = attr.ib(default=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"] = attr.ib(default=None) """ For an argument referenced by another argument of type "new_id", this field points to the other argument that references this argument. """ @property def signature(self) -> str: """ The single-character signature for this 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}" @property def c_type(self) -> str: return { "uint32": "uint32_t", "int32": "int32_t", "uint64": "uint64_t", "int64": "int64_t", "string": "const char *", "fd": "int", "float": "float", "object": "object_id_t", "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, name: str, protocol_type: str, summary: str = "", enum: Optional["Enum"] = None, interface: Optional["Interface"] = None, ) -> "Argument": return cls( name=name, protocol_type=protocol_type, summary=summary, enum=enum, interface=interface, ) @attr.s 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) arguments: List[Argument] = attr.ib(init=False, 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 add_argument(self, arg: Argument) -> None: if arg.name in [a.name for a in self.arguments]: raise ValueError(f"Duplicate argument name '{arg.name}'") self.arguments.append(arg) @property def num_arguments(self) -> int: return len(self.arguments) @property def signature(self) -> str: return "".join([a.signature for a in self.arguments]) @property 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 class Request(Message): @classmethod def create( cls, name: str, opcode: int, interface: "Interface", since: int = 1, is_destructor: bool = False, ) -> "Request": return cls( name=name, opcode=opcode, since=since, interface=interface, is_destructor=is_destructor, ) @property def fqdn(self) -> str: """ The full name of this Request as _request_ """ return f"{self.interface.name}_request_{self.name}" @attr.s class Event(Message): @classmethod def create( cls, name: str, opcode: int, interface: "Interface", since: int = 1, is_destructor: bool = False, ) -> "Event": return cls( name=name, opcode=opcode, since=since, interface=interface, is_destructor=is_destructor, ) @property def fqdn(self) -> str: """ The full name of this Event as _event_ """ return f"{self.interface.name}_event_{self.name}" @attr.s 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() @classmethod def create( cls, name: str, value: int, enum: "Enum", summary: str = "", since: int = 1 ) -> "Entry": return cls(name=name, value=value, enum=enum, summary=summary, since=since) @property def fqdn(self) -> str: """ The full name of this Entry as __ """ return f"{self.enum.fqdn}_{self.name}" @attr.s 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) entries: List[Entry] = attr.ib(init=False, factory=list) @classmethod def create( cls, name: str, interface: "Interface", since: int = 1, is_bitfield: bool = False, ) -> "Enum": return cls(name=name, since=since, interface=interface, is_bitfield=is_bitfield) def add_entry(self, entry: Entry) -> None: for e in self.entries: if e.name == entry.name: raise ValueError(f"Duplicate enum name '{entry.name}'") if e.value == entry.value: raise ValueError(f"Duplicate enum value '{entry.value}'") if self.is_bitfield: if e.value < 0: raise ValueError("Bitmasks must not be less than zero") try: if e.value.bit_count() > 1: raise ValueError("Bitmasks must have exactly one bit set") except AttributeError: pass # bit_count() requires Python 3.10 self.entries.append(entry) @property def fqdn(self): """ The full name of this Enum as _ """ return f"{self.interface.name}_{self.name}" @property def camel_name(self) -> str: return snake2camel(self.name) @attr.s 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) mode: str = attr.ib(validator=attr.validators.in_(["ei", "eis", "brei"])) description: Optional[Description] = attr.ib(default=None) @property def name(self) -> str: """ Returns the mode-adjusted name of the interface, i.e. this may return "ei_pointer", "eis_pointer", "brei_pointer", etc. depending on the mode. """ return Interface.mangle_name(self.protocol_name, self.mode) @property def plainname(self) -> str: """ Returns the plain name of the interface, i.e. this returns "pointer", "handshake", etc. without the "ei_" or "eis_" prefix. """ if self.protocol_name.startswith("ei_"): return f"{self.protocol_name[3:]}" return self.protocol_name @staticmethod def mangle_name(name: str, component: str) -> str: """ Returns the mangled interface name with the component as prefix (e.g. eis_device). The XML only uses `ei_` as prefix, so let's replace that accordingly. """ if name.startswith("ei"): return f"{component}{name[2:]}" return name def add_request(self, request: Request) -> None: if request.name in [r.name for r in self.requests]: raise ValueError(f"Duplicate request name '{request.name}'") self.requests.append(request) def add_event(self, event: Event) -> None: if event.name in [r.name for r in self.events]: raise ValueError(f"Duplicate event name '{event.name}'") self.events.append(event) def add_enum(self, enum: Enum) -> None: if enum.name in [r.name for r in self.enums]: raise ValueError(f"Duplicate enum name '{enum.name}'") self.enums.append(enum) def find_enum(self, name: str) -> Optional[Enum]: for e in self.enums: if e.name == name: return e return None @property def outgoing(self) -> List[Message]: """ Returns the list of messages outgoing from this implementation. We use the same class for both ei and eis. To make the template simpler, the class maps requests/events to incoming/outgoing as correct relative to the implementation. """ if self.mode == "ei": return self.requests # type: ignore elif self.mode == "eis": return self.events # type: ignore else: raise NotImplementedError( f"Interface.outgoing is not supported for mode {self.mode}" ) @property def incoming(self) -> List[Message]: """ Returns the list of messages incoming to this implementation. We use the same class for both ei and eis. To make the template simpler, the class maps requests/events to incoming/outgoing as correct relative to the implementation. """ if self.mode == "ei": return self.events # type: ignore elif self.mode == "eis": return self.requests # type: ignore else: raise NotImplementedError( f"Interface.incoming is not supported for mode {self.mode}" ) @property def c_type(self) -> str: return f"struct {self.name} *" @property def as_c_arg(self) -> str: return f"{self.c_type} {self.name}" @property def camel_name(self) -> str: return snake2camel(self.name) @classmethod def create(cls, protocol_name: str, version: int, mode: str = "ei") -> "Interface": assert mode in ["ei", "eis", "brei"] return cls(protocol_name=protocol_name, version=version, mode=mode) @attr.s class XmlError(Exception): line: int = attr.ib() column: int = attr.ib() message: str = attr.ib() def __str__(self) -> str: return f"line {self.line}:{self.column}: {self.message}" @classmethod def create(cls, message: str, location: Tuple[int, int] = (0, 0)) -> "XmlError": return cls(line=location[0], column=location[1], message=message) @attr.s class Copyright: text: str = attr.ib(default="") is_complete: bool = attr.ib(init=False, default=False) @attr.s class Protocol: copyright: Optional[str] = attr.ib(default=None) interfaces: List[Interface] = attr.ib(factory=list) @attr.s 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) 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) # A dict of arg name to interface_arg name mappings current_interface_arg_names: Dict[str, str] = attr.ib(init=False, default=attr.Factory(dict)) # type: ignore current_new_id_arg: Optional[Argument] = attr.ib(init=False, default=None) _run_counter: int = attr.ib(init=False, default=0, repr=False) @property def location(self) -> Tuple[int, int]: line = self._locator.getLineNumber() # type: ignore col = self._locator.getColumnNumber() # type: ignore return line, col def interface_by_name(self, protocol_name: str) -> Interface: """ Look up an interface by its protocol name (i.e. always "ei_foo", regardless of what we're generating). """ try: return [ iface for iface in self.interfaces if iface.protocol_name == protocol_name ].pop() except IndexError: raise XmlError.create( f"Unable to find interface {protocol_name}", self.location ) def startDocument(self): self._run_counter += 1 def startElement(self, element: str, attrs: dict): if element == "interface": if self.current_interface is not None: raise XmlError.create( f"Invalid element '{element}' inside interface '{self.current_interface.name}'", self.location, ) try: name = attrs["name"] version = attrs["version"] except KeyError as e: raise XmlError.create( f"Missing attribute {e} in element '{element}'", self.location, ) protocol_name = name # We only create the interface on the first run, in subsequent runs we # re-use them so we can cross reference correctly if self._run_counter > 1: intf = self.interface_by_name(protocol_name) else: intf = Interface.create( protocol_name=protocol_name, version=version, mode=self.component, ) self.interfaces.append(intf) self.current_interface = intf # first run only parses interfaces if self._run_counter <= 1: return if element == "enum": if self.current_interface is None: raise XmlError.create( f"Invalid element '{element}' outside an ", self.location, ) if self.current_message is not None: raise XmlError.create( f"Invalid element '{element}' inside '{self.current_message.name}'", self.location, ) try: name = attrs["name"] since = attrs["since"] except KeyError as e: raise XmlError.create( f"Missing attribute {e} in element '{element}'", self.location, ) try: is_bitfield = { "true": True, "false": False, }[attrs.get("bitfield", "false")] except KeyError as e: raise XmlError.create( f"Invalid value {e} for boolean bitfield attribute in '{element}'", self.location, ) # We only create the enum on the second run, in subsequent runs # we re-use them so we can cross-reference correctly if self._run_counter > 2: enum = self.current_interface.find_enum(name) if enum is None: raise XmlError.create( f"Invalid enum {name}. This is a parser bug", self.location, ) else: enum = Enum.create( name=name, since=since, interface=self.current_interface, is_bitfield=is_bitfield, ) try: self.current_interface.add_enum(enum) except ValueError as e: raise XmlError.create(str(e), self.location) self.current_message = enum # second run only parses enums if self._run_counter <= 2: return if element == "request": if self.current_interface is None: raise XmlError.create( f"Invalid element '{element}' outside an ", self.location, ) try: name = attrs["name"] since = attrs["since"] except KeyError as e: raise XmlError.create( f"Missing attribute {e} in element '{element}'", self.location, ) is_destructor = attrs.get("type", "") == "destructor" opcode = len(self.current_interface.requests) request = Request.create( name=name, since=since, opcode=opcode, interface=self.current_interface, is_destructor=is_destructor, ) request.context_type = attrs.get("context-type") try: self.current_interface.add_request(request) except ValueError as e: raise XmlError.create(str(e), self.location) self.current_message = request elif element == "event": if self.current_interface is None: raise XmlError.create( f"Invalid element '{element}' outside an ", self.location, ) if self.current_message is not None: raise XmlError.create( f"Invalid element '{element}' inside '{self.current_message.name}'", self.location, ) try: name = attrs["name"] since = attrs["since"] except KeyError as e: raise XmlError.create( f"Missing attribute {e} in element '{element}'", self.location, ) is_destructor = attrs.get("type", "") == "destructor" opcode = len(self.current_interface.events) event = Event.create( name=name, since=since, opcode=opcode, interface=self.current_interface, is_destructor=is_destructor, ) event.context_type = attrs.get("context-type") try: self.current_interface.add_event(event) except ValueError as e: raise XmlError.create(str(e), self.location) self.current_message = event elif element == "arg": if self.current_interface is None: raise XmlError.create( f"Invalid element '{element}' outside an ", self.location, ) if not isinstance(self.current_message, Message): raise XmlError.create( f"Invalid element '{element}' must be inside or ", self.location, ) name = attrs["name"] proto_type = attrs["type"] if proto_type not in PROTOCOL_TYPES: raise XmlError.create( f"Invalid type '{proto_type}' for '{self.current_interface.name}.{self.current_message.name}::{name}'", self.location, ) summary = attrs.get("summary", "") interface_name = attrs.get("interface", None) if interface_name is not None: 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: if "." in enum_name: iname, enum_name = enum_name.split(".") intf = self.interface_by_name(iname) else: intf = self.current_interface enum = intf.find_enum(enum_name) if enum is None: raise XmlError.create( f"Failed to find enum '{intf.name}.{enum_name}'", self.location, ) arg = Argument.create( name=name, protocol_type=proto_type, summary=summary, enum=enum, interface=interface, ) 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( f"Invalid element '{element}' outside an ", self.location, ) if not isinstance(self.current_message, Enum): raise XmlError.create( f"Invalid element '{element}' must be inside ", self.location, ) name = attrs["name"] value = int(attrs["value"]) summary = attrs.get("summary", "") since = int(attrs.get("since", 1)) entry = Entry.create( name=name, value=value, enum=self.current_message, summary=summary, since=since, ) try: self.current_message.add_entry(entry) except ValueError as e: raise XmlError.create(str(e), self.location) elif element == "description": summary = attrs.get("summary", "") self.current_description = Description(summary=summary) elif element == "copyright": if self.copyright is not None: raise XmlError.create( "Multiple tags in file", self.location ) self.copyright = Copyright() def endElement(self, name): if name == "interface": assert self.current_interface is not None self.current_interface = None # first run only parses interfaces if self._run_counter <= 1: return if name == "enum": assert isinstance(self.current_message, Enum) self.current_message = None # second run only parses interfaces and enums 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 elif name == "event": assert isinstance(self.current_message, Event) self.current_message = None elif name == "description": assert self.current_description is not None self.current_description.text = dedent(self.current_description.text) if self.current_message is None: assert self.current_interface is not None self.current_interface.description = self.current_description else: self.current_message.description = self.current_description self.current_description = None elif name == "copyright": assert self.copyright is not None self.copyright.text = dedent(self.copyright.text) self.copyright.is_complete = True def characters(self, content): if self.current_description is not None: self.current_description.text += content elif self.copyright is not None and not self.copyright.is_complete: self.copyright.text += content @classmethod def create(cls, component: str) -> "ProtocolParser": h = cls(component=component) return h def parse(protofile: Path, component: str) -> Protocol: proto = ProtocolParser.create(component=component) xml.sax.parse(os.fspath(protofile), proto) # We parse three times, once to fetch all the interfaces, one for enums, then to parse the details xml.sax.parse(os.fspath(protofile), proto) xml.sax.parse(os.fspath(protofile), proto) copyright = proto.copyright.text if proto.copyright else None return Protocol( copyright=copyright, interfaces=proto.interfaces, ) def generate_source( proto: Protocol, template: str, component: str, extra_data: Optional[dict] ) -> jinja2.environment.TemplateStream: assert component in ["ei", "eis", "brei"] data: dict[str, Any] = {} data["component"] = component data["interfaces"] = proto.interfaces data["extra"] = extra_data loader: jinja2.BaseLoader if template == "-": loader = jinja2.FunctionLoader(lambda _: sys.stdin.read()) filename = "" else: path = Path(template) assert path.exists(), f"Failed to find template {path}" filename = path.name loader = jinja2.FileSystemLoader(os.fspath(path.parent)) env = jinja2.Environment( loader=loader, trim_blocks=True, lstrip_blocks=True, ) # jinja filter to convert foo into "struct foo *" def filter_c_type(name): return f"struct {name} *" # jinja filter to convert foo into "struct foo *foo" def filter_as_c_arg(name): return f"struct {name} *{name}" # escape any ei[s]?_foo.bar with markdown backticks def filter_ei_escape_names(str, quotes="`"): if not str: return str import re 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 env.filters["camel"] = snake2camel env.filters["ei_escape_names"] = filter_ei_escape_names jtemplate = env.get_template(filename) return jtemplate.stream(data) def scanner(argv: list[str]) -> None: parser = argparse.ArgumentParser( description=dedent( """ ei-scanner is a tool to parse the EI protocol description XML and pass the data to a Jinja2 template. That template can then be used to generate protocol bindings for the desired language. typical usages: ei-scanner --component=ei protocol.xml my-template.tpl ei-scanner --component=eis --output=bindings.rs protocol.xml bindings.rs.tpl Elements in the XML file are provided as variables with attributes generally matching the XML file. For example, each interface has requests, events and enums, and each of those has a name. ei-scanner additionally provides the following values to the Jinja2 templates: - interface.incoming and interface.outgoing: maps to the requests/events of the interface, depending on the component. - argument.signature: a single-character signature type mapping from the protocol XML type: uint32 -> "u" int32 -> "i" float -> "f" fd -> "h" new_id -> "n" object -> "o" string -> "s" ei-scanner adds the following Jinja2 filters for convenience: {{foo|c_type}} ... resolves to "struct foo *" {{foo|as_c_arg}} ... resolves to "struct foo *foo" {{foo_bar|camel}} ... resolves to "FooBar" """ ), formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument( "--component", type=str, choices=["ei", "eis", "brei"], default="ei" ) parser.add_argument( "--output", type=str, default="-", help="Output file to write to" ) parser.add_argument("protocol", type=Path, help="The protocol XML file") parser.add_argument( "--jinja-extra-data", type=str, help="Extra data (in JSON format) to pass through to the Jinja template as 'extra'", default=None, ) parser.add_argument( "--jinja-extra-data-file", type=Path, help="Path to file with extra data to pass through to the Jinja template as 'extra'", default=None, ) parser.add_argument( "template", type=str, help="The Jinja2 compatible template file" ) ns = parser.parse_args(argv) assert ns.protocol.exists() try: proto = parse( protofile=ns.protocol, component=ns.component, ) except xml.sax.SAXParseException as e: print(f"Parser error: {e}", file=sys.stderr) raise SystemExit(1) except XmlError as e: print(f"Protocol XML error: {e}", file=sys.stderr) raise SystemExit(1) if ns.jinja_extra_data is not None: import json extra_data = json.loads(ns.jinja_extra_data) elif ns.jinja_extra_data_file is not None: if ns.jinja_extra_data_file.name.endswith( ".yml" ) or ns.jinja_extra_data_file.name.endswith(".yaml"): import yaml with open(ns.jinja_extra_data_file) as fd: extra_data = yaml.safe_load(fd) elif ns.jinja_extra_data_file.name.endswith(".json"): import json 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) raise SystemExit(1) else: extra_data = None stream = generate_source( proto=proto, template=ns.template, component=ns.component, extra_data=extra_data ) file = sys.stdout if ns.output == "-" else open(ns.output, "w") stream.dump(file) if __name__ == "__main__": scanner(sys.argv[1:]) libei-1.2.1/proto/meson.build000066400000000000000000000013061456005336000161060ustar00rootroot00000000000000scanner_source = files('ei-scanner') scanner = find_program(scanner_source) protocol_xml_path = meson.current_source_dir() / 'protocol.xml' protocol_dtd_path = meson.current_source_dir() / 'protocol.dtd' protocol_xml = files(protocol_xml_path) protocol_dtd = files(protocol_dtd_path) xmllint = find_program('xmllint', required: false) if xmllint.found() test('dtdcheck', xmllint, args: ['--dtdvalid', protocol_dtd, protocol_xml] ) endif pymod = import('python') required_python_modules = ['attr', '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 libei-1.2.1/proto/protocol.dtd000066400000000000000000000026471456005336000163130ustar00rootroot00000000000000 libei-1.2.1/proto/protocol.xml000066400000000000000000002115141456005336000163330ustar00rootroot00000000000000 Copyright © 2008-2011 Kristian Høgsberg Copyright © 2010-2011 Intel Corporation Copyright © 2012-2013 Collabora, Ltd. Copyright © 2023 Red Hat, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This is a special interface to setup the client as seen by the EIS implementation. The object for this interface has the fixed object id 0 and only exists until the connection has been set up, see the ei_handshake.connection event. The ei_handshake version is 1 until: - the EIS implementation sends the interface_version event with a version other than 1, and, in response, - the client sends the interface_version request with a version equal or lower to the EIS implementation version. The EIS implementation must send the interface_version event immediately once the physical connection has been established. Once the ei_connection.connection event has been sent the handshake is destroyed by the EIS implementation. Notifies the EIS implementation that this client supports the given version of the ei_handshake interface. The version number must be less than or equal to the version in the handshake_version event sent by the EIS implementation when the connection was established. Immediately after sending this request, the client must assume the negotiated version number for the ei_handshake interface and the EIS implementation may send events and process requests matching that version. This request must be sent exactly once and it must be the first request the client sends. Notify the EIS implementation that configuration is complete. In the future (and possibly after requiring user interaction), the EIS implementation responds by sending the ei_handshake.connection event. This enum denotes context types for the libei context. A context type of receiver is a libei context receiving events from the EIS implementation. A context type of sender is a libei context sending events to the EIS implementation. Notify the EIS implementation of the type of this context. The context types defines whether the client will send events to or receive events from the EIS implementation. Depending on the context type, certain requests must not be used and some events must not be sent by the EIS implementation. This request is optional, the default client type is context_type.receiver. This request must not be sent more than once and must be sent before ei_handshake.finish. Notify the EIS implementation of the client name. The name is a human-presentable UTF-8 string and should represent the client name as accurately as possible. This name may be presented to the user for identification of this client (e.g. to confirm the client has permissions to connect). There is no requirement for the EIS implementation to use this name. For example, where the client is managed through an XDG Desktop Portal an EIS implementation would typically use client identification information sent by the portal instead. This request is optional, the default client name is implementation-defined. This request must not be sent more than once and must be sent before ei_handshake.finish. Notify the EIS implementation that the client supports the given named interface with the given maximum version number. Future objects created by the EIS implementation will use the respective interface version (or any lesser version). This request must be sent for the "ei_connection" interface, failing to do so will result in the EIS implementation disconnecting the client on ei_handshake.finish. This request must not be sent for the "ei_handshake" interface, use the ei_handshake.handshake_version request instead. Note that an EIS implementation may consider some interfaces to be required and immediately ei_connection.disconnect a client not supporting those interfaces. This request must not be sent more than once per interface and must be sent before ei_handshake.finish. This event is sent exactly once and immediately after connection to the EIS implementation. In response, the client must send the ei_handshake.handshake_version request with any version up to including the version provided in this event. See the ei_handshake.handshake_version request for details on what happens next. Notifies the client that the EIS implementation supports the given named interface with the given maximum version number. This event must be sent by the EIS implementation for any interfaces that supports client-created objects (e.g. "ei_callback") before the ei_handshake.connection event. The client must not assume those interfaces are supported unless and until those versions have been received. This request must not be sent for the "ei_handshake" interface, use the handshake_version event instead. This event may be sent by the EIS implementation for any other supported interface (but not necessarily all supported interfaces) before the ei_handshake.connection event. Provides the client with the connection object that is the top-level object for all future requests and events. This event is sent exactly once at some unspecified time after the client sends the ei_handshake.finish request to the EIS implementation. The ei_handshake object will be destroyed by the EIS implementation immediately after this event has been sent, a client must not attempt to use it after that point. The version sent by the EIS implementation is the version of the "ei_connection" interface as announced by ei_handshake.interface_version, or any lower version. The serial number is the start value of the EIS implementation's serial number sequence. Clients must not assume any specific value for this serial number. Any future serial number in any event is monotonically increasing by an unspecified amount. The core connection object. This is the top-level object for any communication with the EIS implementation. Note that for a client to receive this object, it must announce support for this interface in ei_handshake.interface_version. The sync request asks the EIS implementation to emit the 'done' event on the returned ei_callback object. Since requests are handled in-order and events are delivered in-order, this can be used as a synchronization point to ensure all previous requests and the resulting events have been handled. The object returned by this request will be destroyed by the EIS implementation after the callback is fired and as such the client must not attempt to use it after that point. The callback_data in the ei_callback.done event is always zero. Note that for a client to use this request it must announce support for the "ei_callback" interface in ei_handshake.interface_version. It is a protocol violation to request sync without having announced the "ei_callback" interface and the EIS implementation must disconnect the client. A request to the EIS implementation that this client should be disconnected. This is a courtesy request to allow the EIS implementation to distinquish between a client disconnecting on purpose and one disconnecting through the socket becoming invalid. Immediately after sending this request, the client may destroy the ei_connection object and it should close the socket. The EIS implementation will treat the connection as already disconnected on receipt and does not send the ei_connection.disconnect event in response to this request. A reason why a client was disconnected. This enum is intended to provide information to the client on whether it was disconnected as part of normal operations or as result of an error on either the client or EIS implementation side. A nonzero value describes an error, with the generic value "error" (1) reserved as fallback. This enum may be extended in the future, clients must be able to handle values that are not in their supported version of this enum. This event may be sent by the EIS implementation immediately before the client is disconnected. The last_serial argument is set to the last serial number used in a request by the client or zero if the client has not yet issued a request. Where a client is disconnected by EIS on purpose, for example after a user interaction, the reason is disconnect_reason.disconnected (i.e. zero) and the explanation is NULL. Where a client is disconnected due to some invalid request or other protocol error, the reason is one of disconnect_reason (i.e. nonzero) and explanation may contain a string explaining why. This string is intended to help debugging only and is not guaranteed to stay constant. The ei_connection object will be destroyed by the EIS implementation immediately after this event has been sent, a client must not attempt to use it after that point. There is no guarantee this event is sent - the connection may be closed without a disconnection event. Notification that a new seat has been added. A seat is a set of input devices that logically belong together. This event is only sent if the client announced support for the "ei_seat" interface in ei_handshake.interface_version. The interface version is equal or less to the client-supported version in ei_handshake.interface_version for the "ei_seat" interface. Notification that an object ID used in an earlier request was invalid and does not exist. This event is sent by the EIS implementation when an object that does not exist as seen by the EIS implementation. The protocol is asynchronous and this may occur e.g. when the EIS implementation destroys an object at the same time as the client requests functionality from that object. For example, an EIS implementation may send ei_device.destroyed and destroy the device's resources (and protocol object) at the same time as the client attempts to ei_device.start_emulating on that object. It is the client's responsibilty to unwind any state changes done to the object since the last successful message. The ping event asks the client to emit the 'done' event on the provided ei_callback object. Since requests are handled in-order and events are delivered in-order, this can be used as a synchronization point to ensure all previous requests and the resulting events have been handled. The object returned by this request must be destroyed by the ei client implementation after the callback is fired and as such the client must not attempt to use it after that point. The callback_data in the resulting ei_pingpong.done request is ignored by the EIS implementation. Note that for a EIS implementation to use this request the client must announce support for this interface in ei_handshake.interface_version. It is a protocol violation to send this event to a client without the "ei_pingpong" interface. Interface for ensuring a roundtrip to the EIS implementation. Clients can handle the 'done' event to get notified when the related request that created the ei_callback object is done. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notify the client when the related request is done. Immediately after this event the ei_callback object is destroyed by the EIS implementation and as such the client must not attempt to use it after that point. Interface for ensuring a roundtrip to the client implementation. This interface is identical to ei_callback but is intended for the EIS implementation to enforce a roundtrip to the client. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notify the EIS implementation when the related event is done. Immediately after this request the ei_pingpong object is destroyed by the client and as such must not be used any further. An ei_seat represents a set of input devices that logically belong together. In most cases only one seat is present and all input devices on that seat share the same pointer and keyboard focus. A seat has potential capabilities, a client is expected to bind to those capabilities. The EIS implementation then creates logical input devices based on the capabilities the client is interested in. Immediately after creation of the ei_seat object, the EIS implementation sends a burst of events with information about this seat. This burst of events is terminated by the ei_seat.done event. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notification that the client is no longer interested in this seat. The EIS implementation will release any resources related to this seat and send the ei_seat.destroyed event once complete. Note that releasing a seat does not guarantee another seat becomes available. In other words, in most single-seat cases, releasing the seat means the connection becomes effectively inert. Bind to the bitmask of capabilities given. The bitmask is zero or more of the masks representing an interface as provided in the ei_seat.capability event. See the ei_seat.capability event documentation for examples. Binding masks that are not supported in the ei_device's interface version is a client bug and may result in disconnection. A client may send this request multiple times to adjust the capabilities it is interested in. If previously-bound capabilities are dropped by the client, the EIS implementation may ei_device.remove devices that have these capabilities. This seat has been removed and a client should release all associated resources. This ei_seat object will be destroyed by the EIS implementation immmediately after after this event is sent and as such the client must not attempt to use it after that point. The name of this seat, if any. This event is optional and sent once immediately after object creation. It is a protocol violation to send this event after the ei_seat.done event. A notification that this seat supports devices with the given interface. The interface is mapped to a bitmask by the EIS implementation. A client may then binary OR these bitmasks in ei_seat.bind. In response, the EIS implementation may then create device based on those bound capabilities. For example, an EIS implementation may map "ei_pointer" to 0x1, "ei_keyboard" to 0x4 and "ei_touchscreen" to 0x8. A client may then ei_seat.bind(0xc) to bind to keyboard and touchscreen but not pointer. Note that as shown in this example the set of masks may be sparse. The value of the mask is contant for the lifetime of the seat but may differ between seats. Note that seat capabilities only represent a mask of possible capabilities on devices in this seat. A capability that is not available on the seat cannot ever be available on any device in this seat. For example, a seat that only has the pointer and keyboard capabilities can never have a device with the touchscreen capability. It is up to the EIS implementation to decide how many (if any) devices with any given capability exist in this seat. Only interfaces that the client announced during ei_handshake.interface_version can be a seat capability. This event is sent multiple times - once per supported interface. The set of capabilities is constant for the lifetime of the seat. It is a protocol violation to send this event after the ei_seat.done event. Notification that the initial burst of events is complete and the client can set up this seat now. It is a protocol violation to send this event more than once. Notification that a new device has been added. This event is only sent if the client announced support for the "ei_device" interface in ei_handshake.interface_version. The interface version is equal or less to the client-supported version in ei_handshake.interface_version for the "ei_device" interface. An ei_device represents a single logical input devices. Like physical input devices an ei_device may have multiple capabilities and may e.g. function as pointer and keyboard. Depending on the ei_handshake.context_type, an ei_device can emulate events via client requests or receive events. It is a protocol violation to emulate certain events on a receiver device, or for the EIS implementation to send certain events to the device. See the individual request/event documentation for details. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notification that the client is no longer interested in this device. Note that releasing a device does not guarantee another device becomes available. The EIS implementation will release any resources related to this device and send the ei_device.destroyed event once complete. Notify the EIS implementation that the given device is about to start sending events. This should be seen more as a transactional boundary than a time-based boundary. The primary use-cases for this are to allow for setup on the EIS implementation side and/or UI updates to indicate that a device is sending events now and for out-of-band information to sync with a given event sequence. There is no actual requirement that events start immediately once emulation starts and there is no requirement that a client calls ei_device.stop_emulating after the most recent events. For example, in a remote desktop use-case the client would call ei_device.start_emulating once the remote desktop session starts (rather than when the device sends events) and ei_device.stop_emulating once the remote desktop session stops. The sequence number identifies this transaction between start/stop emulating. It must go up by at least 1 on each call to ei_device.start_emulating. Wraparound must be handled by the EIS implementation but callers must ensure that detection of wraparound is possible. It is a protocol violation to request ei_device.start_emulating after ei_device.start_emulating without an intermediate stop_emulating. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. Notify the EIS implementation that the given device is no longer sending events. See ei_device.start_emulating for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. Generate a frame event to group the current set of events into a logical hardware event. This function must be called after one or more events on any of ei_pointer, ei_pointer_absolute, ei_scroll, ei_button, ei_keyboard or ei_touchscreen has been requested by the EIS implementation. The EIS implementation should not process changes to the device state until the ei_device.frame event. For example, pressing and releasing a key within the same frame is a logical noop. The given timestamp applies to all events in the current frame. The timestamp must be in microseconds of CLOCK_MONOTONIC. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. This device has been removed and a client should release all associated resources. This ei_device object will be destroyed by the EIS implementation immmediately after after this event is sent and as such the client must not attempt to use it after that point. The name of this device, if any. This event is optional and sent once immediately after object creation. It is a protocol violation to send this event after the ei_device.done event. If the device type is ei_device.device_type.virtual, the device is a virtual device representing input as applied on the EIS implementation's screen. A relative virtual device generates input events in logical pixels, an absolute virtual device generates input events in logical pixels on one of the device's regions. Virtual devices do not have a ei_device.dimension but it may have an ei_device.region. If the device type is ei_device.device_type.physical, the device is a representation of a physical device as if connected to the EIS implementation's host computer. A relative physical device generates input events in mm, an absolute physical device generates input events in mm within the device's specified physical size. Physical devices do not have regions and no ei_device.region events are sent for such devices. The device type, one of virtual or physical. Devices of type ei_device.device_type.physical are supported only clients of type ei_handshake.context_type.receiver. This event is sent once immediately after object creation. It is a protocol violation to send this event after the ei_device.done event. The device dimensions in mm. This event is optional and sent once immediately after object creation. This event is only sent for devices of ei_device.device_type.physical. It is a protocol violation to send this event after the ei_device.done event. Notifies the client of one region. The number of regions is constant for a device and all regions are announced immediately after object creation. A region is rectangular and defined by an x/y offset and a width and a height. A region defines the area on an EIS desktop layout that is accessible by this device - this region may not be the full area of the desktop. Input events may only be sent for points within the regions. The use of regions is private to the EIS compositor and coordinates may not match the size of the actual desktop. For example, a compositor may set a 1920x1080 region to represent a 4K monitor and transparently map input events into the respective true pixels. Absolute devices may have different regions, it is up to the libei client to send events through the correct device to target the right pixel. For example, a dual-head setup my have two absolute devices, the first with a zero offset region spanning the left screen, the second with a nonzero offset spanning the right screen. The physical scale denotes a constant factor that needs to be applied to any relative movement on this region for that movement to match the same *physical* movement on another region. It is an EIS implementation bug to advertise the absolute pointer capability on a device_type.virtual device without advertising an ei_region for this device. This event is optional and sent immediately after object creation. Where a device has multiple regions, this event is sent once for each region. It is a protocol violation to send this event after the ei_device.done event. Notification that a new device has a sub-interface. This event may be sent for the - "ei_pointer" interface if the device has the ei_device.capabilities.pointer capability - "ei_pointer_absolute" interface if the device has the ei_device.capabilities.pointer_absolute capability - "ei_scroll" interface if the device has the ei_device.capabilities.scroll capability - "ei_button" interface if the device has the ei_device.capabilities.button capability - "ei_keyboard" interface if the device has the ei_device.capabilities.keyboard capability - "ei_touchscreen" interface if the device has the ei_device.capabilities.touchscreen capability The interface version is equal or less to the client-supported version in ei_handshake.interface_version for the respective interface. This event is optional and sent immediately after object creation and at most once per interface. It is a protocol violation to send this event after the ei_device.done event. Notification that the initial burst of events is complete and the client can set up this device now. It is a protocol violation to send this event more than once per device. Notification that the device has been resumed by the EIS implementation and (depending on the ei_handshake.context_type) the client may request ei_device.start_emulating or the EIS implementation may ei_device.start_emulating events. It is a client bug to request emulation of events on a device that is not resumed. The EIS implementation may silently discard such events. A newly advertised device is in the ei_device.paused state. Notification that the device has been paused by the EIS implementation and no futher events will be accepted on this device until it is resumed again. For devices of ei_device_setup.context_type sender, the client thus does not need to request ei_device.stop_emulating and may request ei_device.start_emulating after a subsequent ei_device.resumed. For devices of ei_device_setup.context_type receiver and where the EIS implementation did not send a ei_device.stop_emulating prior to this event, the device may send a ei_device.start_emulating event after a subsequent ei_device.resumed event. 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 It is a client bug to request emulation of events on a device that is not resumed. The EIS implementation may silently discard such events. A newly advertised device is in the ei_device.paused state. See the ei_device.start_emulating request for details. It is a protocol violation to send this event for a client of an ei_handshake.context_type other than receiver. See the ei_device.stop_emulating request for details. It is a protocol violation to send this event for a client of an ei_handshake.context_type other than receiver. See the ei_device.frame request for details. It is a protocol violation to send this event for a client of an ei_handshake.context_type other than receiver. Notifies the client that the region specified in the next ei_device.region event is to be assigned the given mapping_id. 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. This event is optional and sent immediately after object creation but before the corresponding ei_device.region event. Where a device has multiple regions, this event may be sent zero or one time for each region. It is a protocol violation to send this event after the ei_device.done event or to send this event without a corresponding following ei_device.region event. Interface for pointer motion requests and events. This interface is available on devices with the ei_device.capability pointer. This interface is only provided once per device and where a client requests ei_pointer.release the interface does not get re-initialized. An EIS implementation may adjust the behavior of the device (including removing the device) if the interface is releasd. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notification that the client is no longer interested in this pointer. The EIS implementation will release any resources related to this pointer and send the ei_pointer.destroyed event once complete. Generate a relative motion event on this pointer. It is a client bug to send this request more than once within the same ei_device.frame. It is a client bug to send this request on a device without the ei_device.capabilities.pointer capability. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. This object has been removed and a client should release all associated resources. This object will be destroyed by the EIS implementation immmediately after after this event is sent and as such the client must not attempt to use it after that point. See the ei_pointer.motion_relative request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. Interface for absolute pointer requests and events. This interface is available on devices with the ei_device.capability pointer_absolute. This interface is only provided once per device and where a client requests ei_pointer_absolute.release the interface does not get re-initialized. An EIS implementation may adjust the behavior of the device (including removing the device) if the interface is releasd. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notification that the client is no longer interested in this object. The EIS implementation will release any resources related to this object and send the ei_pointer_absolute.destroyed event once complete. Generate an absolute motion event on this pointer. The x/y coordinates must be within the device's regions or the event is silently discarded. It is a client bug to send this request more than once within the same ei_device.frame. It is a client bug to send this request on a device without the ei_device.capabilities.pointer_absolute capability. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. This object has been removed and a client should release all associated resources. This object will be destroyed by the EIS implementation immmediately after after this event is sent and as such the client must not attempt to use it after that point. See the ei_pointer_absolute.motion_absolute request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. Interface for scroll requests and events. This interface is available on devices with the ei_device.capability scroll. This interface is only provided once per device and where a client requests ei_scroll.release the interface does not get re-initialized. An EIS implementation may adjust the behavior of the device (including removing the device) if the interface is releasd. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notification that the client is no longer interested in this object. The EIS implementation will release any resources related to this object and send the ei_scroll.destroyed event once complete. Generate a a smooth (pixel-precise) scroll event on this pointer. Clients must not send ei_scroll.scroll_discrete events for the same event, the EIS implementation is responsible for emulation of discrete scroll events. It is a client bug to send this request more than once within the same ei_device.frame. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. Generate a a discrete (e.g. wheel) scroll event on this pointer. Clients must not send ei_scroll.scroll events for the same event, the EIS implementation is responsible for emulation of smooth scroll events. A discrete scroll event is based logical scroll units (equivalent to one mouse wheel click). The value for one scroll unit is 120, a fraction or multiple thereof represents a fraction or multiple of a wheel click. It is a client bug to send this request more than once within the same ei_device.frame. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. Generate a a scroll stop or cancel event on this pointer. A scroll stop event notifies the EIS implementation that the interaction causing a scroll motion previously triggered with ei_scroll.scroll or ei_scroll.scroll_discrete has stopped. For example, if all fingers are lifted off a touchpad, two-finger scrolling has logically stopped. The EIS implementation may use this information to e.g. start kinetic scrolling previously based on the previous finger speed. If is_cancel is nonzero, the event represents a cancellation of the current interaction. This indicates that the interaction has stopped to the point where further (server-emulated) scroll events from this device are wrong. It is a client bug to send this request more than once within the same ei_device.frame. It is a client bug to send this request for an axis that had a a nonzero value in either ei_scroll.scroll or ei_scroll.scroll_discrete in the current frame. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. This object has been removed and a client should release all associated resources. This object will be destroyed by the EIS implementation immmediately after after this event is sent and as such the client must not attempt to use it after that point. See the ei_scroll.scroll request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. See the ei_scroll.scroll_discrete request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. See the ei_scroll.scroll_stop request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. Interface for button requests and events. This interface is available on devices with the ei_device.capability button. This interface is only provided once per device and where a client requests ei_button.release the interface does not get re-initialized. An EIS implementation may adjust the behavior of the device (including removing the device) if the interface is releasd. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notification that the client is no longer interested in this object. The EIS implementation will release any resources related to this object and send the ei_button.destroyed event once complete. The logical state of a button. Generate a button event on this pointer. The button codes must match the defines in linux/input-event-codes.h. It is a client bug to send more than one button request for the same button within the same ei_device.frame. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. This pointer has been removed and a client should release all associated resources. This ei_scroll object will be destroyed by the EIS implementation immmediately after after this event is sent and as such the client must not attempt to use it after that point. See the ei_scroll.button request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. It is an EIS implementation bug to send more than one button request for the same button within the same ei_device.frame. Interface for keyboard requests and events. This interface is available on devices with the ei_device.capability keyboard. This interface is only provided once per device and where a client requests ei_keyboard.release the interface does not get re-initialized. An EIS implementation may adjust the behavior of the device (including removing the device) if the interface is releasd. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notification that the client is no longer interested in this keyboard. The EIS implementation will release any resources related to this keyboard and send the ei_keyboard.destroyed event once complete. The logical state of a key. Generate a key event on this keyboard. If the device has an ei_keyboard.keymap, the key code corresponds to that keymap. The key codes must match the defines in linux/input-event-codes.h. It is a client bug to send more than one key request for the same key within the same ei_device.frame. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than sender. This keyboard has been removed and a client should release all associated resources. This ei_keyboard object will be destroyed by the EIS implementation immmediately after after this event is sent and as such the client must not attempt to use it after that point. The keymap type describes how the keymap in the ei_keyboard.keymap event should be parsed. Notification that this device has a keymap. Future key events must be interpreted by the client according to this keymap. For clients of ei_handshake.context_type sender it is the client's responsibility to send the correct ei_keyboard.key keycodes to generate the expected keysym in the EIS implementation. The keymap is constant for the lifetime of the device. This event provides a file descriptor to the client which can be memory-mapped in read-only mode to provide a keyboard mapping description. The fd must be mapped with MAP_PRIVATE by the recipient, as MAP_SHARED may fail. This event is sent immediately after the ei_keyboard object is created and before the ei_device.done event. It is a protocol violation to send this event after the ei_device.done event. See the ei_keyboard.key request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. It is a protocol violation to send a key down event in the same frame as a key up event for the same key in the same frame. Notification that the EIS implementation has changed modifier states on this device. Future ei_keyboard.key requests must take the new modifier state into account. A client must assume that all modifiers are lifted when it receives an ei_device.paused event. The EIS implementation must send this event after ei_device.resumed to notify the client of any nonzero modifier state. This event does not reqire an ei_device.frame and should be processed immediately by the client. This event is only sent for devices with an ei_keyboard.keymap. Interface for touchscreen requests and events. This interface is available on devices with the ei_device.capability touchscreen. This interface is only provided once per device and where a client requests ei_touchscreen.release the interface does not get re-initialized. An EIS implementation may adjust the behavior of the device (including removing the device) if the interface is releasd. Note that for a client to receive objects of this type, it must announce support for this interface in ei_handshake.interface_version. Notification that the client is no longer interested in this touch. The EIS implementation will release any resources related to this touch and send the ei_touch.destroyed event once complete. Notifies the EIS implementation about a new touch logically down at the given coordinates. The touchid is a unique id for this touch. Touchids may be re-used after ei_touchscreen.up. The x/y coordinates must be within the device's regions or the event and future ei_touchscreen.motion events with the same touchid are silently discarded. It is a protocol violation to send a touch down in the same frame as a touch motion or touch up. Notifies the EIS implementation about an existing touch changing position to the given coordinates. The touchid is the unique id for this touch previously sent with ei_touchscreen.down. The x/y coordinates must be within the device's regions or the event is silently discarded. It is a protocol violation to send a touch motion in the same frame as a touch down or touch up. Notifies the EIS implementation about an existing touch being logically up. The touchid is the unique id for this touch previously sent with ei_touchscreen.down. The touchid may be re-used after this request. It is a protocol violation to send a touch up in the same frame as a touch motion or touch down. This touch has been removed and a client should release all associated resources. This ei_touchscreen object will be destroyed by the EIS implementation immmediately after after this event is sent and as such the client must not attempt to use it after that point. See the ei_touchscreen.down request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. It is a protocol violation to send a touch down in the same frame as a touch motion or touch up. See the ei_touchscreen.motion request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. It is a protocol violation to send a touch motion in the same frame as a touch down or touch up. See the ei_touchscreen.up request for details. It is a protocol violation to send this request for a client of an ei_handshake.context_type other than receiver. It is a protocol violation to send a touch up in the same frame as a touch motion or touch down. libei-1.2.1/src/000077500000000000000000000000001456005336000133705ustar00rootroot00000000000000libei-1.2.1/src/brei-proto.h.tmpl000066400000000000000000000025631456005336000166040ustar00rootroot00000000000000/** * GENERATED FILE, DO NOT EDIT * * SPDX-License-Identifier: MIT */ {# this is a jinja template, warning above is for the generated file Non-obvious variables set by the scanner that are used in this template: - request.fqdn/event.fqdn - the full name of a request/event with the interface name prefixed, "ei_foo_request_bar" or "ei_foo_event_bar" - incoming/outgoing: points to the list of requests or events, depending which one is the outgoing one from the perspective of the file we're generating (ei or eis) #} {# target: because eis is actually eis_client in the code, the target points to either "ei" or "eis_client" and we need the matching get_context or get_client for those. This is specific to the libei/libeis implementation so it's done here in the template only. #} {% if component == "eis" %} {% set target = { "name": "eis_client", "context": "client" } %} {% else %} {% set target = { "name": "ei", "context": "context" } %} {% endif %} #pragma once #ifdef _cplusplus extern "C" { #endif #include typedef uint64_t object_id_t; typedef object_id_t new_id_t; {% for interface in interfaces %} {% for enum in interface.enums %} enum {{enum.fqdn}} { {% for entry in enum.entries %} {{enum.fqdn.upper()}}_{{entry.name.upper()}} = {{entry.value}}, {% endfor %} }; {% endfor %} {% endfor %} #ifdef _cplusplus } #endif libei-1.2.1/src/brei-shared.c000066400000000000000000000544441456005336000157340ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "brei-shared.h" #include "brei-proto.h" struct brei_context { struct object object; void *parent_context; brei_logfunc_t log_func; void *log_context; }; struct brei_header { object_id_t sender_id; uint32_t msglen; /* length of message in bytes including this header */ uint32_t opcode; } _packed_; static_assert(sizeof(struct brei_header) == 16, "Unexpected size for brei_header struct"); /** * For a given string length (including null byte) return * the number of bytes needed on the protocol, including the * 4-byte length field. */ static inline uint32_t brei_string_proto_length(uint32_t 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) { } static OBJECT_IMPLEMENT_CREATE(brei_context); OBJECT_IMPLEMENT_REF(brei_context); OBJECT_IMPLEMENT_UNREF_CLEANUP(brei_context); OBJECT_IMPLEMENT_SETTER(brei_context, log_func, brei_logfunc_t); OBJECT_IMPLEMENT_SETTER(brei_context, log_context, void *); static void brei_result_destroy(struct brei_result *res) { free(res->explanation); } static OBJECT_IMPLEMENT_CREATE(brei_result); OBJECT_IMPLEMENT_GETTER(brei_result, data, void *); OBJECT_IMPLEMENT_SETTER(brei_result, data, void *); OBJECT_IMPLEMENT_GETTER(brei_result, reason, int); OBJECT_IMPLEMENT_GETTER(brei_result, explanation, const char *); OBJECT_IMPLEMENT_REF(brei_result); OBJECT_IMPLEMENT_UNREF_CLEANUP(brei_result); struct brei_result * brei_result_new(int reason, const char *format, ...) { struct brei_result *result = brei_result_create(NULL); result->reason = reason; if (format) { va_list args; va_start(args, format); result->explanation = xvaprintf(format, args); va_end(args); } else { assert(reason == 0); /* Any error needs an explanation */ } return result; } struct brei_result * brei_result_new_success(void *data) { struct brei_result *result = brei_result_new(0, NULL); result->data = data; return result; } struct brei_result * brei_result_new_from_neg_errno(int err) { if (err >= 0) return NULL; return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_ERROR, "%s", strerror(-err)); } struct brei_context * brei_context_new(void *parent_context) { struct brei_context *brei = brei_context_create(NULL); brei->parent_context = parent_context; return brei; } #define log_debug(T_, ...) \ brei_log_msg((T_), BREI_LOG_PRIORITY_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_info(T_, ...) \ brei_log_msg((T_), BREI_LOG_PRIORITY_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_warn(T_, ...) \ brei_log_msg((T_), BREI_LOG_PRIORITY_WARNING, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_error(T_, ...) \ brei_log_msg((T_), BREI_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_bug(T_, ...) \ brei_log_msg((T_), BREI_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪳 brei bug: " __VA_ARGS__) #define log_bug_client(T_, ...) \ brei_log_msg((T_), BREI_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪲 Bug: " __VA_ARGS__) _printf_(6, 0) static void brei_log_msg_va(struct brei_context *brei, enum brei_log_priority priority, const char *file, int lineno, const char *func, const char *format, va_list ap) { if (brei->log_func && brei->log_context) brei->log_func(brei->log_context, priority, file, lineno, func, format, ap); } _printf_(6, 7) static void brei_log_msg(struct brei_context *brei, enum brei_log_priority priority, const char *file, int lineno, const char *func, const char *format, ...) { va_list args; va_start(args, format); brei_log_msg_va(brei, priority, file, lineno, func, format, args); va_end(args); } 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, char ***strings_out) { size_t nargs = strlen(signature); if (nargs > 256) { return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Too many arguments in signature (%zu)", nargs); } /* 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) { switch (*s) { case 'i': case 'u': case 'f': arg->u = *p++; break; case 'x': arg->x = *(int64_t *) p; p++; p++; break; case 'o': case 'n': case 't': memcpy(&arg->x, p, sizeof(arg->x)); p++; p++; break; case 'h': arg->h = iobuf_take_fd(buf); break; case 's': { uint32_t slen = *p; uint32_t remaining = end - p; 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", slen, remaining * 4); } if (slen == 0) { arg->s = NULL; } else { _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; } default: return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Invalid signature '%c'", *s); } arg++; s++; nargs++; } *args_out = steal(&args); *strings_out = steal(&strings); *nargs_out = nargs; return NULL; } static struct brei_result * brei_marshal(struct brei_context *brei, struct iobuf *buf, const char *signature, size_t nargs, va_list args) { const char *s = signature; int32_t i; uint32_t u; int64_t x; uint64_t t; float f; int fd; while (*s) { switch (*s) { case 'i': i = va_arg(args, int32_t); iobuf_append_u32(buf, i); break; case 'u': u = va_arg(args, uint32_t); iobuf_append_u32(buf, u); break; case 'x': x = va_arg(args, int64_t); iobuf_append_u64(buf, x); break; case 'o': case 'n': case 't': t = va_arg(args, uint64_t); iobuf_append_u64(buf, t); break; case 'f': f = va_arg(args, double); iobuf_append_f32(buf, f); break; case 'h': fd = va_arg(args, int); iobuf_append_fd(buf, fd); break; case 's': { static const char zeroes[4] = {0}; const char *str = va_arg(args, const char*); /* FIXME: nullable strings */ uint32_t slen = str ? strlen(str) + 1 : 0; iobuf_append_u32(buf, slen); if (slen > 0) { iobuf_append(buf, str, slen); if (slen % 4) iobuf_append(buf, zeroes, 4 - slen % 4); } break; } default: return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Invalid signature '%c'", *s); } s++; } return NULL; } struct brei_result * brei_marshal_message(struct brei_context *brei, object_id_t id, uint32_t opcode, const char *signature, size_t nargs, va_list args) { _cleanup_iobuf_ struct iobuf *buf = iobuf_new(128); _unref_(brei_result) *result = brei_marshal(brei, buf, signature, nargs, args); if (result) return steal(&result); 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, header, sizeof(header)); return brei_result_new_success(steal(&buf)); } struct brei_result * brei_dispatch(struct brei_context *brei, int fd, int (*lookup_object)(object_id_t object_id, struct brei_object **object, void *user_data), void *user_data) { _unref_(brei_result) *result = NULL; _cleanup_iobuf_ struct iobuf *buf = iobuf_new(64); int rc = iobuf_recv_from_fd(buf, fd); if (rc == -EAGAIN) { return NULL; } else if (rc == 0) { return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_TRANSPORT, "socket disconnected"); } else if (rc < 0) { return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_TRANSPORT, "error: %s", strerror(-rc)); } while (true) { uint32_t *data = (uint32_t*)iobuf_data(buf); size_t len = iobuf_len(buf); const size_t headersize = sizeof(struct brei_header); if (len < headersize) break; const struct brei_header *header = (const struct brei_header*)data; uint32_t msglen = header->msglen; if (len < msglen) break; object_id_t object_id = header->sender_id; uint32_t opcode = header->opcode;; /* Find the object, it is stored in the ei/eis context */ struct brei_object *object = NULL; rc = lookup_object(header->sender_id, &object, user_data); if (rc == -ENOENT) { iobuf_pop(buf, msglen); continue; } if (rc < 0) { result = brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_ERROR, "lookup_object failed with %d (%s)", -rc, strerror(-rc)); goto error; } assert(object); /* We know the object's interface, now find the event we * need to parse */ const struct brei_interface *interface = object->interface; assert(interface); if (opcode >= interface->nincoming) { result = brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "opcode %u exceeds interface %s method count %u", header->opcode, interface->name, interface->nincoming); goto error; } iobuf_pop(buf, headersize); /* Demarshal the protocol into a set of arguments */ _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, &strings); if (result) goto error; log_debug(brei, "dispatching %s.%s() on object %#" PRIx64 "", interface->name, interface->incoming[opcode].name, object_id); /* Success! Let's pass this on to the * context to process */ result = interface->dispatcher(object->implementation, opcode, nargs, args); if (result) goto error; iobuf_pop(buf, msglen - headersize); } error: if (result) { log_error(brei, "%s", brei_result_get_explanation(result)); return steal(&result); } return NULL; } void brei_drain_fd(int fd) { _cleanup_iobuf_ struct iobuf *buf = iobuf_new(1024); int rc; while ((rc = iobuf_recv_from_fd(buf, fd)) > 0) ; } #ifdef _enable_tests_ #include "util-munit.h" MUNIT_TEST(test_brei_string_proto_length) { munit_assert_int(brei_string_proto_length(0), ==, 4); munit_assert_int(brei_string_proto_length(1), ==, 8); munit_assert_int(brei_string_proto_length(4), ==, 8); munit_assert_int(brei_string_proto_length(5), ==, 12); munit_assert_int(brei_string_proto_length(8), ==, 12); munit_assert_int(brei_string_proto_length(12), ==, 16); munit_assert_int(brei_string_proto_length(13), ==, 20); return MUNIT_OK; } static struct brei_result * brei_marshal_va(struct brei_context *brei, struct iobuf *buf, const char *signature, size_t nargs, ...) { va_list args; va_start(args, nargs); struct brei_result *result = brei_marshal(brei, buf, signature, nargs, args); va_end(args); return result; } MUNIT_TEST(test_brei_marshal) { _unref_(brei_context) *brei = brei_context_new(NULL); _cleanup_iobuf_ struct iobuf *buf = iobuf_new(64); const char *str = "eierspeise"; { _unref_(brei_result) *result = brei_marshal_va(brei, buf, "noiusf", 5, (object_id_t)0xab, (object_id_t)0xcd, (int32_t)-13, (uint32_t)0xfffd, str, 1.45); munit_assert_ptr_null(result); } _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, &strings); munit_assert_ptr_null(result); munit_assert_int(nargs, ==, 6); munit_assert_int(args[0].o, ==, 0xab); munit_assert_int(args[1].o, ==, 0xcd); munit_assert_int(args[2].i, ==, -13); munit_assert_int(args[3].u, ==, 0xfffd); 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; } MUNIT_TEST(test_brei_marshal_bad_sig) { _unref_(brei_context) *brei = brei_context_new(NULL); _cleanup_iobuf_ struct iobuf *buf = iobuf_new(64); const char *str = "eierspeise"; { _unref_(brei_result) *result = brei_marshal_va(brei, buf, "noiusf", 5, (object_id_t)0xab, (object_id_t)0xcd, (int32_t)-13, (uint32_t)0xfffd, str, 1.45); munit_assert_ptr_null(result); } _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, &strings); munit_assert_ptr_not_null(result); munit_assert_int(brei_result_get_reason(result), ==, BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL); munit_assert_int(nargs, ==, 789); /* nargs must not be touched on error */ return MUNIT_OK; } static int brei_send_message(struct brei_context *brei, int fd, object_id_t id, uint32_t opcode, const char *signature, size_t nargs, va_list args) { _unref_(brei_result) *result = brei_marshal_message(brei, id, opcode, signature, nargs, args); if (brei_result_get_reason(result) != 0) return -EBADMSG; _cleanup_iobuf_ struct iobuf *buf = NULL; buf = brei_result_get_data(result); return iobuf_send(buf, fd); } static int brei_send_message_va(int fd, object_id_t id, uint32_t opcode, const char *signature, size_t nargs, ...) { _unref_(brei_context) *brei = brei_context_new(NULL); va_list args; va_start(args, nargs); int rc = brei_send_message(brei, fd, id, opcode, signature, nargs, args); va_end(args); return rc; } /** * Return the number of int32s required to store count bytes. */ static inline uint32_t 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); _cleanup_close_ int sock_read = sv[0]; _cleanup_close_ int sock_write = sv[1]; const int header_size = 16; { const int msglen = header_size + 8; /* 2 * 4 bytes */ object_id_t id = 1; uint32_t opcode = 2; const char *signature = "uu"; int rc = brei_send_message_va(sock_write, id, opcode, signature, 2, 0xff, 0xdddd); munit_assert_int(rc, ==, msglen); uint32_t buf[64]; 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); munit_assert_int(header->opcode, ==, opcode); munit_assert_int(buf[4], ==, 0xff); munit_assert_int(buf[5], ==, 0xdddd); } { const int msglen = header_size + 8; /* 2 * 4 bytes */ object_id_t id = 1; uint32_t opcode = 2; const char *signature = "fi"; int rc = brei_send_message_va(sock_write, id, opcode, signature, 2, 1.234, -12); munit_assert_int(rc, ==, msglen); uint32_t buf[64]; int len = read(sock_read, buf, sizeof(buf)); union { uint32_t bytes; float f; } 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); munit_assert_int(header->opcode, ==, opcode); ufloat.bytes = buf[4]; munit_assert_double_equal(ufloat.f, 1.234, 4/* precision */); munit_assert_int(buf[5], ==, -12); } { const char string[12] = "hello wor"; /* tests padding too */ uint32_t string_len = bytes_to_int32(strlen0(string)) * 4; munit_assert_int(string_len, ==, sizeof(string)); 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"; int rc = brei_send_message_va(sock_write, id, opcode, signature, 4, (int32_t)-42, string, (object_id_t)0xab, (object_id_t)0xcdef); munit_assert_int(rc, ==, msglen); uint32_t buf[64]; 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_uint64(header->sender_id, ==, id); munit_assert_int(header->msglen, ==, msglen); munit_assert_int(header->opcode, ==, opcode); munit_assert_int(buf[4], ==, -42); 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); 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); } { const char string1[12] = "hello wor"; /* tests padding too */ const char string2[8] = "barba"; /* tests padding too */ const int msglen = header_size + 8 + sizeof(string1) + sizeof(string2); /* 8 header + 2 * 4 bytes slen + string lengths */ object_id_t id = 2; uint32_t opcode = 3; const char *signature = "ss"; int rc = brei_send_message_va(sock_write, id, opcode, signature, 2, string1, string2); munit_assert_int(rc, ==, msglen); uint32_t buf[64]; 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_uint64(header->sender_id, ==, id); munit_assert_int(header->msglen, ==, msglen); munit_assert_int(header->opcode, ==, opcode); 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); 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); } { int fds[2]; int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, fds); munit_assert_int(rc, ==, 0); _cleanup_close_ int left = fds[0]; _cleanup_close_ int right = fds[1]; /* actual message data to be sent */ char data[] = "some data\n"; const int msglen = header_size + 8; /* 2 unsigned, fd is not in data */ object_id_t id = 2; uint32_t opcode = 3; const char *signature = "uhu"; rc = brei_send_message_va(sock_write, id, opcode, signature, 3, 0xab, right, 0xcd); munit_assert_int(rc, ==, msglen); /* We passed it down, can close it now */ close(right); right = -1; /* receive the brei message */ _cleanup_iobuf_ struct iobuf *recv = iobuf_new(64); int len = iobuf_recv_from_fd(recv, sock_read); 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_uint64(header->sender_id, ==, id); munit_assert_int(header->msglen, ==, msglen); munit_assert_int(header->opcode, ==, opcode); munit_assert_int(buf[4], ==, 0xab); /* fd is not in data */ munit_assert_int(buf[5], ==, 0xcd); _cleanup_close_ int fd = iobuf_take_fd(recv); munit_assert_int(fd, !=, -1); munit_assert_int(iobuf_take_fd(recv), ==, -1); /* only one fd */ /* send some data down the dup'd fd */ rc = xsend(fd, data, sizeof(data)); munit_assert_int(rc, ==, sizeof(data)); char recvbuf[64] = {0}; rc = xread(left, recvbuf, sizeof(recvbuf)); munit_assert_int(rc, ==, sizeof(data)); munit_assert_string_equal(recvbuf, data); } return MUNIT_OK; } #endif libei-1.2.1/src/brei-shared.h000066400000000000000000000114331456005336000157300ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include "brei-proto.h" #include "util-macros.h" #include "util-list.h" #include "util-object.h" struct brei_interface; struct brei_message; struct brei_object; struct brei_context; struct brei_result { struct object object; int reason; /* enum ei(s)_connection_disconnect_reason */ char *explanation; void *data; }; struct brei_result * brei_result_new_success(void *data); struct brei_result * brei_result_new_from_neg_errno(int err); _printf_(2, 3) struct brei_result * brei_result_new(int reson, const char *format, ...); OBJECT_DECLARE_GETTER(brei_result, data, void *); OBJECT_DECLARE_SETTER(brei_result, data, void *); OBJECT_DECLARE_GETTER(brei_result, reason, int); OBJECT_DECLARE_GETTER(brei_result, explanation, const char *); OBJECT_DECLARE_REF(brei_result); OBJECT_DECLARE_UNREF(brei_result); enum brei_log_priority { BREI_LOG_PRIORITY_DEBUG = 10, BREI_LOG_PRIORITY_INFO = 20, BREI_LOG_PRIORITY_WARNING = 30, BREI_LOG_PRIORITY_ERROR = 40, }; typedef void (*brei_logfunc_t)(void *context, enum brei_log_priority priority, const char *file, int lineno, const char *func, const char *format, va_list args); struct brei_context *brei_context_new(void *user_data); OBJECT_DECLARE_SETTER(brei_context, log_func, brei_logfunc_t); OBJECT_DECLARE_SETTER(brei_context, log_context, void *); OBJECT_DECLARE_REF(brei_context); OBJECT_DECLARE_UNREF(brei_context); union brei_arg { uint32_t u; int32_t i; float f; int h; const char *s; object_id_t o; object_id_t n; uint64_t t; int64_t x; }; struct brei_message { const char *name; /* request/event name */ const char *signature; const struct brei_interface **types; }; typedef struct brei_result * (*brei_event_dispatcher)(void *object, uint32_t opcode, size_t nargs, union brei_arg *args); struct brei_interface { const char *name; uint32_t version; uint32_t nrequests; const struct brei_message *requests; uint32_t nevents; const struct brei_message *events; uint32_t nincoming; const struct brei_message *incoming; brei_event_dispatcher dispatcher; }; struct brei_object { const struct brei_interface *interface; void *implementation; /* pointer to the actual object */ object_id_t id; /* protocol object id */ uint32_t version; /* protocol object interface version */ struct list link; /* in the ei/eis object list */ }; /** * Marshal the message for the given object id, opcode and signature into * a struct iobuf. On succes, the returned result has that iobuf * as brei_result_get_data(), otherwise the result is the error. */ struct brei_result * brei_marshal_message(struct brei_context *brei, object_id_t id, uint32_t opcode, const char *signature, size_t nargs, va_list args); /** * Dispatch messages for the data on fd. * The callback is called for each protocol message, parsing must be done by * the caller. * * On success, the callback must return the number of bytes consumed in msgdata * On failure, the callback must return a negative errno * * @return NULL on success or a struct with the error reason */ struct brei_result * brei_dispatch(struct brei_context *brei, int fd, int (*lookup_object)(object_id_t object_id, struct brei_object **object, void *user_data), void *user_data); /** * Drain all pending content from the file descriptor and discard it. */ void brei_drain_fd(int fd); static inline bool brei_is_server_id(object_id_t id) { return id >= 0xff00000000000000; } static inline bool brei_is_client_id(object_id_t id) { return id != 0 && id < 0xff00000000000000; } libei-1.2.1/src/ei-proto.c.tmpl000066400000000000000000000130571456005336000162530ustar00rootroot00000000000000/** * GENERATED FILE, DO NOT EDIT * * SPDX-License-Identifier: MIT */ {#- this is a jinja template, warning above is for the generated file Non-obvious variables set by the scanner that are used in this template: - request.fqdn/event.fqdn - the full name of a request/event with the interface name prefixed, "ei_foo_request_bar" or "ei_foo_event_bar" - incoming/outgoing: points to the list of requests or events, depending which one is the outgoing one from the perspective of the file we're generating (ei or eis) #} {# target: because eis is actually eis_client in the code, the target points to either "ei" or "eis_client" and we need the matching get_context or get_client for those. This is specific to the libei/libeis implementation so it's done here in the template only. #} {% set target = {} %} {% if component == "eis" %} {% set target = { "name": "eis_client", "context": "client" } %} {% else %} {% set target = { "name": "ei", "context": "context" } %} {% endif %} #include #include #include #include "brei-shared.h" #include "brei-proto.h" {% if extra.headerfile %} #include "{{extra.headerfile}}" {% endif %} /** * Forward declarations. This file is intended to be compile-able without including * any of the actual implementation files. */ struct {{target.name}}; /** * The function that converts the given arguments into a wire format and sends it. * This function must be provided by the implementation, it is called by all * message sending functions (requests for libei, events for libeis). */ extern int {{target.name}}_send_message( {{target.name|as_c_arg}}, const struct brei_object *obj, uint32_t opcode, const char *signature, size_t nargs, ... ); {% for interface in interfaces %} /*************************************************************************** * Interface: {{interface.name.ljust(60)}} * ***************************************************************************/ /* returns the protocol interface from the given struct, see the dispatcher */ const struct {{interface.name}}_interface *{{interface.name}}_get_interface({{interface.as_c_arg}}); /* returns the message sending context from the given struct */ {{target.name|c_type}} {{interface.name}}_get_{{target.context}}({{interface.as_c_arg}}); /* Returns the protocol object id from the given struct */ const struct brei_object * {{interface.name}}_get_proto_object({{interface.as_c_arg}}); /* request opcodes */ {% for request in interface.requests %} #define {{request.fqdn.upper().ljust(45)}} {{request.opcode}} {% endfor %} /* event opcodes */ {% for event in interface.events %} #define {{event.fqdn.upper().ljust(45)}} {{event.opcode}} {% endfor %} /* Message sender functions */ {% for outgoing in interface.outgoing %} int {{outgoing.fqdn}}({{interface.as_c_arg}}{%- for arg in outgoing.arguments %}, {{arg.as_c_arg}}{% endfor %}) { if (!{{interface.name}}) return -ENOENT; const struct brei_object *obj = {{interface.name}}_get_proto_object({{interface.name}}); {{target.name|c_type}} ctx = {{interface.name}}_get_{{target.context}}({{interface.name}}); if (obj->version < {{outgoing.fqdn.upper()}}_SINCE_VERSION) return -ENOTSUP; return {{target.name}}_send_message( ctx, obj, {{outgoing.fqdn.upper()}}, "{{outgoing.signature}}", {{outgoing.arguments|length}}{%- for arg in outgoing.arguments %}, {{arg.name}}{% endfor -%} ); } {% endfor %} /** * The dispatcher for the {{interface.name}} interface is the function called by * brei with the messages parsed from the wire. */ static struct brei_result * {{interface.name}}_dispatcher( {{interface.as_c_arg}}, uint32_t opcode, size_t nargs, union brei_arg *args ) { {% if interface.incoming|length %} const struct {{interface.name}}_interface *interface = {{interface.name}}_get_interface({{interface.name}}); const struct brei_object *obj = {{interface.name}}_get_proto_object({{interface.name}}); if (!interface) return NULL; switch (opcode) { {% for incoming in interface.incoming %} 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", 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", opcode); } static const struct brei_message {{interface.name}}_requests[] = { {% for request in interface.requests %} { "{{request.name}}", "{{request.signature}}" }, {% endfor %} }; static const struct brei_message {{interface.name}}_events[] = { {% for event in interface.events %} { "{{event.name}}", "{{event.signature}}" }, {% endfor %} }; static const struct brei_message {{interface.name}}_incoming[] = { {% for msg in interface.incoming %} { "{{msg.name}}", "{{msg.signature}}" }, {% endfor %} }; const struct brei_interface {{interface.name}}_proto_interface = { .name = "{{interface.name}}", .version = {{interface.version}}, .nrequests = {{interface.requests|length}}, .requests = {{interface.name}}_requests, .nevents = {{interface.events|length}}, .events = {{interface.name}}_events, .nincoming = {{interface.incoming|length}}, .incoming = {{interface.name}}_incoming, .dispatcher = (brei_event_dispatcher){{interface.name}}_dispatcher, }; {% endfor %} libei-1.2.1/src/ei-proto.h.tmpl000066400000000000000000000064511456005336000162600ustar00rootroot00000000000000/** * GENERATED FILE, DO NOT EDIT * * SPDX-License-Identifier: MIT */ {# this is a jinja template, warning above is for the generated file Non-obvious variables set by the scanner that are used in this template: - request.fqdn/event.fqdn - the full name of a request/event with the interface name prefixed, "ei_foo_request_bar" or "ei_foo_event_bar" - incoming/outgoing: points to the list of requests or events, depending which one is the outgoing one from the perspective of the file we're generating (ei or eis) #} {# target: because eis is actually eis_client in the code, the target points to either "ei" or "eis_client" and we need the matching get_context or get_client for those. This is specific to the libei/libeis implementation so it's done here in the template only. #} {% if component == "eis" %} {% set target = { "name": "eis_client", "context": "client" } %} {% else %} {% set target = { "name": "ei", "context": "context" } %} {% endif %} #pragma once #ifdef _cplusplus extern "C" { #endif #include #include /** * Forward declarations. This file is intended to be compile-able without including * any of the actual sources files. */ struct {{target.name}}; /* Interface declarations */ {% for interface in interfaces %} struct {{interface.name}}; {% endfor %} {% for interface in interfaces %} extern const struct brei_interface {{interface.name}}_proto_interface; {% endfor %} {% for interface in interfaces %} #define {{interface.name.upper()}}_INTERFACE_NAME "{{interface.protocol_name}}" {% endfor %} __attribute__((unused)) static const char *{{component.upper()}}_INTERFACE_NAMES[] = { {% for interface in interfaces %} {{interface.name.upper()}}_INTERFACE_NAME, {% endfor %} }; /* Interface indices as used in the protocol.xml file */ {% for interface in interfaces %} #define {{interface.name.upper()}}_INTERFACE_INDEX {{loop.index0}} {% endfor %} #define {{component.upper()}}_INTERFACE_COUNT {{interfaces|length}} {% for interface in interfaces %} /** {{interface.name}} **/ {% for request in interface.requests %} #define {{request.fqdn.upper()}}_SINCE_VERSION {{request.since}} {% endfor %} {% for event in interface.events %} #define {{event.fqdn.upper()}}_SINCE_VERSION {{event.since}} {% endfor %} {% for enum in interface.enums %} enum {{enum.fqdn}} { {% for entry in enum.entries %} {{enum.fqdn.upper()}}_{{entry.name.upper()}} = {{entry.value}}, {% endfor %} }; {% for entry in enum.entries %} #define {{enum.fqdn.upper()}}_{{entry.name.upper()}}_SINCE_VERSION {{entry.since}} {% endfor %} {% endfor %} /* Message sender functions */ {% for outgoing in interface.outgoing %} extern int {{outgoing.fqdn}}({{interface.as_c_arg}}{%- for arg in outgoing.arguments %}, {{arg.as_c_arg}}{% endfor %}); {% endfor %} /** * Interface to handle incoming messages for objects of type {{interface.name}}. * * After parsing the wire message, the data is dispatched into the functions below. */ struct {{interface.name}}_interface { {% for incoming in interface.incoming %} struct brei_result * (*{{incoming.name}})({{interface.as_c_arg}}{%- for arg in incoming.arguments %}, {{arg.as_c_arg}}{% endfor %}); {% endfor %} }; {% endfor %} #ifdef _cplusplus } #endif libei-1.2.1/src/libei-button.c000066400000000000000000000053151456005336000161350ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 button WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void ei_button_destroy(struct ei_button *button) { struct ei *ei = ei_button_get_context(button); ei_unregister_object(ei, &button->proto_object); } OBJECT_IMPLEMENT_REF(ei_button); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_button); static OBJECT_IMPLEMENT_CREATE(ei_button); static OBJECT_IMPLEMENT_PARENT(ei_button, ei_device); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_button, proto_object, const struct brei_object*); struct ei_device * ei_button_get_device(struct ei_button *button) { return ei_button_parent(button); } struct ei* ei_button_get_context(struct ei_button *button) { return ei_device_get_context(ei_button_get_device(button)); } const struct ei_button_interface * ei_button_get_interface(struct ei_button *button) { struct ei_device *device = ei_button_get_device(button); return ei_device_get_button_interface(device); } struct ei_button * ei_button_new(struct ei_device *device, object_id_t id, uint32_t version) { struct ei_button *button = ei_button_create(&device->object); struct ei *ei = ei_device_get_context(device); button->proto_object.id = id; button->proto_object.implementation = button; button->proto_object.interface = &ei_button_proto_interface; button->proto_object.version = version; ei_register_object(ei, &button->proto_object); return button; /* ref owned by caller */ } libei-1.2.1/src/libei-button.h000066400000000000000000000035441456005336000161440ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 button WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_device; struct ei_button; /* This is a protocol-only object, not exposed in the API */ struct ei_button { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(ei_button, context, struct ei*); OBJECT_DECLARE_GETTER(ei_button, device, struct ei_device*); OBJECT_DECLARE_GETTER(ei_button, proto_object, const struct brei_object*); OBJECT_DECLARE_GETTER(ei_button, interface, const struct ei_button_interface *); OBJECT_DECLARE_REF(ei_button); OBJECT_DECLARE_UNREF(ei_button); struct ei_button * ei_button_new(struct ei_device *device, object_id_t id, uint32_t version); libei-1.2.1/src/libei-callback.c000066400000000000000000000063211456005336000163540ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 callback WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void ei_callback_destroy(struct ei_callback *callback) { struct ei *ei = ei_callback_get_context(callback); ei_unregister_object(ei, &callback->proto_object); } OBJECT_IMPLEMENT_REF(ei_callback); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_callback); OBJECT_IMPLEMENT_GETTER(ei_callback, user_data, void*); OBJECT_IMPLEMENT_SETTER(ei_callback, user_data, void*); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_callback, proto_object, const struct brei_object*); static OBJECT_IMPLEMENT_CREATE(ei_callback); static OBJECT_IMPLEMENT_PARENT(ei_callback, ei); struct ei* ei_callback_get_context(struct ei_callback *callback) { assert(callback); return ei_callback_parent(callback); } object_id_t 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) { callback->func(callback, callback->callback_data, callback_data); return NULL; } static const struct ei_callback_interface interface = { .done = handle_msg_done, }; const struct ei_callback_interface * ei_callback_get_interface(struct ei_callback *callback) { return &interface; } struct ei_callback * ei_callback_new(struct ei *ei, ei_callback_func func, void *callback_data) { struct ei_callback *callback = ei_callback_create(&ei->object); callback->proto_object.id = ei_get_new_id(ei); callback->proto_object.implementation = callback; callback->proto_object.interface = &ei_callback_proto_interface; callback->proto_object.version = VERSION_V(1); callback->callback_data = callback_data; ei_register_object(ei, &callback->proto_object); callback->func = func; list_init(&callback->link); return callback; /* ref owned by caller */ } libei-1.2.1/src/libei-callback.h000066400000000000000000000045361456005336000163670ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 callback WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_callback; typedef void (*ei_callback_func)(struct ei_callback *callback, void *callback_data, uint64_t proto_data); /* This is a protocol-only object, not exposed in the API */ struct ei_callback { struct object object; struct brei_object proto_object; void *user_data; /* Note: user-data is attached to the object */ struct list link; /* for use by the callers, if needed */ ei_callback_func func; void *callback_data; /* Note: callback-data is attached to the 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 *); OBJECT_DECLARE_REF(ei_callback); OBJECT_DECLARE_UNREF(ei_callback); struct ei_callback * ei_callback_new(struct ei *ei, ei_callback_func func, void *callback_data); libei-1.2.1/src/libei-connection.c000066400000000000000000000105441456005336000167610ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void 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); } } OBJECT_IMPLEMENT_REF(ei_connection); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_connection); static OBJECT_IMPLEMENT_CREATE(ei_connection); static OBJECT_IMPLEMENT_PARENT(ei_connection, ei); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_connection, proto_object, const struct brei_object*); uint32_t ei_connection_get_version(struct ei_connection *connection) { return connection->proto_object.version; } object_id_t ei_connection_get_id(struct ei_connection *connection) { return connection->proto_object.id; } struct ei* ei_connection_get_context(struct ei_connection *connection) { assert(connection); return ei_connection_parent(connection); } const struct ei_connection_interface * ei_connection_get_interface(struct ei_connection *connection) { struct ei *ei = ei_connection_parent(connection); return ei_get_interface(ei); } struct ei_connection * ei_connection_new(struct ei *ei, object_id_t id, uint32_t version) { struct ei_connection *connection = ei_connection_create(&ei->object); connection->proto_object.id = id; connection->proto_object.implementation = connection; connection->proto_object.interface = &ei_connection_proto_interface; connection->proto_object.version = version; ei_register_object(ei, &connection->proto_object); list_init(&connection->pending_callbacks); return connection; /* ref owned by caller */ } struct callback_user_data { ei_connection_sync_callback_t cb; void *user_data; }; 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); /* remove from pending callbacks */ list_remove(&callback->link); ei_callback_unref(callback); } void ei_connection_sync(struct ei_connection *connection, ei_connection_sync_callback_t cb, void *user_data) { struct ei *ei = ei_connection_get_context(connection); /* This is double-wrapped because we only use this for debugging purposes for * now. The actual callback calls sync_callback with our connection, * 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); ei_connection_request_sync(connection, ei_callback_get_id(callback), ei_callback_get_version(callback)); } libei-1.2.1/src/libei-connection.h000066400000000000000000000044331456005336000167660ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_connection; /* This is a protocol-only object, not exposed in the API */ struct ei_connection { struct object object; struct brei_object proto_object; struct list pending_callbacks; }; OBJECT_DECLARE_GETTER(ei_connection, context, struct ei*); OBJECT_DECLARE_GETTER(ei_connection, version, uint32_t); OBJECT_DECLARE_GETTER(ei_connection, id, object_id_t); OBJECT_DECLARE_GETTER(ei_connection, proto_object, const struct brei_object*); OBJECT_DECLARE_GETTER(ei_connection, interface, const struct ei_connection_interface *); OBJECT_DECLARE_REF(ei_connection); OBJECT_DECLARE_UNREF(ei_connection); struct ei_connection * ei_connection_new(struct ei *ei, object_id_t id, uint32_t version); /** * 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); void ei_connection_sync(struct ei_connection *connection, ei_connection_sync_callback_t callback, void *user_data); libei-1.2.1/src/libei-device.c000066400000000000000000001366361456005336000160740ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-time.h" #include "util-strings.h" #include "libei-private.h" #include "ei-proto.h" DEFINE_UNREF_CLEANUP_FUNC(ei_region); static const char * ei_device_state_to_string(enum ei_device_state state) { switch (state) { CASE_RETURN_STRING(EI_DEVICE_STATE_NEW); CASE_RETURN_STRING(EI_DEVICE_STATE_PAUSED); CASE_RETURN_STRING(EI_DEVICE_STATE_RESUMED); CASE_RETURN_STRING(EI_DEVICE_STATE_EMULATING); CASE_RETURN_STRING(EI_DEVICE_STATE_REMOVED_FROM_CLIENT); CASE_RETURN_STRING(EI_DEVICE_STATE_REMOVED_FROM_SERVER); CASE_RETURN_STRING(EI_DEVICE_STATE_DEAD); } assert(!"Unhandled device state"); } static void ei_device_set_state(struct ei_device *device, enum ei_device_state state) { enum ei_device_state old_state = device->state; device->state = state; log_debug(ei_device_get_context(device), "device %#" PRIx64 ": %s → %s", ei_device_get_id(device), ei_device_state_to_string(old_state), ei_device_state_to_string(state)); } static void ei_device_destroy(struct ei_device *device) { struct ei_seat *seat = ei_device_get_seat(device); struct ei_region *region; struct ei_event *event; assert(device->state == EI_DEVICE_STATE_DEAD); list_for_each_safe(region, &device->regions, link) ei_region_unref(region); list_for_each_safe(event, &device->pending_event_queue, link) { list_remove(&event->link); ei_event_unref(event); } list_remove(&device->link); ei_keymap_unref(device->keymap); ei_pointer_unref(device->pointer); ei_pointer_absolute_unref(device->pointer_absolute); ei_scroll_unref(device->scroll); ei_button_unref(device->button); ei_touchscreen_unref(device->touchscreen); ei_keyboard_unref(device->keyboard); ei_seat_unref(seat); free(device->name); free(device->pending_region_mapping_id); } _public_ OBJECT_IMPLEMENT_REF(ei_device); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_device); static OBJECT_IMPLEMENT_CREATE(ei_device); static OBJECT_IMPLEMENT_PARENT(ei_device, ei_seat); _public_ OBJECT_IMPLEMENT_GETTER(ei_device, type, enum ei_device_type); _public_ OBJECT_IMPLEMENT_GETTER(ei_device, name, const char *); _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 *); object_id_t ei_device_get_id(struct ei_device *device) { return device->proto_object.id; } _public_ struct ei_seat * ei_device_get_seat(struct ei_device *device) { return ei_device_parent(device); } _public_ struct ei* ei_device_get_context(struct ei_device *device) { assert(device); return ei_seat_get_context(ei_device_get_seat(device)); } static inline bool ei_device_in_region(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 true; } return false; } static struct brei_result * handle_msg_destroy(struct ei_device *device, uint32_t serial) { struct ei *ei = ei_device_get_context(device); ei_update_serial(ei, serial); log_debug(ei, "Removed device %#" PRIx64 "", ei_device_get_id(device)); ei_device_removed_by_server(device); return NULL; } static struct brei_result * handle_msg_name(struct ei_device *device, const char *name) { ei_device_set_name(device, name); return NULL; } static struct brei_result * handle_msg_device_type(struct ei_device *device, enum ei_device_type type) { if (device->type != 0) return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "EIS sent the device type twice"); ei_device_set_type(device, type); return NULL; } static struct brei_result * handle_msg_dimensions(struct ei_device *device, uint32_t width, uint32_t height) { if (ei_device_get_width(device) != 0 || ei_device_get_height(device) != 0) return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "EIS sent the device type twice"); ei_device_set_size(device, width, height); return NULL; } static struct brei_result * handle_msg_region(struct ei_device *device, uint32_t x, uint32_t y, uint32_t w, uint32_t h, float scale) { _unref_(ei_region) *r = ei_region_new(); 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) { struct ei *ei = ei_device_get_context(device); uint32_t width = ei_device_get_width(device); uint32_t height = ei_device_get_height(device); switch (ei_device_get_type(device)) { case EI_DEVICE_TYPE_PHYSICAL: if (width == 0 || height == 0) return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "missing width/height for physical device"); break; case EI_DEVICE_TYPE_VIRTUAL: if (width != 0 || height != 0) return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "no width/height allowed for virtual device"); break; default: return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Unsupported device type %ud", ei_device_get_type(device)); } if (device->pointer) mask_add(device->capabilities, EI_DEVICE_CAP_POINTER); if (device->pointer_absolute) mask_add(device->capabilities, EI_DEVICE_CAP_POINTER_ABSOLUTE); if (device->button) mask_add(device->capabilities, EI_DEVICE_CAP_BUTTON); if (device->scroll) mask_add(device->capabilities, EI_DEVICE_CAP_SCROLL); if (device->keyboard) mask_add(device->capabilities, EI_DEVICE_CAP_KEYBOARD); if (device->touchscreen) mask_add(device->capabilities, EI_DEVICE_CAP_TOUCH); if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) && !ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD) && !ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH) && !ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON) && !ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) { log_debug(ei, "Rejecting device %#" PRIx64 " '%s' with no known capabilities", ei_device_get_id(device), ei_device_get_name(device)); ei_device_close(device); /* FIXME: this is untested */ return NULL; } ei_device_added(device); ei_queue_device_added_event(device); ei_device_done(device); log_debug(ei, "Added device %#" PRIx64 " '%s' caps: %s%s%s%s%s%s seat: %s", ei_device_get_id(device), ei_device_get_name(device), ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) ? "p" : "", ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) ? "a" : "", ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD) ? "k" : "", ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH) ? "t" : "", ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON) ? "b" : "", ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL) ? "s" : "", ei_seat_get_name(ei_device_get_seat(device))); return NULL; } static struct brei_result * handle_msg_resumed(struct ei_device *device, uint32_t serial) { ei_update_serial(ei_device_get_context(device), serial); ei_device_resumed(device); ei_queue_device_resumed_event(device); return NULL; } static struct brei_result * handle_msg_paused(struct ei_device *device, uint32_t serial) { ei_update_serial(ei_device_get_context(device), serial); ei_device_paused(device); ei_queue_device_paused_event(device); return NULL; } #define DISCONNECT_IF_INVALID_ID(device_, id_) do { \ if (!brei_is_server_id(id_)) { \ struct ei *ei_ = ei_device_get_context(device_); \ log_bug(ei_, "Received invalid object id %#" PRIx64 ". Disconnecting", id_); \ return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Received invalid object id %#" PRIx64 ".", id_); \ } \ } while(0) #define DISCONNECT_IF_SENDER_CONTEXT(device_) do {\ struct ei *ei_ = ei_device_get_context(device_); \ if (ei_is_sender(ei_)) { \ return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_MODE, \ "Invalid event from receiver EIS context. Disconnecting"); \ } \ } while(0) static struct brei_result * handle_msg_start_emulating(struct ei_device *device, uint32_t serial, uint32_t sequence) { struct brei_result *result = NULL; DISCONNECT_IF_SENDER_CONTEXT(device); ei_update_serial(ei_device_get_context(device), serial); switch (device->state) { case EI_DEVICE_STATE_DEAD: case EI_DEVICE_STATE_NEW: case EI_DEVICE_STATE_PAUSED: 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); break; case EI_DEVICE_STATE_RESUMED: ei_queue_device_start_emulating_event(device, sequence); device->state = EI_DEVICE_STATE_EMULATING; break; case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: /* race condition, that's fine */ break; } return result; } static struct brei_result * handle_msg_stop_emulating(struct ei_device *device, uint32_t serial) { struct brei_result *result = NULL; DISCONNECT_IF_SENDER_CONTEXT(device); ei_update_serial(ei_device_get_context(device), serial); switch (device->state) { case EI_DEVICE_STATE_DEAD: case EI_DEVICE_STATE_NEW: case EI_DEVICE_STATE_PAUSED: 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); break; case EI_DEVICE_STATE_EMULATING: ei_queue_device_stop_emulating_event(device); device->state = EI_DEVICE_STATE_RESUMED; break; case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: /* race condition, that's fine */ break; } return result; } static struct brei_result * maybe_error_on_device_state(struct ei_device *device, const char *event_type) { switch (device->state) { case EI_DEVICE_STATE_EMULATING: return NULL; case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: /* we removed the device but server sent us an event - most * likely a race condition, so let's ignore it */ return NULL; case EI_DEVICE_STATE_NEW: case EI_DEVICE_STATE_PAUSED: case EI_DEVICE_STATE_RESUMED: case EI_DEVICE_STATE_REMOVED_FROM_SERVER: case EI_DEVICE_STATE_DEAD: break; } return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Invalid device state %ud for a %s event", device->state, event_type); } static struct brei_result * handle_msg_frame(struct ei_device *device, uint32_t serial, uint64_t time) { DISCONNECT_IF_SENDER_CONTEXT(device); ei_update_serial(ei_device_get_context(device), serial); if (device->state == EI_DEVICE_STATE_EMULATING) { ei_queue_frame_event(device, time); return NULL; } return maybe_error_on_device_state(device, "frame"); } static struct brei_result * handle_msg_interface(struct ei_device *device, object_id_t id, const char *name, uint32_t version) { 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"); device->touchscreen = ei_touchscreen_new(device, id, version); } else { return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Unsupported interface '%s' on device", name); } return NULL; } static const struct ei_device_interface interface = { .destroyed = handle_msg_destroy, .name = handle_msg_name, .device_type = handle_msg_device_type, .dimensions = handle_msg_dimensions, .region = handle_msg_region, .done = handle_msg_done, .resumed = handle_msg_resumed, .paused = handle_msg_paused, .start_emulating = handle_msg_start_emulating, .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 * ei_device_get_interface(struct ei_device *device) { return &interface; } static struct brei_result * handle_msg_pointer_rel(struct ei_pointer *pointer, float x, float y) { struct ei_device *device = ei_pointer_get_device(pointer); DISCONNECT_IF_SENDER_CONTEXT(device); if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) { return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Pointer rel event for non-pointer device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { ei_queue_pointer_rel_event(device, x, y); return NULL; } return maybe_error_on_device_state(device, "pointer rel"); } static struct brei_result * handle_msg_pointer_abs(struct ei_pointer_absolute *pointer, float x, float y) { struct ei_device *device = ei_pointer_absolute_get_device(pointer); DISCONNECT_IF_SENDER_CONTEXT(device); if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Pointer abs event for non-pointer device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { if (!ei_device_in_region(device, x, y)) return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_VALUE, "abs position outside regions"); ei_queue_pointer_abs_event(device, x, y); return NULL; } return maybe_error_on_device_state(device, "pointer abs"); } static struct brei_result * handle_msg_button(struct ei_button *button, uint32_t btn, uint32_t state) { struct ei_device *device = ei_button_get_device(button); DISCONNECT_IF_SENDER_CONTEXT(device); if (!ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON)) { return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Button event for non-button device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { ei_queue_pointer_button_event(device, btn, !!state); return NULL; } return maybe_error_on_device_state(device, "pointer button"); } static struct brei_result * handle_msg_scroll(struct ei_scroll *scroll, float x, float y) { struct ei_device *device = ei_scroll_get_device(scroll); DISCONNECT_IF_SENDER_CONTEXT(device); if (!ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) { return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Scroll event for non-scroll device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { ei_queue_pointer_scroll_event(device, x, y); return NULL; } return maybe_error_on_device_state(device, "pointer scroll"); } static struct brei_result * handle_msg_scroll_discrete(struct ei_scroll *scroll, int32_t x, int32_t y) { struct ei_device *device = ei_scroll_get_device(scroll); DISCONNECT_IF_SENDER_CONTEXT(device); if (!ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) { return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Scroll discrete event for non-scroll device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { ei_queue_pointer_scroll_discrete_event(device, x, y); return NULL; } return maybe_error_on_device_state(device, "pointer scroll discrete"); } static struct brei_result * handle_msg_scroll_stop(struct ei_scroll *scroll, uint32_t x, uint32_t y, uint32_t is_cancel) { struct ei_device *device = ei_scroll_get_device(scroll); DISCONNECT_IF_SENDER_CONTEXT(device); if (!ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) { return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Scroll stop event for non-scroll device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { if (is_cancel) ei_queue_pointer_scroll_cancel_event(device, !!x, !!y); else ei_queue_pointer_scroll_stop_event(device, !!x, !!y); return NULL; } return maybe_error_on_device_state(device, "pointer scroll discrete"); } static struct brei_result * handle_msg_pointer_destroy(struct ei_pointer *pointer, uint32_t serial) { struct ei *ei = ei_pointer_get_context(pointer); ei_update_serial(ei, serial); struct ei_device *device = ei_pointer_get_device(pointer); ei_pointer_unref(steal(&device->pointer)); return NULL; } static struct brei_result * handle_msg_pointer_absolute_destroy(struct ei_pointer_absolute *pointer_absolute, uint32_t serial) { struct ei *ei = ei_pointer_absolute_get_context(pointer_absolute); ei_update_serial(ei, serial); struct ei_device *device = ei_pointer_absolute_get_device(pointer_absolute); ei_pointer_absolute_unref(steal(&device->pointer_absolute)); return NULL; } static struct brei_result * handle_msg_scroll_destroy(struct ei_scroll *scroll, uint32_t serial) { struct ei *ei = ei_scroll_get_context(scroll); ei_update_serial(ei, serial); struct ei_device *device = ei_scroll_get_device(scroll); ei_scroll_unref(steal(&device->scroll)); return NULL; } static struct brei_result * handle_msg_button_destroy(struct ei_button *button, uint32_t serial) { struct ei *ei = ei_button_get_context(button); ei_update_serial(ei, serial); struct ei_device *device = ei_button_get_device(button); ei_button_unref(steal(&device->button)); return NULL; } static const struct ei_pointer_interface pointer_interface = { .destroyed = handle_msg_pointer_destroy, .motion_relative = handle_msg_pointer_rel, }; static const struct ei_pointer_absolute_interface pointer_absolute_interface = { .destroyed = handle_msg_pointer_absolute_destroy, .motion_absolute = handle_msg_pointer_abs, }; static const struct ei_button_interface button_interface = { .destroyed = handle_msg_button_destroy, .button = handle_msg_button, }; static const struct ei_scroll_interface scroll_interface = { .destroyed = handle_msg_scroll_destroy, .scroll = handle_msg_scroll, .scroll_stop = handle_msg_scroll_stop, .scroll_discrete = handle_msg_scroll_discrete, }; const struct ei_pointer_interface * ei_device_get_pointer_interface(struct ei_device *device) { return &pointer_interface; } const struct ei_pointer_absolute_interface * ei_device_get_pointer_absolute_interface(struct ei_device *device) { return &pointer_absolute_interface; } const struct ei_scroll_interface * ei_device_get_scroll_interface(struct ei_device *device) { return &scroll_interface; } const struct ei_button_interface * ei_device_get_button_interface(struct ei_device *device) { return &button_interface; } static struct brei_result * handle_msg_keymap(struct ei_keyboard *keyboard, uint32_t keymap_type, uint32_t keymap_sz, int keymap_fd) { struct ei_device *device = ei_keyboard_get_device(keyboard); ei_device_set_keymap(device, keymap_type, keymap_fd, keymap_sz); return NULL; } static struct brei_result * handle_msg_keyboard_key(struct ei_keyboard *keyboard, uint32_t key, uint32_t state) { struct ei_device *device = ei_keyboard_get_device(keyboard); DISCONNECT_IF_SENDER_CONTEXT(device); if (!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) { return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Key event for non-keyboard device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { ei_queue_keyboard_key_event(device, key, !!state); return NULL; } return maybe_error_on_device_state(device, "key"); } static struct brei_result * handle_msg_keyboard_modifiers(struct ei_keyboard *keyboard, uint32_t serial, uint32_t depressed, uint32_t locked, uint32_t latched, uint32_t group) { struct ei *ei = ei_keyboard_get_context(keyboard); ei_update_serial(ei, serial); struct ei_device *device = ei_keyboard_get_device(keyboard); if (!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) { return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Modifier event for non-keyboard device"); } struct ei_xkb_modifiers mods = { .depressed = depressed, .latched = latched, .locked = locked, .group = group, }; ei_queue_keyboard_modifiers_event(device, &mods); return NULL; } static struct brei_result * handle_msg_keyboard_destroy(struct ei_keyboard *keyboard, uint32_t serial) { struct ei *ei = ei_keyboard_get_context(keyboard); ei_update_serial(ei, serial); struct ei_device *device = ei_keyboard_get_device(keyboard); ei_keyboard_unref(steal(&device->keyboard)); return NULL; } static const struct ei_keyboard_interface keyboard_interface = { .destroyed = handle_msg_keyboard_destroy, .keymap = handle_msg_keymap, .key = handle_msg_keyboard_key, .modifiers = handle_msg_keyboard_modifiers, }; const struct ei_keyboard_interface * ei_device_get_keyboard_interface(struct ei_device *device) { return &keyboard_interface; } static struct brei_result * handle_msg_touch_down(struct ei_touchscreen *touchscreen, uint32_t touchid, float x, float y) { 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 down event for non-touch device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { ei_queue_touch_down_event(device, touchid, x, y); return NULL; } return maybe_error_on_device_state(device, "touch down"); } static struct brei_result * handle_msg_touch_motion(struct ei_touchscreen *touchscreen, uint32_t touchid, float x, float y) { 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 motion event for non-touch device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { ei_queue_touch_motion_event(device, touchid, x, y); return NULL; } return maybe_error_on_device_state(device, "touch motion"); } static struct brei_result * handle_msg_touch_up(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 up event for non-touch device"); } if (device->state == EI_DEVICE_STATE_EMULATING) { ei_queue_touch_up_event(device, touchid); return NULL; } return maybe_error_on_device_state(device, "touch up"); } static struct brei_result * handle_msg_touchscreen_destroy(struct ei_touchscreen *touchscreen, uint32_t serial) { struct ei *ei = ei_touchscreen_get_context(touchscreen); ei_update_serial(ei, serial); struct ei_device *device = ei_touchscreen_get_device(touchscreen); ei_touchscreen_unref(steal(&device->touchscreen)); return NULL; } static const struct ei_touchscreen_interface touchscreen_interface = { .destroyed = handle_msg_touchscreen_destroy, .down = handle_msg_touch_down, .motion = handle_msg_touch_motion, .up = handle_msg_touch_up, }; const struct ei_touchscreen_interface * ei_device_get_touchscreen_interface(struct ei_device *device) { return &touchscreen_interface; } struct ei_device * ei_device_new(struct ei_seat *seat, object_id_t deviceid, uint32_t version) { struct ei_device *device = ei_device_create(&seat->object); struct ei *ei = ei_seat_get_context(seat); device->proto_object.id = deviceid, device->proto_object.implementation = device; device->proto_object.interface = &ei_device_proto_interface; device->proto_object.version = version; list_init(&device->proto_object.link); ei_register_object(ei, &device->proto_object); device->capabilities = 0; device->state = EI_DEVICE_STATE_NEW; device->name = xaprintf("unnamed device %#" PRIx64 "", deviceid); list_init(&device->regions); list_init(&device->pending_event_queue); /* We have a ref to the seat to make sure our seat doesn't get * destroyed while a ref to the device is still alive. * And the seat has a ref to the device in the seat->devices list. * dropped when the device is removed. */ ei_seat_ref(seat); return device; } void ei_device_done(struct ei_device *device) { ei_device_paused(device); } void ei_device_add_region(struct ei_device *device, struct ei_region *region) { if (device->state != EI_DEVICE_STATE_NEW) return; ei_region_ref(region); list_append(&device->regions, ®ion->link); } _public_ OBJECT_IMPLEMENT_REF(ei_keymap); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_keymap); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, type, enum ei_keymap_type); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, fd, int); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, size, size_t); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, device, struct ei_device *); _public_ OBJECT_IMPLEMENT_GETTER(ei_keymap, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(ei_keymap, user_data, void *); static void ei_keymap_destroy(struct ei_keymap *keymap) { xclose(keymap->fd); } static OBJECT_IMPLEMENT_CREATE(ei_keymap); _public_ struct ei_keymap * ei_device_keyboard_get_keymap(struct ei_device *device) { return device->keymap; } static struct ei_keymap * ei_keymap_new(struct ei *ei, enum ei_keymap_type type, int fd, size_t size) { _unref_(ei_keymap) *keymap = ei_keymap_create(NULL); switch (type) { case EI_KEYMAP_TYPE_XKB: break; default: log_bug(ei, "Unsupported keymap type: %u", type); return NULL; } 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) { log_bug(ei, "Failed to dup keymap fd: %s", strerror(errno)); return NULL; } keymap->fd = newfd; keymap->type = type; keymap->size = size; return ei_keymap_ref(keymap); } void ei_device_set_keymap(struct ei_device *device, enum ei_keymap_type type, int keymap_fd, size_t size) { device->keymap = ei_keymap_unref(device->keymap); if (!type) return; 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); } static int ei_device_send_release(struct ei_device *device) { struct ei *ei = ei_device_get_context(device); if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; if (device->pointer) ei_pointer_request_release(device->pointer); if (device->keyboard) ei_keyboard_request_release(device->keyboard); if (device->touchscreen) ei_touchscreen_request_release(device->touchscreen); if (device->scroll) ei_scroll_request_release(device->scroll); if (device->button) ei_button_request_release(device->button); int rc = ei_device_request_release(device); if (rc) ei_disconnect(ei); return rc; } _public_ void ei_device_close(struct ei_device *device) { switch (device->state) { case EI_DEVICE_STATE_NEW: case EI_DEVICE_STATE_DEAD: case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: case EI_DEVICE_STATE_REMOVED_FROM_SERVER: break; case EI_DEVICE_STATE_EMULATING: if (ei_is_sender(ei_device_get_context(device))) ei_device_request_stop_emulating(device, ei_get_serial(ei_device_get_context(device))); _fallthrough_; case EI_DEVICE_STATE_PAUSED: case EI_DEVICE_STATE_RESUMED: ei_device_set_state(device, EI_DEVICE_STATE_REMOVED_FROM_CLIENT); ei_device_send_release(device); break; } } void 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); switch (device->state) { case EI_DEVICE_STATE_NEW: case EI_DEVICE_STATE_DEAD: case EI_DEVICE_STATE_REMOVED_FROM_SERVER: break; case EI_DEVICE_STATE_REMOVED_FROM_CLIENT: case EI_DEVICE_STATE_PAUSED: case EI_DEVICE_STATE_RESUMED: case EI_DEVICE_STATE_EMULATING: /* 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 */ ei_pointer_unref(steal(&device->pointer)); ei_keyboard_unref(steal(&device->keyboard)); ei_touchscreen_unref(steal(&device->touchscreen)); ei_scroll_unref(steal(&device->scroll)); ei_button_unref(steal(&device->button)); ei_unregister_object(ei, &device->proto_object); ei_queue_device_removed_event(device); ei_device_set_state(device, EI_DEVICE_STATE_DEAD); /* Device is dead now. Move it from the devices list to * removed and drop the seat's ref to the device. * This should be the last ref to the device that libei has * (not counting any queued events). Device is kept alive by * any client refs but once those drop, the device can be * destroyed. */ list_remove(&device->link); list_append(&seat->devices_removed, &device->link); ei_device_unref(device); break; } } void ei_device_resumed(struct ei_device *device) { ei_device_set_state(device, EI_DEVICE_STATE_RESUMED); } void ei_device_paused(struct ei_device *device) { ei_device_set_state(device, EI_DEVICE_STATE_PAUSED); } void ei_device_added(struct ei_device *device) { } void ei_device_set_type(struct ei_device *device, enum ei_device_type type) { switch(type) { case EI_DEVICE_TYPE_PHYSICAL: case EI_DEVICE_TYPE_VIRTUAL: device->type = type; break; default: log_bug(ei_device_get_context(device), "Invalid device type %u", type); break; } } void ei_device_set_size(struct ei_device *device, uint32_t width, uint32_t height) { if (device->type == EI_DEVICE_TYPE_PHYSICAL) { device->width = width; device->height = height; } } void ei_device_set_name(struct ei_device *device, const char *name) { free(device->name); device->name = xstrdup(name); } _public_ bool ei_device_has_capability(struct ei_device *device, enum ei_device_capability cap) { switch (cap) { case EI_DEVICE_CAP_POINTER: case EI_DEVICE_CAP_POINTER_ABSOLUTE: case EI_DEVICE_CAP_KEYBOARD: case EI_DEVICE_CAP_TOUCH: case EI_DEVICE_CAP_BUTTON: case EI_DEVICE_CAP_SCROLL: return mask_all(device->capabilities, cap); } return false; } static void ei_device_frame_now(struct ei_device *device) { uint64_t now = ei_now(ei_device_get_context(device)); ei_device_frame(device, now); } static void _flush_frame(struct ei_device *device, const char *func) { if (device->send_frame_event) { log_bug_client(ei_device_get_context(device), "%s: missing call to ei_device_frame()", func); ei_device_frame_now(device); } } #define ei_device_flush_frame(d_) _flush_frame(d_, __func__) _public_ void ei_device_start_emulating(struct ei_device *device, uint32_t sequence) { struct ei *ei = ei_device_get_context(device); if (device->state != EI_DEVICE_STATE_RESUMED) return; assert(!device->send_frame_event); device->state = EI_DEVICE_STATE_EMULATING; int rc = ei_device_request_start_emulating(device, ei_get_serial(ei), sequence); if (rc) ei_disconnect(ei_device_get_context(device)); } _public_ void ei_device_stop_emulating(struct ei_device *device) { struct ei *ei = ei_device_get_context(device); if (device->state != EI_DEVICE_STATE_EMULATING) return; ei_device_flush_frame(device); device->state = EI_DEVICE_STATE_RESUMED; int rc = ei_device_request_stop_emulating(device, ei_get_serial(ei)); if (rc) ei_disconnect(ei_device_get_context(device)); } _public_ struct ei_region * 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) { if (x) { device->scroll_state.x_is_stopped = false; device->scroll_state.x_is_cancelled = false; } if (y) { device->scroll_state.y_is_stopped = false; device->scroll_state.y_is_cancelled = false; } } static int ei_send_pointer_rel(struct ei_device *device, double x, double y) { 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_pointer_request_motion_relative(device->pointer, x, y); if (rc) ei_disconnect(ei); return rc; } static int ei_send_pointer_abs(struct ei_device *device, double x, double y) { 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_pointer_absolute_request_motion_absolute(device->pointer_absolute, x, y); if (rc) ei_disconnect(ei); return rc; } static int ei_send_pointer_button(struct ei_device *device, uint32_t button, bool is_press) { 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_button_request_button(device->button, button, is_press); if (rc) ei_disconnect(ei); return rc; } static int ei_send_pointer_scroll(struct ei_device *device, double x, double y) { 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_scroll_request_scroll(device->scroll, x, y); if (rc) ei_disconnect(ei); return rc; } static int ei_send_pointer_scroll_stop(struct ei_device *device, double x, double y) { 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_scroll_request_scroll_stop(device->scroll, x, y, false); if (rc) ei_disconnect(ei); return rc; } static int ei_send_pointer_scroll_cancel(struct ei_device *device, double x, double y) { 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_scroll_request_scroll_stop(device->scroll, x, y, true); if (rc) ei_disconnect(ei); return rc; } static int ei_send_pointer_scroll_discrete(struct ei_device *device, int32_t x, int32_t y) { 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_scroll_request_scroll_discrete(device->scroll, x, y); if (rc) ei_disconnect(ei); return rc; } _public_ void ei_device_pointer_motion(struct ei_device *device, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) { log_bug_client(ei_device_get_context(device), "%s: device is not a pointer", __func__); return; } if (device->state != EI_DEVICE_STATE_EMULATING) { log_bug_client(ei_device_get_context(device), "%s: device is not emulating", __func__); return; } ei_send_pointer_rel(device, x, y); } _public_ void ei_device_pointer_motion_absolute(struct ei_device *device, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(ei_device_get_context(device), "%s: device is not an absolute pointer", __func__); return; } if (device->state != EI_DEVICE_STATE_EMULATING) { log_bug_client(ei_device_get_context(device), "%s: device is not emulating", __func__); return; } if (!ei_device_in_region(device, x, y)) return; ei_send_pointer_abs(device, x, y); } _public_ void ei_device_button_button(struct ei_device *device, uint32_t button, bool is_press) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON)) { log_bug_client(ei_device_get_context(device), "%s: device is not a button device", __func__); return; } if (device->state != EI_DEVICE_STATE_EMULATING) { log_bug_client(ei_device_get_context(device), "%s: device is not emulating", __func__); return; } /* Ignore anything < BTN_MOUSE. Avoids the common error of sending * numerical buttons instead of BTN_LEFT and friends. */ if (button < 0x110) { log_bug_client(ei_device_get_context(device), "%s: button code must be one of BTN_*", __func__); return; } ei_send_pointer_button(device, button, is_press); } _public_ void ei_device_scroll_delta(struct ei_device *device, double x, double y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) { log_bug_client(ei_device_get_context(device), "%s: device is not scroll device", __func__); } if (device->state != EI_DEVICE_STATE_EMULATING) { log_bug_client(ei_device_get_context(device), "%s: device is not emulating", __func__); return; } ei_device_resume_scrolling(device, x, y); ei_send_pointer_scroll(device, x, y); } _public_ void ei_device_scroll_stop(struct ei_device *device, bool x, bool y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) { log_bug_client(ei_device_get_context(device), "%s: device is not scroll device", __func__); } if (device->state != EI_DEVICE_STATE_EMULATING) { log_bug_client(ei_device_get_context(device), "%s: device is not emulating", __func__); return; } /* Filter out duplicate scroll stop requests */ if (x && !device->scroll_state.x_is_stopped) device->scroll_state.x_is_stopped = true; else x = false; if (y && !device->scroll_state.y_is_stopped) device->scroll_state.y_is_stopped = true; else y = false; if (x || y) ei_send_pointer_scroll_stop(device, x, y); } _public_ void ei_device_scroll_cancel(struct ei_device *device, bool x, bool y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) { log_bug_client(ei_device_get_context(device), "%s: device is not scroll device", __func__); } if (device->state != EI_DEVICE_STATE_EMULATING) { log_bug_client(ei_device_get_context(device), "%s: device is not emulating", __func__); return; } /* Filter out duplicate scroll cancelled requests */ if (x && !device->scroll_state.x_is_cancelled) { device->scroll_state.x_is_stopped = true; device->scroll_state.x_is_cancelled = true; } else { x = false; } if (y && !device->scroll_state.y_is_cancelled) { device->scroll_state.y_is_stopped = true; device->scroll_state.y_is_cancelled = true; } else { y = false; } if (x || y) ei_send_pointer_scroll_cancel(device, x, y); } _public_ void ei_device_scroll_discrete(struct ei_device *device, int32_t x, int32_t y) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) { log_bug_client(ei_device_get_context(device), "%s: device is not scroll device", __func__); } if (device->state != EI_DEVICE_STATE_EMULATING) { log_bug_client(ei_device_get_context(device), "%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); } static int ei_send_keyboard_key(struct ei_device *device, uint32_t key, bool is_press) { 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_keyboard_request_key(device->keyboard, key, is_press); if (rc) ei_disconnect(ei); return rc; } _public_ void ei_device_keyboard_key(struct ei_device *device, uint32_t key, bool is_press) { if (!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) { log_bug_client(ei_device_get_context(device), "%s: device is not a keyboard", __func__); return; } if (device->state != EI_DEVICE_STATE_EMULATING) { log_bug_client(ei_device_get_context(device), "%s: device is not emulating", __func__); return; } ei_send_keyboard_key(device, key, is_press); } _public_ OBJECT_IMPLEMENT_REF(ei_touch); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_touch); _public_ OBJECT_IMPLEMENT_GETTER(ei_touch, device, struct ei_device*); _public_ OBJECT_IMPLEMENT_GETTER(ei_touch, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(ei_touch, user_data, void *); static void ei_touch_destroy(struct ei_touch *touch) { if (touch->state == TOUCH_IS_DOWN) ei_touch_up(touch); /* Enforce a frame, otherwise we're just pending. If the client * doesn't want this, it needs to ei_touch_up() */ ei_device_frame_now(touch->device); ei_device_unref(touch->device); } static OBJECT_IMPLEMENT_CREATE(ei_touch); static int ei_send_touch_down(struct ei_device *device, uint32_t tid, double x, double y) { 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_down(device->touchscreen, tid, x, y); if (rc) ei_disconnect(ei); return rc; } static int ei_send_touch_motion(struct ei_device *device, uint32_t tid, double x, double y) { 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_motion(device->touchscreen, tid, x, y); if (rc) ei_disconnect(ei); return rc; } static int ei_send_touch_up(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_up(device->touchscreen, tid); if (rc) ei_disconnect(ei); return rc; } _public_ struct ei_touch * ei_device_touch_new(struct ei_device *device) { static uint32_t tracking_id = 0; /* Not using the device as parent object because we need a ref * to it */ struct ei_touch *touch = ei_touch_create(NULL); touch->device = ei_device_ref(device); touch->state = TOUCH_IS_NEW; touch->tracking_id = ++tracking_id; return touch; } _public_ void ei_touch_down(struct ei_touch *touch, double x, double y) { 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_NEW) { log_bug_client(ei_device_get_context(device), "%s: touch %u already down or up", __func__, touch->tracking_id); 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; } } touch->state = TOUCH_IS_DOWN; ei_send_touch_down(device, touch->tracking_id, x, y); } _public_ void ei_touch_motion(struct ei_touch *touch, double x, double y) { 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; } 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; } } ei_send_touch_motion(touch->device, touch->tracking_id, x, y); } _public_ void 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 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; ei_send_touch_up(touch->device, touch->tracking_id); } _public_ void ei_device_frame(struct ei_device *device, uint64_t time) { struct ei *ei = ei_device_get_context(device); if (device->state != EI_DEVICE_STATE_EMULATING) return; if (!device->send_frame_event) return; device->send_frame_event = false; int rc = ei_device_request_frame(device, ei_get_serial(ei), time); if (rc) ei_disconnect(ei_device_get_context(device)); return; } libei-1.2.1/src/libei-device.h000066400000000000000000000111131456005336000160570ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "libei.h" #include "util-object.h" #include "util-list.h" #include "brei-shared.h" #include "libei-pointer.h" #include "libei-keyboard.h" #include "libei-touchscreen.h" enum ei_device_state { /* Before the DeviceAddedDone was received */ EI_DEVICE_STATE_NEW, EI_DEVICE_STATE_PAUSED, EI_DEVICE_STATE_RESUMED, EI_DEVICE_STATE_EMULATING, /** * Client removed the device, we no longer accept events from the * client */ EI_DEVICE_STATE_REMOVED_FROM_CLIENT, /** * Server removed the device, we need to remove it ourselves now. */ EI_DEVICE_STATE_REMOVED_FROM_SERVER, /** * Device has been removed by both sides. */ EI_DEVICE_STATE_DEAD, }; struct ei_device { struct object object; void *user_data; struct brei_object proto_object; struct ei_pointer *pointer; struct ei_pointer_absolute *pointer_absolute; struct ei_scroll *scroll; struct ei_button *button; struct ei_keyboard *keyboard; struct ei_touchscreen *touchscreen; struct list link; enum ei_device_state state; uint32_t capabilities; char *name; enum ei_device_type type; struct list pending_event_queue; /* incoming events waiting for a frame */ bool send_frame_event; uint32_t width, height; struct list regions; struct { bool x_is_stopped, y_is_stopped; bool x_is_cancelled, y_is_cancelled; } scroll_state; struct ei_keymap *keymap; char *pending_region_mapping_id; }; struct ei_keymap { struct object object; struct ei_device *device; void *user_data; enum ei_keymap_type type; int fd; size_t size; bool assigned; }; struct ei_touch { struct object object; struct ei_device *device; void *user_data; uint32_t tracking_id; enum { TOUCH_IS_NEW, TOUCH_IS_DOWN, TOUCH_IS_UP, } state; double x, y; }; OBJECT_DECLARE_GETTER(ei_device, id, object_id_t); OBJECT_DECLARE_GETTER(ei_device, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(ei_device, interface, const struct ei_device_interface *); OBJECT_DECLARE_GETTER(ei_device, pointer_interface, const struct ei_pointer_interface *); OBJECT_DECLARE_GETTER(ei_device, pointer_absolute_interface, const struct ei_pointer_absolute_interface *); OBJECT_DECLARE_GETTER(ei_device, scroll_interface, const struct ei_scroll_interface *); OBJECT_DECLARE_GETTER(ei_device, button_interface, const struct ei_button_interface *); OBJECT_DECLARE_GETTER(ei_device, keyboard_interface, const struct ei_keyboard_interface *); OBJECT_DECLARE_GETTER(ei_device, touchscreen_interface, const struct ei_touchscreen_interface *); OBJECT_DECLARE_SETTER(ei_device, type, enum ei_device_type); OBJECT_DECLARE_SETTER(ei_device, name, const char*); OBJECT_DECLARE_SETTER(ei_device, seat, const char*); struct ei_device * ei_device_new(struct ei_seat *seat, object_id_t deviceid, uint32_t version); void ei_device_add_region(struct ei_device *device, struct ei_region *r); void ei_device_done(struct ei_device *device); void ei_device_removed_by_server(struct ei_device *device); void ei_device_event_start_emulating(struct ei_device *device, uint32_t sequence); void ei_device_event_stop_emulating(struct ei_device *device); void ei_device_added(struct ei_device *device); void ei_device_paused(struct ei_device *device); void ei_device_resumed(struct ei_device *device); void ei_device_set_size(struct ei_device *device, uint32_t width, uint32_t height); void ei_device_set_keymap(struct ei_device *device, enum ei_keymap_type type, int keymap_fd, size_t size); libei-1.2.1/src/libei-event.c000066400000000000000000000216061456005336000157440ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2021 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 "libei-private.h" OBJECT_IMPLEMENT_REF(ei_event); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_event); _public_ OBJECT_IMPLEMENT_GETTER(ei_event, type, enum ei_event_type); _public_ OBJECT_IMPLEMENT_GETTER(ei_event, device, struct ei_device*); _public_ OBJECT_IMPLEMENT_GETTER(ei_event, seat, struct ei_seat*); _public_ const char * ei_event_type_to_string(enum ei_event_type type) { switch(type) { CASE_RETURN_STRING(EI_EVENT_CONNECT); CASE_RETURN_STRING(EI_EVENT_DISCONNECT); CASE_RETURN_STRING(EI_EVENT_SEAT_ADDED); CASE_RETURN_STRING(EI_EVENT_SEAT_REMOVED); CASE_RETURN_STRING(EI_EVENT_DEVICE_ADDED); CASE_RETURN_STRING(EI_EVENT_DEVICE_REMOVED); 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_FRAME); CASE_RETURN_STRING(EI_EVENT_DEVICE_START_EMULATING); CASE_RETURN_STRING(EI_EVENT_DEVICE_STOP_EMULATING); CASE_RETURN_STRING(EI_EVENT_POINTER_MOTION); CASE_RETURN_STRING(EI_EVENT_POINTER_MOTION_ABSOLUTE); CASE_RETURN_STRING(EI_EVENT_BUTTON_BUTTON); CASE_RETURN_STRING(EI_EVENT_SCROLL_DELTA); CASE_RETURN_STRING(EI_EVENT_SCROLL_STOP); CASE_RETURN_STRING(EI_EVENT_SCROLL_CANCEL); CASE_RETURN_STRING(EI_EVENT_SCROLL_DISCRETE); CASE_RETURN_STRING(EI_EVENT_KEYBOARD_KEY); CASE_RETURN_STRING(EI_EVENT_TOUCH_DOWN); CASE_RETURN_STRING(EI_EVENT_TOUCH_UP); CASE_RETURN_STRING(EI_EVENT_TOUCH_MOTION); } return NULL; } static void ei_event_destroy(struct ei_event *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: case EI_EVENT_KEYBOARD_MODIFIERS: case EI_EVENT_FRAME: case EI_EVENT_DEVICE_START_EMULATING: case EI_EVENT_DEVICE_STOP_EMULATING: case EI_EVENT_POINTER_MOTION: case EI_EVENT_POINTER_MOTION_ABSOLUTE: case EI_EVENT_BUTTON_BUTTON: case EI_EVENT_SCROLL_DELTA: case EI_EVENT_SCROLL_STOP: case EI_EVENT_SCROLL_CANCEL: case EI_EVENT_SCROLL_DISCRETE: case EI_EVENT_KEYBOARD_KEY: case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_UP: case EI_EVENT_TOUCH_MOTION: break; } ei_device_unref(event->device); ei_seat_unref(event->seat); } static OBJECT_IMPLEMENT_CREATE(ei_event); static OBJECT_IMPLEMENT_PARENT(ei_event, ei); struct ei_event * ei_event_new(struct ei *ei) { return ei_event_create(&ei->object); } struct ei_event * ei_event_new_for_device(struct ei_device *device) { struct ei_event *event = ei_event_new(ei_device_get_context(device)); event->seat = ei_seat_ref(ei_device_get_seat(device)); event->device = ei_device_ref(device); return event; } struct ei * ei_event_get_context(struct ei_event *event) { return ei_event_parent(event); } static inline bool check_event_type(struct ei_event *event, const char *function_name, ...) { bool rc = false; va_list args; unsigned int type_permitted; enum ei_event_type type = ei_event_get_type(event); va_start(args, function_name); type_permitted = va_arg(args, unsigned int); while (type_permitted != (unsigned int)-1) { if (type_permitted == type) { rc = true; break; } type_permitted = va_arg(args, unsigned int); } va_end(args); if (!rc) log_bug_client(ei_event_get_context(event), "Invalid event type %u passed to %s()", type, function_name); return rc; } #define require_event_type(event_, retval_, ...) \ if (!check_event_type(event_, __func__, __VA_ARGS__, -1)) \ return retval_; \ _public_ uint32_t ei_event_emulating_get_sequence(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_DEVICE_START_EMULATING); return event->start_emulating.sequence; } _public_ uint32_t ei_event_keyboard_get_xkb_mods_depressed(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_KEYBOARD_MODIFIERS); return event->modifiers.depressed; } _public_ uint32_t ei_event_keyboard_get_xkb_mods_latched(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_KEYBOARD_MODIFIERS); return event->modifiers.latched; } _public_ uint32_t ei_event_keyboard_get_xkb_mods_locked(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_KEYBOARD_MODIFIERS); return event->modifiers.locked; } _public_ uint32_t ei_event_keyboard_get_xkb_group(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_KEYBOARD_MODIFIERS); return event->modifiers.group; } _public_ double ei_event_pointer_get_dx(struct ei_event *event) { require_event_type(event, 0.0, EI_EVENT_POINTER_MOTION) return event->pointer.dx; } _public_ double ei_event_pointer_get_dy(struct ei_event *event) { require_event_type(event, 0.0, EI_EVENT_POINTER_MOTION); return event->pointer.dy; } _public_ double ei_event_pointer_get_absolute_x(struct ei_event *event) { require_event_type(event, 0.0, EI_EVENT_POINTER_MOTION_ABSOLUTE); return event->pointer.absx; } _public_ double ei_event_pointer_get_absolute_y(struct ei_event *event) { require_event_type(event, 0.0, EI_EVENT_POINTER_MOTION_ABSOLUTE); return event->pointer.absy; } _public_ uint32_t ei_event_button_get_button(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_BUTTON_BUTTON); return event->pointer.button; } _public_ bool ei_event_button_get_is_press(struct ei_event *event) { require_event_type(event, false, EI_EVENT_BUTTON_BUTTON); return event->pointer.button_is_press; } _public_ double ei_event_scroll_get_dx(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_SCROLL_DELTA); return event->pointer.sx; } _public_ double ei_event_scroll_get_dy(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_SCROLL_DELTA); return event->pointer.sy; } _public_ int32_t ei_event_scroll_get_discrete_dx(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_SCROLL_DISCRETE); return event->pointer.sdx; } _public_ int32_t ei_event_scroll_get_discrete_dy(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_SCROLL_DISCRETE); return event->pointer.sdy; } _public_ bool ei_event_scroll_get_stop_x(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_SCROLL_STOP, EI_EVENT_SCROLL_CANCEL); return event->pointer.stop_x; } _public_ bool ei_event_scroll_get_stop_y(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_SCROLL_STOP, EI_EVENT_SCROLL_CANCEL); return event->pointer.stop_y; } _public_ uint32_t ei_event_keyboard_get_key(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_KEYBOARD_KEY); return event->keyboard.key; } _public_ bool ei_event_keyboard_get_key_is_press(struct ei_event *event) { require_event_type(event, false, EI_EVENT_KEYBOARD_KEY); return event->keyboard.key_is_press; } _public_ uint32_t ei_event_touch_get_id(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_TOUCH_DOWN, EI_EVENT_TOUCH_UP, EI_EVENT_TOUCH_MOTION); return event->touch.touchid; } _public_ double ei_event_touch_get_x(struct ei_event *event) { require_event_type(event, 0.0, EI_EVENT_TOUCH_DOWN, EI_EVENT_TOUCH_MOTION); return event->touch.x; } _public_ double ei_event_touch_get_y(struct ei_event *event) { require_event_type(event, 0.0, EI_EVENT_TOUCH_DOWN, EI_EVENT_TOUCH_MOTION); return event->touch.y; } _public_ uint64_t ei_event_get_time(struct ei_event *event) { require_event_type(event, 0, EI_EVENT_POINTER_MOTION, EI_EVENT_POINTER_MOTION_ABSOLUTE, EI_EVENT_BUTTON_BUTTON, EI_EVENT_SCROLL_DELTA, EI_EVENT_SCROLL_STOP, EI_EVENT_SCROLL_CANCEL, EI_EVENT_SCROLL_DISCRETE, EI_EVENT_KEYBOARD_KEY, EI_EVENT_TOUCH_DOWN, EI_EVENT_TOUCH_UP, EI_EVENT_TOUCH_MOTION, EI_EVENT_FRAME); return event->timestamp; } libei-1.2.1/src/libei-event.h000066400000000000000000000044421456005336000157500ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "libei.h" #include "util-object.h" #include "util-list.h" struct ei; struct ei_xkb_modifiers { uint32_t depressed; uint32_t latched; uint32_t locked; uint32_t group; }; struct ei_event { struct object object; /* Parent is struct ei */ enum ei_event_type type; struct list link; struct ei_seat *seat; /* NULL if device is non-NULL */ struct ei_device *device; uint64_t timestamp; union { struct ei_xkb_modifiers modifiers; struct { double dx, dy; /* relative motion */ double absx, absy; /* absolute motion */ double sx, sy; /* scroll */ int32_t sdx, sdy; /* discrete scroll */ bool stop_x, stop_y; /* scroll stop */ uint32_t button; bool button_is_press; } pointer; struct { uint32_t key; bool key_is_press; } keyboard; struct { uint32_t touchid; double x, y; } touch; struct { uint32_t sequence; } start_emulating; }; }; struct ei_event * ei_event_new(struct ei *ei); struct ei_event * 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); libei-1.2.1/src/libei-fd.c000066400000000000000000000043231456005336000152110ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include #include "util-mem.h" #include "util-macros.h" #include "util-sources.h" #include "util-strings.h" #include "util-object.h" #include "libei.h" #include "libei-private.h" /** * This is the simplest backend: the caller prepares a socket and we just * take that and use it. */ struct ei_fd { struct object object; }; static inline void ei_fd_destroy(struct ei_fd *backend) { /* nothing to do here */ } static OBJECT_IMPLEMENT_CREATE(ei_fd); static OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_fd); static void interface_fd_destroy(struct ei *ei, void *backend) { struct ei_fd *ei_fd = backend; ei_fd_unref(ei_fd); } static const struct ei_backend_interface interface = { .destroy = interface_fd_destroy, }; _public_ int ei_setup_backend_fd(struct ei *ei, int fd) { assert(ei); assert(!ei->backend); struct ei_fd *ei_fd = ei_fd_create(&ei->object); ei->backend = ei_fd; ei->backend_interface = interface; return ei_set_socket(ei, fd); } libei-1.2.1/src/libei-handshake.c000066400000000000000000000161461456005336000165540ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void ei_handshake_destroy(struct ei_handshake *handshake) { struct ei *ei = ei_handshake_get_context(handshake); ei_unregister_object(ei, &handshake->proto_object); } OBJECT_IMPLEMENT_REF(ei_handshake); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_handshake); OBJECT_IMPLEMENT_GETTER(ei_handshake, user_data, void*); OBJECT_IMPLEMENT_SETTER(ei_handshake, user_data, void*); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_handshake, proto_object, const struct brei_object *); static OBJECT_IMPLEMENT_CREATE(ei_handshake); static OBJECT_IMPLEMENT_PARENT(ei_handshake, ei); struct ei* ei_handshake_get_context(struct ei_handshake *handshake) { assert(handshake); return ei_handshake_parent(handshake); } uint32_t ei_handshake_get_version(struct ei_handshake *handshake) { return handshake->proto_object.version; } static int ei_handshake_initialize(struct ei_handshake *setup, uint32_t version) { struct ei *ei = ei_handshake_get_context(setup); struct ei_interface_versions *v = &ei->interface_versions; ei_handshake_request_handshake_version(setup, v->ei_handshake); if (version >= EI_HANDSHAKE_REQUEST_CONTEXT_TYPE_SINCE_VERSION) ei_handshake_request_context_type(setup, ei->is_sender ? EI_HANDSHAKE_CONTEXT_TYPE_SENDER : EI_HANDSHAKE_CONTEXT_TYPE_RECEIVER); if (version >= EI_HANDSHAKE_REQUEST_NAME_SINCE_VERSION) ei_handshake_request_name(setup, ei->name); if (version >= EI_HANDSHAKE_REQUEST_INTERFACE_VERSION_SINCE_VERSION) { ei_handshake_request_interface_version(setup, EI_CONNECTION_INTERFACE_NAME, v->ei_connection); ei_handshake_request_interface_version(setup, EI_CALLBACK_INTERFACE_NAME, v->ei_callback); ei_handshake_request_interface_version(setup, EI_PINGPONG_INTERFACE_NAME, v->ei_pingpong); ei_handshake_request_interface_version(setup, EI_SEAT_INTERFACE_NAME, v->ei_seat); ei_handshake_request_interface_version(setup, EI_DEVICE_INTERFACE_NAME, v->ei_device); ei_handshake_request_interface_version(setup, EI_POINTER_INTERFACE_NAME, v->ei_pointer); ei_handshake_request_interface_version(setup, EI_POINTER_ABSOLUTE_INTERFACE_NAME, v->ei_pointer_absolute); ei_handshake_request_interface_version(setup, EI_SCROLL_INTERFACE_NAME, v->ei_scroll); ei_handshake_request_interface_version(setup, EI_BUTTON_INTERFACE_NAME, v->ei_button); ei_handshake_request_interface_version(setup, EI_KEYBOARD_INTERFACE_NAME, v->ei_keyboard); ei_handshake_request_interface_version(setup, EI_TOUCHSCREEN_INTERFACE_NAME, v->ei_touchscreen); } ei_handshake_request_finish(setup); return 0; } static struct brei_result * handle_msg_handshake_version(struct ei_handshake *setup, uint32_t version) { struct ei *ei = ei_handshake_get_context(setup); struct ei_interface_versions *v = &ei->interface_versions; uint32_t min_version = min(version, ei->interface_versions.ei_handshake); v->ei_handshake = min_version; /* Now upgrade our protocol object to the server version (if applicable) */ setup->proto_object.version = min_version; /* Now send all the bits we need to send */ ei_handshake_initialize(setup, min_version); return NULL; } static struct brei_result * handle_msg_interface_version(struct ei_handshake *setup, const char *name, uint32_t version) { struct ei *ei = ei_handshake_get_context(setup); struct ei_interface_versions *v = &ei->interface_versions; if (streq(name, EI_HANDSHAKE_INTERFACE_NAME)) { /* EIS shouldn't send this anyway, let's ignore this */ } #define VERSION_UPDATE(iface_) if (streq(name, #iface_)) v->iface_ = min(version, v->iface_); else VERSION_UPDATE(ei_connection) else VERSION_UPDATE(ei_callback) else VERSION_UPDATE(ei_pingpong) else VERSION_UPDATE(ei_seat) else VERSION_UPDATE(ei_device) else VERSION_UPDATE(ei_pointer) else VERSION_UPDATE(ei_pointer_absolute) else VERSION_UPDATE(ei_scroll) else VERSION_UPDATE(ei_button) else VERSION_UPDATE(ei_keyboard) else VERSION_UPDATE(ei_touchscreen) #undef VERSION_UPDATE return NULL; } static void connected(struct ei_connection *connection, void *user_data) { struct ei *ei = ei_connection_get_context(connection); /* If we get here, the server didn't immediately disconnect us */ if (ei->state == EI_STATE_DISCONNECTED) return; ei_connected(ei); } static struct brei_result * handle_msg_connection(struct ei_handshake *setup, uint32_t serial, object_id_t id, uint32_t version) { struct ei *ei = ei_handshake_get_context(setup); assert(setup == ei->handshake); /* 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); /* 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); return NULL; } static const struct ei_handshake_interface interface = { .handshake_version = handle_msg_handshake_version, .interface_version = handle_msg_interface_version, .connection = handle_msg_connection, }; const struct ei_handshake_interface * ei_handshake_get_interface(struct ei_handshake *handshake) { return &interface; } struct ei_handshake * ei_handshake_new(struct ei *ei, uint32_t version) { struct ei_handshake *handshake = ei_handshake_create(&ei->object); handshake->proto_object.id = ei_get_new_id(ei); assert(handshake->proto_object.id == 0); /* Special object */ handshake->proto_object.implementation = handshake; handshake->proto_object.interface = &ei_handshake_proto_interface; handshake->proto_object.version = version; ei_register_object(ei, &handshake->proto_object); return handshake; /* ref owned by caller */ } libei-1.2.1/src/libei-handshake.h000066400000000000000000000042531456005336000165550ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_handshake; typedef void (*ei_handshake_func)(struct ei_handshake *handshake, void *handshake_data, uint32_t proto_data); /* This is a protocol-only object, not exposed in the API */ struct ei_handshake { struct object object; struct brei_object proto_object; void *user_data; /* Note: user-data is attached to the object */ }; OBJECT_DECLARE_GETTER(ei_handshake, context, struct ei*); OBJECT_DECLARE_GETTER(ei_handshake, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(ei_handshake, id, object_id_t); OBJECT_DECLARE_GETTER(ei_handshake, version, uint32_t); OBJECT_DECLARE_GETTER(ei_handshake, interface, const struct ei_handshake_interface *); OBJECT_DECLARE_GETTER(ei_handshake, user_data, void*); OBJECT_DECLARE_SETTER(ei_handshake, user_data, void*); OBJECT_DECLARE_REF(ei_handshake); OBJECT_DECLARE_UNREF(ei_handshake); struct ei_handshake * ei_handshake_new(struct ei *ei, uint32_t version); libei-1.2.1/src/libei-keyboard.c000066400000000000000000000054411456005336000164220ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 keyboard WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void ei_keyboard_destroy(struct ei_keyboard *keyboard) { struct ei *ei = ei_keyboard_get_context(keyboard); ei_unregister_object(ei, &keyboard->proto_object); } OBJECT_IMPLEMENT_REF(ei_keyboard); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_keyboard); static OBJECT_IMPLEMENT_CREATE(ei_keyboard); static OBJECT_IMPLEMENT_PARENT(ei_keyboard, ei_device); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_keyboard, proto_object, const struct brei_object*); struct ei_device * ei_keyboard_get_device(struct ei_keyboard *keyboard) { return ei_keyboard_parent(keyboard); } struct ei* ei_keyboard_get_context(struct ei_keyboard *keyboard) { return ei_device_get_context(ei_keyboard_get_device(keyboard)); } const struct ei_keyboard_interface * ei_keyboard_get_interface(struct ei_keyboard *keyboard) { struct ei_device *device = ei_keyboard_get_device(keyboard); return ei_device_get_keyboard_interface(device); } struct ei_keyboard * ei_keyboard_new(struct ei_device *device, object_id_t id, uint32_t version) { struct ei_keyboard *keyboard = ei_keyboard_create(&device->object); struct ei *ei = ei_device_get_context(device); keyboard->proto_object.id = id; keyboard->proto_object.implementation = keyboard; keyboard->proto_object.interface = &ei_keyboard_proto_interface; keyboard->proto_object.version = version; ei_register_object(ei, &keyboard->proto_object); return keyboard; /* ref owned by caller */ } libei-1.2.1/src/libei-keyboard.h000066400000000000000000000035741456005336000164340ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 keyboard WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_device; struct ei_keyboard; /* This is a protocol-only object, not exposed in the API */ struct ei_keyboard { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(ei_keyboard, context, struct ei*); OBJECT_DECLARE_GETTER(ei_keyboard, device, struct ei_device*); OBJECT_DECLARE_GETTER(ei_keyboard, proto_object, const struct brei_object*); OBJECT_DECLARE_GETTER(ei_keyboard, interface, const struct ei_keyboard_interface *); OBJECT_DECLARE_REF(ei_keyboard); OBJECT_DECLARE_UNREF(ei_keyboard); struct ei_keyboard * ei_keyboard_new(struct ei_device *device, object_id_t id, uint32_t version); libei-1.2.1/src/libei-log.c000066400000000000000000000145421456005336000154050ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include "util-macros.h" #include "util-color.h" #include "util-strings.h" #include "libei-private.h" struct ei_log_context { const char *file; const char *func; int line; }; _public_ OBJECT_IMPLEMENT_GETTER(ei_log_context, file, const char *); _public_ OBJECT_IMPLEMENT_GETTER(ei_log_context, func, const char *); _public_ OBJECT_IMPLEMENT_GETTER(ei_log_context, line, unsigned int); static void ei_default_log_handler(struct ei *ei, enum ei_log_priority priority, const char *message, struct ei_log_context *ctx) { struct lut { const char *color; const char *prefix; } lut[] = { { .color = ansi_colorcode[RED], .prefix = "", }, /* debug starts at 10 */ { .color = ansi_colorcode[HIGHLIGHT], .prefix = "DEBUG", }, { .color = ansi_colorcode[GREEN], .prefix = "INFO", }, { .color = ansi_colorcode[BLUE], .prefix = "WARN", }, { .color = ansi_colorcode[RED], .prefix = "ERROR", }, }; static time_t last_time = 0; const char *reset_code = ansi_colorcode[RESET]; run_only_once { if (!isatty(STDERR_FILENO)) { struct lut *l; ARRAY_FOR_EACH(lut, l) l->color = ""; reset_code = ""; } } time_t now = time(NULL); char timestamp[64]; if (last_time != now) { struct tm *tm = localtime(&now); strftime(timestamp, sizeof(timestamp), "%T", tm); } else { xsnprintf(timestamp, sizeof(timestamp), "..."); } size_t idx = priority/10; assert(idx < ARRAY_LENGTH(lut)); fprintf(stderr, " %8s | %s%4s%s | %s\n", timestamp, lut[idx].color, lut[idx].prefix, reset_code, message); last_time = now; } _public_ void ei_log_set_handler(struct ei *ei, ei_log_handler log_handler) { ei->log.handler = log_handler ? log_handler : ei_default_log_handler; } _public_ void ei_log_set_priority(struct ei *ei, enum ei_log_priority priority) { switch (priority) { case EI_LOG_PRIORITY_DEBUG: case EI_LOG_PRIORITY_INFO: case EI_LOG_PRIORITY_WARNING: case EI_LOG_PRIORITY_ERROR: break; default: abort(); } ei->log.priority = priority; } _public_ enum ei_log_priority ei_log_get_priority(const struct ei *ei) { return ei->log.priority; } void ei_log_msg(struct ei *ei, enum ei_log_priority priority, const char *file, int lineno, const char *func, const char *format, ...) { va_list args; va_start(args, format); ei_log_msg_va(ei, priority, file, lineno, func, format, args); va_end(args); } void ei_log_msg_va(struct ei *ei, enum ei_log_priority priority, const char *file, int lineno, const char *func, const char *format, va_list ap) { if (ei->log.priority > priority || !ei->log.handler) return; _cleanup_free_ char *message = xvaprintf(format, ap); struct ei_log_context ctx = { .file = file, .line = lineno, .func = func, }; ei->log.handler(ei, priority, message, &ctx); } #ifdef _enable_tests_ #include "util-munit.h" struct log_handler_check { enum ei_log_priority min_priority; const char *expected_message; }; static void test_loghandler(struct ei *ei, enum ei_log_priority priority, const char *message, struct ei_log_context *ctx) { struct log_handler_check *check = ei_get_user_data(ei); munit_assert_int(priority, >=, check->min_priority); munit_assert_string_equal(message, check->expected_message); /* Second arg is the line number, if this code ever moves further up, * this test may fail */ munit_assert_int(ei_log_context_get_line(ctx), >, 170); munit_assert_true(strendswith(ei_log_context_get_file(ctx), "libei-log.c")); munit_assert_string_equal(ei_log_context_get_func(ctx), "test_log_handler"); } MUNIT_TEST(test_log_handler) { struct log_handler_check check = {0}; struct ei *ei = ei_new(&check); munit_assert_ptr_equal(ei->log.handler, ei_default_log_handler); munit_assert_int(ei->log.priority, ==, EI_LOG_PRIORITY_INFO); ei_log_set_handler(ei, test_loghandler); check.min_priority = EI_LOG_PRIORITY_INFO; /* default */ check.expected_message = "info message"; log_debug(ei, "default is below this level"); log_info(ei, "info message"); check.expected_message = "error message"; log_error(ei, "error message"); check.min_priority = EI_LOG_PRIORITY_ERROR; ei_log_set_priority(ei, EI_LOG_PRIORITY_ERROR); munit_assert_int(ei->log.priority, ==, EI_LOG_PRIORITY_ERROR); log_error(ei, "error message"); log_warn(ei, "warn message"); log_info(ei, "info message"); log_debug(ei, "debug message"); ei_log_set_priority(ei, EI_LOG_PRIORITY_DEBUG); /* Make sure they come through at the right priority */ check.min_priority = EI_LOG_PRIORITY_ERROR; check.expected_message = "error message"; log_error(ei, "error message"); check.min_priority = EI_LOG_PRIORITY_WARNING; check.expected_message = "warn message"; log_warn(ei, "warn message"); check.min_priority = EI_LOG_PRIORITY_INFO; check.expected_message = "info message"; log_info(ei, "info message"); check.min_priority = EI_LOG_PRIORITY_DEBUG; check.expected_message = "debug message"; log_debug(ei, "debug message"); /* Can't capture this easily, so this is mostly just a crash test */ ei_log_set_handler(ei, NULL); munit_assert_ptr_equal(ei->log.handler, ei_default_log_handler); log_debug(ei, "ignore this, testing NULL log handler"); ei_unref(ei); return MUNIT_OK; } #endif libei-1.2.1/src/libei-pingpong.c000066400000000000000000000055071456005336000164460ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void ei_pingpong_destroy(struct ei_pingpong *pingpong) { struct ei *ei = ei_pingpong_get_context(pingpong); ei_unregister_object(ei, &pingpong->proto_object); } OBJECT_IMPLEMENT_REF(ei_pingpong); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_pingpong); OBJECT_IMPLEMENT_GETTER(ei_pingpong, user_data, void*); OBJECT_IMPLEMENT_SETTER(ei_pingpong, user_data, void*); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_pingpong, proto_object, const struct brei_object*); static OBJECT_IMPLEMENT_CREATE(ei_pingpong); static OBJECT_IMPLEMENT_PARENT(ei_pingpong, ei); struct ei* ei_pingpong_get_context(struct ei_pingpong *pingpong) { assert(pingpong); return ei_pingpong_parent(pingpong); } object_id_t ei_pingpong_get_id(struct ei_pingpong *pingpong) { return pingpong->proto_object.id; } static const struct ei_pingpong_interface interface = { }; const struct ei_pingpong_interface * ei_pingpong_get_interface(struct ei_pingpong *pingpong) { return &interface; } struct ei_pingpong * ei_pingpong_new_for_id(struct ei *ei, object_id_t id, uint32_t version) { struct ei_pingpong *pingpong = ei_pingpong_create(&ei->object); pingpong->proto_object.id = id; pingpong->proto_object.implementation = pingpong; pingpong->proto_object.interface = &ei_pingpong_proto_interface; pingpong->proto_object.version = version; /* FIXME */ ei_register_object(ei, &pingpong->proto_object); list_init(&pingpong->link); return pingpong; /* ref owned by caller */ } libei-1.2.1/src/libei-pingpong.h000066400000000000000000000042311456005336000164440ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_pingpong; /* This is a protocol-only object, not exposed in the API */ struct ei_pingpong { struct object object; struct brei_object proto_object; void *user_data; /* Note: user-data is attached to the object */ struct list link; /* for use by the callers, if needed */ void *pingpong_data; /* Note: pingpong-data is attached to the pingpong */ }; OBJECT_DECLARE_GETTER(ei_pingpong, context, struct ei*); OBJECT_DECLARE_GETTER(ei_pingpong, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(ei_pingpong, id, object_id_t); OBJECT_DECLARE_GETTER(ei_pingpong, interface, const struct ei_pingpong_interface *); OBJECT_DECLARE_GETTER(ei_pingpong, user_data, void *); OBJECT_DECLARE_SETTER(ei_pingpong, user_data, void *); OBJECT_DECLARE_REF(ei_pingpong); OBJECT_DECLARE_UNREF(ei_pingpong); struct ei_pingpong * ei_pingpong_new_for_id(struct ei *ei, object_id_t id, uint32_t version); libei-1.2.1/src/libei-pointer-absolute.c000066400000000000000000000061611456005336000201160ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void ei_pointer_absolute_destroy(struct ei_pointer_absolute *pointer_absolute) { struct ei *ei = ei_pointer_absolute_get_context(pointer_absolute); ei_unregister_object(ei, &pointer_absolute->proto_object); } OBJECT_IMPLEMENT_REF(ei_pointer_absolute); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_pointer_absolute); static OBJECT_IMPLEMENT_CREATE(ei_pointer_absolute); static OBJECT_IMPLEMENT_PARENT(ei_pointer_absolute, ei_device); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_pointer_absolute, proto_object, const struct brei_object*); struct ei_device * ei_pointer_absolute_get_device(struct ei_pointer_absolute *pointer_absolute) { return ei_pointer_absolute_parent(pointer_absolute); } struct ei* ei_pointer_absolute_get_context(struct ei_pointer_absolute *pointer_absolute) { return ei_device_get_context(ei_pointer_absolute_get_device(pointer_absolute)); } const struct ei_pointer_absolute_interface * ei_pointer_absolute_get_interface(struct ei_pointer_absolute *pointer_absolute) { struct ei_device *device = ei_pointer_absolute_get_device(pointer_absolute); return ei_device_get_pointer_absolute_interface(device); } struct ei_pointer_absolute * ei_pointer_absolute_new(struct ei_device *device, object_id_t id, uint32_t version) { struct ei_pointer_absolute *pointer_absolute = ei_pointer_absolute_create(&device->object); struct ei *ei = ei_device_get_context(device); pointer_absolute->proto_object.id = id; pointer_absolute->proto_object.implementation = pointer_absolute; pointer_absolute->proto_object.interface = &ei_pointer_absolute_proto_interface; pointer_absolute->proto_object.version = version; ei_register_object(ei, &pointer_absolute->proto_object); return pointer_absolute; /* ref owned by caller */ } libei-1.2.1/src/libei-pointer-absolute.h000066400000000000000000000037341456005336000201260ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_device; struct ei_pointer_absolute; /* This is a protocol-only object, not exposed in the API */ struct ei_pointer_absolute { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(ei_pointer_absolute, context, struct ei*); OBJECT_DECLARE_GETTER(ei_pointer_absolute, device, struct ei_device*); OBJECT_DECLARE_GETTER(ei_pointer_absolute, proto_object, const struct brei_object*); OBJECT_DECLARE_GETTER(ei_pointer_absolute, interface, const struct ei_pointer_absolute_interface *); OBJECT_DECLARE_REF(ei_pointer_absolute); OBJECT_DECLARE_UNREF(ei_pointer_absolute); struct ei_pointer_absolute * ei_pointer_absolute_new(struct ei_device *device, object_id_t id, uint32_t version); libei-1.2.1/src/libei-pointer.c000066400000000000000000000053671456005336000163110ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 pointer WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void ei_pointer_destroy(struct ei_pointer *pointer) { struct ei *ei = ei_pointer_get_context(pointer); ei_unregister_object(ei, &pointer->proto_object); } OBJECT_IMPLEMENT_REF(ei_pointer); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_pointer); static OBJECT_IMPLEMENT_CREATE(ei_pointer); static OBJECT_IMPLEMENT_PARENT(ei_pointer, ei_device); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_pointer, proto_object, const struct brei_object*); struct ei_device * ei_pointer_get_device(struct ei_pointer *pointer) { return ei_pointer_parent(pointer); } struct ei* ei_pointer_get_context(struct ei_pointer *pointer) { return ei_device_get_context(ei_pointer_get_device(pointer)); } const struct ei_pointer_interface * ei_pointer_get_interface(struct ei_pointer *pointer) { struct ei_device *device = ei_pointer_get_device(pointer); return ei_device_get_pointer_interface(device); } struct ei_pointer * ei_pointer_new(struct ei_device *device, object_id_t id, uint32_t version) { struct ei_pointer *pointer = ei_pointer_create(&device->object); struct ei *ei = ei_device_get_context(device); pointer->proto_object.id = id; pointer->proto_object.implementation = pointer; pointer->proto_object.interface = &ei_pointer_proto_interface; pointer->proto_object.version = version; ei_register_object(ei, &pointer->proto_object); return pointer; /* ref owned by caller */ } libei-1.2.1/src/libei-pointer.h000066400000000000000000000035601456005336000163070ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 pointer WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_device; struct ei_pointer; /* This is a protocol-only object, not exposed in the API */ struct ei_pointer { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(ei_pointer, context, struct ei*); OBJECT_DECLARE_GETTER(ei_pointer, device, struct ei_device*); OBJECT_DECLARE_GETTER(ei_pointer, proto_object, const struct brei_object*); OBJECT_DECLARE_GETTER(ei_pointer, interface, const struct ei_pointer_interface *); OBJECT_DECLARE_REF(ei_pointer); OBJECT_DECLARE_UNREF(ei_pointer); struct ei_pointer * ei_pointer_new(struct ei_device *device, object_id_t id, uint32_t version); libei-1.2.1/src/libei-private.h000066400000000000000000000163221456005336000163010ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include "util-macros.h" #include "util-object.h" #include "libei.h" #include "brei-shared.h" #include "util-list.h" #include "util-sources.h" #include "util-structs.h" #include "libei-button.h" #include "libei-callback.h" #include "libei-connection.h" #include "libei-device.h" #include "libei-event.h" #include "libei-handshake.h" #include "libei-keyboard.h" #include "libei-pingpong.h" #include "libei-pointer-absolute.h" #include "libei-pointer.h" #include "libei-region.h" #include "libei-scroll.h" #include "libei-seat.h" #include "libei-touchscreen.h" struct ei_backend_interface { void (*destroy)(struct ei *ei, void *backend); }; enum ei_state { EI_STATE_NEW, /* No backend yet */ EI_STATE_BACKEND, /* We have a backend */ EI_STATE_CONNECTING, /* client requested connect */ EI_STATE_CONNECTED, /* server has sent connect */ EI_STATE_DISCONNECTING, /* in the process of cleaning up */ EI_STATE_DISCONNECTED, }; struct ei_interface_versions { uint32_t ei_connection; uint32_t ei_handshake; uint32_t ei_callback; uint32_t ei_pingpong; uint32_t ei_seat; uint32_t ei_device; uint32_t ei_pointer; uint32_t ei_pointer_absolute; uint32_t ei_scroll; uint32_t ei_button; uint32_t ei_keyboard; 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; struct ei_connection *connection; 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; void *user_data; 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; struct list event_queue; struct list seats; char *name; struct { ei_log_handler handler; enum ei_log_priority priority; } log; ei_clock_now_func clock_now; bool is_sender; }; const struct ei_connection_interface * 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); struct ei_connection * ei_get_connection(struct ei *ei); object_id_t ei_get_new_id(struct ei *ei); void ei_update_serial(struct ei *ei, uint32_t serial); uint32_t ei_get_serial(struct ei *ei); void ei_register_object(struct ei *ei, struct brei_object *object); void ei_unregister_object(struct ei *ei, struct brei_object *object); 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); void ei_queue_connect_event(struct ei *ei); void ei_queue_disconnect_event(struct ei *ei); void ei_queue_device_removed_event(struct ei_device *device); void ei_queue_device_added_event(struct ei_device *device); void ei_queue_device_resumed_event(struct ei_device *device); void ei_queue_device_paused_event(struct ei_device *device); void ei_queue_seat_added_event(struct ei_seat *seat); void ei_queue_seat_removed_event(struct ei_seat *seat); void ei_queue_device_start_emulating_event(struct ei_device *device, uint32_t sequence); void ei_queue_device_stop_emulating_event(struct ei_device *device); void ei_queue_frame_event(struct ei_device *device, uint64_t time); void ei_queue_pointer_rel_event(struct ei_device *device, double x, double y); void ei_queue_pointer_abs_event(struct ei_device *device, double x, double y); void ei_queue_pointer_button_event(struct ei_device *device, uint32_t button, bool is_press); void ei_queue_keyboard_key_event(struct ei_device *device, uint32_t key, bool is_press); void ei_queue_keyboard_modifiers_event(struct ei_device *device, const struct ei_xkb_modifiers *mods); void ei_queue_pointer_scroll_event(struct ei_device *device, double x, double y); void ei_queue_pointer_scroll_discrete_event(struct ei_device *device, int32_t x, int32_t y); void ei_queue_pointer_scroll_stop_event(struct ei_device *device, bool x, bool y); void ei_queue_pointer_scroll_cancel_event(struct ei_device *device, bool x, bool y); void ei_queue_touch_down_event(struct ei_device *device, uint32_t touchid, double x, double y); void ei_queue_touch_motion_event(struct ei_device *device, uint32_t touchid, double x, double y); void ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid); _printf_(6, 7) void ei_log_msg(struct ei *ei, enum ei_log_priority priority, const char *file, int lineno, const char *func, const char *format, ...); _printf_(6, 0) void ei_log_msg_va(struct ei *ei, enum ei_log_priority priority, const char *file, int lineno, const char *func, const char *format, va_list args); #define log_debug(T_, ...) \ ei_log_msg((T_), EI_LOG_PRIORITY_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_info(T_, ...) \ ei_log_msg((T_), EI_LOG_PRIORITY_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_warn(T_, ...) \ ei_log_msg((T_), EI_LOG_PRIORITY_WARNING, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_error(T_, ...) \ ei_log_msg((T_), EI_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_bug(T_, ...) \ 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) libei-1.2.1/src/libei-region.c000066400000000000000000000116731456005336000161110ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-strings.h" #include "libei-private.h" static void ei_region_destroy(struct ei_region *region) { free(region->mapping_id); list_remove(®ion->link); } _public_ OBJECT_IMPLEMENT_REF(ei_region); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_region); static OBJECT_IMPLEMENT_CREATE(ei_region); _public_ OBJECT_IMPLEMENT_GETTER(ei_region, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(ei_region, user_data, void *); _public_ OBJECT_IMPLEMENT_GETTER(ei_region, x, uint32_t); _public_ OBJECT_IMPLEMENT_GETTER(ei_region, y, uint32_t); _public_ OBJECT_IMPLEMENT_GETTER(ei_region, width, uint32_t); _public_ 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) { struct ei_region *region = ei_region_create(NULL); region->physical_scale = 1.0; list_init(®ion->link); return region; } void ei_region_set_offset(struct ei_region *region, uint32_t x, uint32_t y) { region->x = x; region->y = y; } void ei_region_set_size(struct ei_region *region, uint32_t w, uint32_t h) { region->width = w; 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) { return (x >= r->x && x < r->x + r->width && y >= r->y && y < r->y + r->height); } _public_ bool ei_region_convert_point(struct ei_region *r, double *x, double *y) { if (ei_region_contains(r, *x, *y)) { *x -= r->x; *y -= r->y; return true; } 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 libei-1.2.1/src/libei-region.h000066400000000000000000000033521456005336000161110ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-object.h" #include "util-list.h" struct ei_region { struct object object; void *user_data; struct list link; uint32_t x, y; uint32_t width, height; double physical_scale; char *mapping_id; }; struct ei_region * ei_region_new(void); void ei_region_set_size(struct ei_region *region, uint32_t w, uint32_t h); void 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); libei-1.2.1/src/libei-scroll.c000066400000000000000000000053151456005336000161200ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 scroll WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void ei_scroll_destroy(struct ei_scroll *scroll) { struct ei *ei = ei_scroll_get_context(scroll); ei_unregister_object(ei, &scroll->proto_object); } OBJECT_IMPLEMENT_REF(ei_scroll); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_scroll); static OBJECT_IMPLEMENT_CREATE(ei_scroll); static OBJECT_IMPLEMENT_PARENT(ei_scroll, ei_device); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_scroll, proto_object, const struct brei_object*); struct ei_device * ei_scroll_get_device(struct ei_scroll *scroll) { return ei_scroll_parent(scroll); } struct ei* ei_scroll_get_context(struct ei_scroll *scroll) { return ei_device_get_context(ei_scroll_get_device(scroll)); } const struct ei_scroll_interface * ei_scroll_get_interface(struct ei_scroll *scroll) { struct ei_device *device = ei_scroll_get_device(scroll); return ei_device_get_scroll_interface(device); } struct ei_scroll * ei_scroll_new(struct ei_device *device, object_id_t id, uint32_t version) { struct ei_scroll *scroll = ei_scroll_create(&device->object); struct ei *ei = ei_device_get_context(device); scroll->proto_object.id = id; scroll->proto_object.implementation = scroll; scroll->proto_object.interface = &ei_scroll_proto_interface; scroll->proto_object.version = version; ei_register_object(ei, &scroll->proto_object); return scroll; /* ref owned by caller */ } libei-1.2.1/src/libei-scroll.h000066400000000000000000000035441456005336000161270ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 scroll WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_device; struct ei_scroll; /* This is a protocol-only object, not exposed in the API */ struct ei_scroll { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(ei_scroll, context, struct ei*); OBJECT_DECLARE_GETTER(ei_scroll, device, struct ei_device*); OBJECT_DECLARE_GETTER(ei_scroll, proto_object, const struct brei_object*); OBJECT_DECLARE_GETTER(ei_scroll, interface, const struct ei_scroll_interface *); OBJECT_DECLARE_REF(ei_scroll); OBJECT_DECLARE_UNREF(ei_scroll); struct ei_scroll * ei_scroll_new(struct ei_device *device, object_id_t id, uint32_t version); libei-1.2.1/src/libei-seat.c000066400000000000000000000230721456005336000155560ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "libei-private.h" #include "ei-proto.h" static void ei_seat_destroy(struct ei_seat *seat) { free(seat->name); } _public_ OBJECT_IMPLEMENT_REF(ei_seat); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_seat); static OBJECT_IMPLEMENT_CREATE(ei_seat); static OBJECT_IMPLEMENT_PARENT(ei_seat, ei); _public_ OBJECT_IMPLEMENT_GETTER(ei_seat, name, const char *); _public_ OBJECT_IMPLEMENT_SETTER(ei_seat, user_data, void *); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_seat, proto_object, const struct brei_object*); _public_ struct ei* ei_seat_get_context(struct ei_seat *seat) { assert(seat); return ei_seat_parent(seat); } object_id_t ei_seat_get_id(struct ei_seat *seat) { return seat->proto_object.id; } static struct brei_result * handle_msg_destroyed(struct ei_seat *seat, uint32_t serial) { struct ei *ei = ei_seat_get_context(seat); ei_update_serial(ei, serial); log_debug(ei, "server removed seat %s", seat->name); ei_seat_remove(seat); return NULL; } static struct brei_result * handle_msg_name(struct ei_seat *seat, const char * name) { if (seat->name != NULL) return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "EIS sent the seat name twice"); seat->name = xstrdup(name); return 0; } static struct brei_result * handle_msg_capability(struct ei_seat *seat, uint64_t mask, const char *interface) { struct ei *ei = ei_seat_get_context(seat); assert(ARRAY_LENGTH(EI_INTERFACE_NAMES) == ARRAY_LENGTH(seat->capabilities.map)); for (size_t i = 0; i < ARRAY_LENGTH(EI_INTERFACE_NAMES); i++) { if (streq(EI_INTERFACE_NAMES[i], interface)) { if (seat->capabilities.map[i] != 0) return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "EIS sent the seat capabilities for %s twice", interface); log_debug(ei, "seat %#" PRIx64" has cap %s as %#" PRIx64, ei_seat_get_id(seat), interface, mask); seat->capabilities.map[i] = mask; return 0; } } /* EIS must not send anything we didn't announce as supported */ return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "EIS sent an unsupported interface %s", interface); } static struct brei_result * handle_msg_done(struct ei_seat *seat) { struct ei *ei = ei_seat_get_context(seat); seat->state = EI_SEAT_STATE_DONE; log_debug(ei, "Added seat '%s'", seat->name); ei_queue_seat_added_event(seat); return NULL; } #define DISCONNECT_IF_INVALID_ID(seat_, id_) do { \ if (!brei_is_server_id(id_)) { \ struct ei *ei_ = ei_seat_get_context(seat_); \ log_bug(ei_, "Received invalid object id %#" PRIx64 ". Disconnecting", id_); \ return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Received invalid object id %#" PRIx64 ".", id_); \ } \ } while(0) static struct brei_result * handle_msg_device(struct ei_seat *seat, object_id_t id, uint32_t version) { DISCONNECT_IF_INVALID_ID(seat, id); 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); /* this list "owns" the ref for this device */ list_append(&seat->devices, &device->link); return NULL; } static const struct ei_seat_interface interface = { .destroyed = handle_msg_destroyed, .name = handle_msg_name, .capability = handle_msg_capability, .done = handle_msg_done, .device = handle_msg_device, }; const struct ei_seat_interface * ei_seat_get_interface(struct ei_seat *seat) { return &interface; } struct ei_seat * ei_seat_new(struct ei *ei, object_id_t id, uint32_t version) { struct ei_seat *seat = ei_seat_create(&ei->object); seat->proto_object.id = id; seat->proto_object.implementation = seat; seat->proto_object.interface = &ei_seat_proto_interface; seat->proto_object.version = version; ei_register_object(ei, &seat->proto_object); seat->state = EI_SEAT_STATE_NEW; seat->capabilities.bound = 0; list_init(&seat->devices); list_init(&seat->devices_removed); list_init(&seat->link); return seat; /* ref owned by caller */ } void ei_seat_remove(struct ei_seat *seat) { /* Sigh, this is terrible and needs to be fixed: * if our fd is broken, trying to send any event causes an ei_disconnect(), * which eventually calls in here. So we need to guard this function * against nested callers. */ if (seat->state == EI_SEAT_STATE_REMOVED) return; struct ei_device *d; /* If the server disconnects us before processing a new device, we * need to clean this up in the library */ list_for_each_safe(d, &seat->devices, link) { /* remove the device */ ei_device_close(d); /* And pretend to process the removed message from * the server */ ei_device_removed_by_server(d); } /* Check the seat state again, because the above device removal may * have triggered ei_disconnect() */ if (seat->state != EI_SEAT_STATE_REMOVED) { seat->state = EI_SEAT_STATE_REMOVED; list_remove(&seat->link); list_init(&seat->link); ei_queue_seat_removed_event(seat); struct ei *ei = ei_seat_get_context(seat); ei_unregister_object(ei, &seat->proto_object); ei_seat_unref(seat); } } _public_ bool ei_seat_has_capability(struct ei_seat *seat, enum ei_device_capability cap) { switch (cap) { case EI_DEVICE_CAP_POINTER: /* 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; case EI_DEVICE_CAP_POINTER_ABSOLUTE: return seat->capabilities.map[EI_POINTER_ABSOLUTE_INTERFACE_INDEX] != 0; case EI_DEVICE_CAP_KEYBOARD: return seat->capabilities.map[EI_KEYBOARD_INTERFACE_INDEX] != 0; case EI_DEVICE_CAP_TOUCH: return seat->capabilities.map[EI_TOUCHSCREEN_INTERFACE_INDEX] != 0; case EI_DEVICE_CAP_SCROLL: return seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX] != 0; case EI_DEVICE_CAP_BUTTON: return seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX] != 0; } return false; } static int ei_seat_send_bind(struct ei_seat *seat, uint64_t capabilities) { struct ei *ei = ei_seat_get_context(seat); if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED) return 0; int rc = ei_seat_request_bind(seat, capabilities); if (rc) ei_disconnect(ei); return rc; } static uint64_t ei_seat_cap_mask(struct ei_seat *seat, enum ei_device_capability cap) { switch (cap) { case EI_DEVICE_CAP_POINTER_ABSOLUTE: return seat->capabilities.map[EI_POINTER_ABSOLUTE_INTERFACE_INDEX]; case EI_DEVICE_CAP_POINTER: return seat->capabilities.map[EI_POINTER_INTERFACE_INDEX]; case EI_DEVICE_CAP_KEYBOARD: return seat->capabilities.map[EI_KEYBOARD_INTERFACE_INDEX]; case EI_DEVICE_CAP_TOUCH: return seat->capabilities.map[EI_TOUCHSCREEN_INTERFACE_INDEX]; case EI_DEVICE_CAP_BUTTON: return seat->capabilities.map[EI_BUTTON_INTERFACE_INDEX]; case EI_DEVICE_CAP_SCROLL: return seat->capabilities.map[EI_SCROLL_INTERFACE_INDEX]; } return 0; } _public_ void ei_seat_bind_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 = seat->capabilities.bound; 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)); } if (seat->capabilities.bound == mask) return; seat->capabilities.bound = mask; ei_seat_send_bind(seat, seat->capabilities.bound); } _public_ void ei_seat_unbind_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 = seat->capabilities.bound; enum ei_device_capability cap; va_list args; va_start(args, seat); while ((cap = va_arg(args, enum ei_device_capability)) > 0) { mask_remove(mask, ei_seat_cap_mask(seat, cap)); } if (seat->capabilities.bound == mask) return; seat->capabilities.bound = mask; if (seat->capabilities.bound == 0) { struct ei_device *device; list_for_each(device, &seat->devices, link) { if (ei_device_has_capability(device, cap)) ei_device_close(device); } } ei_seat_send_bind(seat, seat->capabilities.bound); } libei-1.2.1/src/libei-seat.h000066400000000000000000000043031456005336000155570ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-object.h" #include "util-list.h" #include "brei-shared.h" #include "ei-proto.h" struct ei; enum ei_seat_state { EI_SEAT_STATE_NEW, EI_SEAT_STATE_DONE, EI_SEAT_STATE_REMOVED, }; struct ei_seat { struct object object; void *user_data; struct brei_object proto_object; struct list link; enum ei_seat_state state; struct list devices; struct list devices_removed; /* removed from seat but client still has a ref */ struct { /* Maps the interface as index to the capability bitmask on the protocol */ uint64_t map[EI_INTERFACE_COUNT]; /* A bitmask of the bitmask the client bound last */ uint64_t bound; } capabilities; char *name; }; OBJECT_DECLARE_GETTER(ei_seat, id, object_id_t); OBJECT_DECLARE_GETTER(ei_seat, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(ei_seat, interface, const struct ei_seat_interface *); struct ei_seat * ei_seat_new(struct ei *ei, object_id_t id, uint32_t version); void ei_seat_remove(struct ei_seat *seat); void ei_seat_drop(struct ei_seat *seat); libei-1.2.1/src/libei-socket.c000066400000000000000000000051211456005336000161050ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include #include "util-mem.h" #include "util-macros.h" #include "util-io.h" #include "util-sources.h" #include "util-strings.h" #include "util-object.h" #include "libei.h" #include "libei-private.h" struct ei_socket { struct object object; }; static inline void ei_socket_destroy(struct ei_socket *socket) { } static OBJECT_IMPLEMENT_CREATE(ei_socket); static OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_socket); static void interface_socket_destroy(struct ei *ei, void *backend) { struct ei_socket *socket = backend; ei_socket_unref(socket); } static const struct ei_backend_interface interface = { .destroy = interface_socket_destroy, }; _public_ int ei_setup_backend_socket(struct ei *ei, const char *socketpath) { assert(ei); assert(!ei->backend); struct ei_socket *ei_socket = ei_socket_create(&ei->object); ei->backend = ei_socket; ei->backend_interface = interface; if (!socketpath) socketpath = getenv("LIBEI_SOCKET"); if (!socketpath || socketpath[0] == '\0') return -ENOENT; _cleanup_free_ char *path = NULL; if (socketpath[0] == '/') { path = xstrdup(socketpath); } else { const char *xdg = getenv("XDG_RUNTIME_DIR"); if (!xdg) return -ENOTDIR; path = xaprintf("%s/%s", xdg, socketpath); } int sockfd = xconnect(path); if (sockfd < 0) return sockfd; return ei_set_socket(ei, sockfd); } libei-1.2.1/src/libei-touchscreen.c000066400000000000000000000056371456005336000171530ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 touchscreen WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libei-private.h" #include "ei-proto.h" static void ei_touchscreen_destroy(struct ei_touchscreen *touchscreen) { struct ei *ei = ei_touchscreen_get_context(touchscreen); ei_unregister_object(ei, &touchscreen->proto_object); } OBJECT_IMPLEMENT_REF(ei_touchscreen); OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_touchscreen); static OBJECT_IMPLEMENT_CREATE(ei_touchscreen); static OBJECT_IMPLEMENT_PARENT(ei_touchscreen, ei_device); OBJECT_IMPLEMENT_GETTER_AS_REF(ei_touchscreen, proto_object, const struct brei_object*); struct ei_device * ei_touchscreen_get_device(struct ei_touchscreen *touchscreen) { return ei_touchscreen_parent(touchscreen); } struct ei* ei_touchscreen_get_context(struct ei_touchscreen *touchscreen) { return ei_device_get_context(ei_touchscreen_get_device(touchscreen)); } const struct ei_touchscreen_interface * ei_touchscreen_get_interface(struct ei_touchscreen *touchscreen) { struct ei_device *device = ei_touchscreen_get_device(touchscreen); return ei_device_get_touchscreen_interface(device); } struct ei_touchscreen * ei_touchscreen_new(struct ei_device *device, object_id_t id, uint32_t version) { struct ei_touchscreen *touchscreen = ei_touchscreen_create(&device->object); struct ei *ei = ei_device_get_context(device); touchscreen->proto_object.id = id; touchscreen->proto_object.implementation = touchscreen; touchscreen->proto_object.interface = &ei_touchscreen_proto_interface; touchscreen->proto_object.version = version; ei_register_object(ei, &touchscreen->proto_object); return touchscreen; /* ref owned by caller */ } libei-1.2.1/src/libei-touchscreen.h000066400000000000000000000036321456005336000171510ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 touch WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "util-list.h" #include "brei-shared.h" struct ei; struct ei_device; struct ei_touchscreen; /* This is a protocol-only object, not exposed in the API */ struct ei_touchscreen { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(ei_touchscreen, context, struct ei*); OBJECT_DECLARE_GETTER(ei_touchscreen, device, struct ei_device*); OBJECT_DECLARE_GETTER(ei_touchscreen, proto_object, const struct brei_object*); OBJECT_DECLARE_GETTER(ei_touchscreen, interface, const struct ei_touchscreen_interface *); OBJECT_DECLARE_REF(ei_touchscreen); OBJECT_DECLARE_UNREF(ei_touchscreen); struct ei_touchscreen * ei_touchscreen_new(struct ei_device *device, object_id_t id, uint32_t version); libei-1.2.1/src/libei.c000066400000000000000000000577751456005336000146450ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include #include #include #include "util-io.h" #include "util-macros.h" #include "util-object.h" #include "util-sources.h" #include "util-strings.h" #include "util-time.h" #include "util-version.h" #include "libei.h" #include "libei-private.h" #include "brei-shared.h" #include "ei-proto.h" _Static_assert(sizeof(enum ei_device_capability) == sizeof(int), "Invalid enum size"); _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) { ei_disconnect(ei); struct ei_event *e; 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 OBJECT_IMPLEMENT_CREATE(ei); _public_ OBJECT_IMPLEMENT_REF(ei); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(ei); _public_ OBJECT_IMPLEMENT_SETTER(ei, user_data, void *); _public_ OBJECT_IMPLEMENT_GETTER(ei, user_data, void *); OBJECT_IMPLEMENT_GETTER(ei, connection, struct ei_connection *); OBJECT_IMPLEMENT_GETTER(ei, serial, uint32_t); DEFINE_UNREF_CLEANUP_FUNC(brei_result); DEFINE_UNREF_CLEANUP_FUNC(ei_pingpong); struct ei * ei_get_context(struct ei *ei) { return ei; /* for the protocol bindings */ } static struct ei * ei_create_context(bool is_sender, void *user_data) { _unref_(ei) *ei = ei_create(NULL); ei->state = EI_STATE_NEW; 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(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), }; /* This must be v1 until the server tells us otherwise */ ei->handshake = ei_handshake_new(ei, VERSION_V(1)); ei->next_object_id = 1; ei->brei = brei_context_new(ei); brei_context_set_log_context(ei->brei, ei); brei_context_set_log_func(ei->brei, (brei_logfunc_t)ei_log_msg_va); ei_log_set_handler(ei, NULL); ei_log_set_priority(ei, EI_LOG_PRIORITY_INFO); ei->sink = sink_new(); if (!ei->sink) return NULL; ei->user_data = user_data; ei->backend = NULL; ei->is_sender = is_sender; return steal(&ei); } object_id_t ei_get_new_id(struct ei *ei) { static const uint64_t server_range = 0xff00000000000000; return ei->next_object_id++ & ~server_range; } void ei_update_serial(struct ei *ei, uint32_t serial) { ei->serial = 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); 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); 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 ei_is_sender(struct ei *ei) { return ei->is_sender; } _public_ struct ei * ei_new(void *user_data) { return ei_new_sender(user_data); } _public_ struct ei * ei_new_sender(void *user_data) { return ei_create_context(true, user_data); } _public_ struct ei * ei_new_receiver(void *user_data) { return ei_create_context(false, user_data); } _public_ int ei_get_fd(struct ei *ei) { return sink_get_fd(ei->sink); } _public_ void ei_dispatch(struct ei *ei) { sink_dispatch(ei->sink); } static void update_event_timestamp(struct ei_event *event, uint64_t time) { switch (event->type) { case EI_EVENT_POINTER_MOTION: case EI_EVENT_POINTER_MOTION_ABSOLUTE: case EI_EVENT_BUTTON_BUTTON: case EI_EVENT_SCROLL_DELTA: case EI_EVENT_SCROLL_STOP: case EI_EVENT_SCROLL_CANCEL: case EI_EVENT_SCROLL_DISCRETE: case EI_EVENT_KEYBOARD_KEY: case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_UP: case EI_EVENT_TOUCH_MOTION: if (event->timestamp != 0) { log_bug(ei_event_get_context(event), "Unexpected timestamp for event of type: %s", ei_event_type_to_string(event->type)); return; } event->timestamp = time; break; default: log_bug(ei_event_get_context(event), "Unexpected event %s in pending queue event", ei_event_type_to_string(event->type)); return; } } static void queue_event(struct ei *ei, struct ei_event *event) { struct ei_device *device = ei_event_get_device(event); struct list *queue = &ei->event_queue; const char *prefix = ""; switch (event->type) { case EI_EVENT_POINTER_MOTION: case EI_EVENT_POINTER_MOTION_ABSOLUTE: case EI_EVENT_BUTTON_BUTTON: case EI_EVENT_SCROLL_DELTA: case EI_EVENT_SCROLL_STOP: case EI_EVENT_SCROLL_CANCEL: case EI_EVENT_SCROLL_DISCRETE: case EI_EVENT_KEYBOARD_KEY: case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_UP: case EI_EVENT_TOUCH_MOTION: prefix = "pending "; queue = &device->pending_event_queue; break; case EI_EVENT_FRAME: /* silently discard empty frames */ if (list_empty(&device->pending_event_queue)) return; struct ei_event *pending; list_for_each_safe(pending, &device->pending_event_queue, link) { update_event_timestamp(pending, event->timestamp); list_remove(&pending->link); list_append(&ei->event_queue, &pending->link); } break; default: if (device && !list_empty(&device->pending_event_queue)) ei_queue_frame_event(device, ei_now(ei)); break; } log_debug(ei, "queuing %sevent type %s (%u)", prefix, ei_event_type_to_string(event->type), event->type); list_append(queue, &event->link); } void ei_queue_connect_event(struct ei *ei) { struct ei_event *e = ei_event_new(ei); e->type = EI_EVENT_CONNECT; queue_event(ei, e); } void ei_queue_disconnect_event(struct ei *ei) { struct ei_event *e = ei_event_new(ei); e->type = EI_EVENT_DISCONNECT; queue_event(ei, e); } void ei_queue_seat_added_event(struct ei_seat *seat) { struct ei *ei= ei_seat_get_context(seat); struct ei_event *e = ei_event_new(ei); e->type = EI_EVENT_SEAT_ADDED; e->seat = ei_seat_ref(seat); queue_event(ei, e); } void ei_queue_seat_removed_event(struct ei_seat *seat) { struct ei *ei= ei_seat_get_context(seat); struct ei_event *e = ei_event_new(ei); e->type = EI_EVENT_SEAT_REMOVED; e->seat = ei_seat_ref(seat); queue_event(ei, e); } static void queue_device_added_event(struct ei_device *device) { struct ei *ei= ei_device_get_context(device); struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_DEVICE_ADDED; queue_event(ei, e); } static void queue_device_removed_event(struct ei_device *device) { struct ei *ei= ei_device_get_context(device); struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_DEVICE_REMOVED; queue_event(ei, e); } void ei_queue_device_paused_event(struct ei_device *device) { struct ei *ei= ei_device_get_context(device); struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_DEVICE_PAUSED; queue_event(ei, e); } void ei_queue_device_resumed_event(struct ei_device *device) { struct ei *ei= ei_device_get_context(device); struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_DEVICE_RESUMED; queue_event(ei, e); } void ei_queue_keyboard_modifiers_event(struct ei_device *device, const struct ei_xkb_modifiers *mods) { struct ei *ei= ei_device_get_context(device); struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_KEYBOARD_MODIFIERS; e->modifiers = *mods; queue_event(ei, e); } void ei_queue_frame_event(struct ei_device *device, uint64_t time) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_FRAME; e->timestamp = time; queue_event(ei_device_get_context(device), e); } void ei_queue_device_start_emulating_event(struct ei_device *device, uint32_t sequence) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_DEVICE_START_EMULATING; e->start_emulating.sequence = sequence; queue_event(ei_device_get_context(device), e); } void ei_queue_device_stop_emulating_event(struct ei_device *device) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_DEVICE_STOP_EMULATING; queue_event(ei_device_get_context(device), e); } void ei_queue_pointer_rel_event(struct ei_device *device, double dx, double dy) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_POINTER_MOTION; e->pointer.dx = dx; e->pointer.dy = dy; queue_event(ei_device_get_context(device), e); } void ei_queue_pointer_abs_event(struct ei_device *device, double x, double y) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_POINTER_MOTION_ABSOLUTE; e->pointer.absx = x; e->pointer.absy = y; queue_event(ei_device_get_context(device), e); } void ei_queue_pointer_button_event(struct ei_device *device, uint32_t button, bool is_press) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_BUTTON_BUTTON; e->pointer.button = button; e->pointer.button_is_press = is_press; queue_event(ei_device_get_context(device), e); } void ei_queue_pointer_scroll_event(struct ei_device *device, double x, double y) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_SCROLL_DELTA; e->pointer.sx = x; e->pointer.sy = y; queue_event(ei_device_get_context(device), e); } void ei_queue_pointer_scroll_discrete_event(struct ei_device *device, int32_t x, int32_t y) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_SCROLL_DISCRETE; e->pointer.sdx = x; e->pointer.sdy = y; queue_event(ei_device_get_context(device), e); } void ei_queue_pointer_scroll_stop_event(struct ei_device *device, bool x, bool y) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_SCROLL_STOP; e->pointer.stop_x = x; e->pointer.stop_y = y; queue_event(ei_device_get_context(device), e); } void ei_queue_pointer_scroll_cancel_event(struct ei_device *device, bool x, bool y) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_SCROLL_CANCEL; e->pointer.stop_x = x; e->pointer.stop_y = y; queue_event(ei_device_get_context(device), e); } void ei_queue_keyboard_key_event(struct ei_device *device, uint32_t key, bool is_press) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_KEYBOARD_KEY; e->keyboard.key = key; e->keyboard.key_is_press = is_press; queue_event(ei_device_get_context(device), e); } void ei_queue_touch_down_event(struct ei_device *device, uint32_t touchid, double x, double y) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_TOUCH_DOWN; e->touch.touchid = touchid, e->touch.x = x; e->touch.y = y; queue_event(ei_device_get_context(device), e); } void ei_queue_touch_motion_event(struct ei_device *device, uint32_t touchid, double x, double y) { struct ei_event *e = ei_event_new_for_device(device); e->type = EI_EVENT_TOUCH_MOTION; e->touch.touchid = touchid, e->touch.x = x; e->touch.y = y; queue_event(ei_device_get_context(device), e); } void 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, queue_event(ei_device_get_context(device), e); } void ei_disconnect(struct ei *ei) { if (ei->state == EI_STATE_DISCONNECTED || ei->state == EI_STATE_DISCONNECTING) return; enum ei_state state = ei->state; /* We need the disconnecting state to be re-entrant ei_device_remove() may call ei_disconnect() on a socket error */ ei->state = EI_STATE_DISCONNECTING; struct ei_seat *seat; list_for_each_safe(seat, &ei->seats, link) { ei_seat_remove(seat); } if (state != EI_STATE_NEW) { ei_connection_request_disconnect(ei->connection); } ei_queue_disconnect_event(ei); ei->state = EI_STATE_DISCONNECTED; if (ei->source) source_remove(ei->source); ei->source = source_unref(ei->source); } #define DISCONNECT_IF_INVALID_ID(connection_, id_) do { \ if (!brei_is_server_id(id_)) { \ struct ei *ei_ = ei_connection_get_context(connection_); \ log_bug(ei_, "Received invalid object id %#" PRIx64 ". Disconnecting", id_); \ return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Received invalid object id %#" PRIx64 ".", id_); \ } \ } while(0) static struct brei_result * handle_msg_seat(struct ei_connection *connection, object_id_t seat_id, uint32_t version) { 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 * we know we're connected */ ei_connected(ei); /* seats list owns the ref */ list_append(&ei->seats, &seat->link); return NULL; } void ei_queue_device_removed_event(struct ei_device *device) { queue_device_removed_event(device); } void ei_queue_device_added_event(struct ei_device *device) { queue_device_added_event(device); } _public_ struct ei_event* ei_get_event(struct ei *ei) { if (list_empty(&ei->event_queue)) return NULL; struct ei_event *e = list_first_entry(&ei->event_queue, e, link); list_remove(&e->link); return e; } _public_ struct ei_event* ei_peek_event(struct ei *ei) { if (list_empty(&ei->event_queue)) return NULL; struct ei_event *e = list_first_entry(&ei->event_queue, e, link); return ei_event_ref(e); } void ei_connected(struct ei *ei) { if (ei->state == EI_STATE_CONNECTING) { ei->state = EI_STATE_CONNECTED; ei_queue_connect_event(ei); } } static struct brei_result * handle_msg_disconnected(struct ei_connection *connection, uint32_t last_serial, uint32_t reason, const char *explanation) { struct ei *ei = ei_connection_get_context(connection); 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 { log_info(ei, "Disconnected after error: %s", explanation); return brei_result_new(reason, "%s", explanation); } } static struct brei_result * 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); /* 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; } static struct brei_result * handle_msg_ping(struct ei_connection *connection, object_id_t id, uint32_t version) { DISCONNECT_IF_INVALID_ID(connection, id); struct ei *ei = ei_connection_get_context(connection); DISCONNECT_IF_INVALID_VERSION(ei, ei_pingpong, id, version); _unref_(ei_pingpong) *pingpong = ei_pingpong_new_for_id(ei, id, version); ei_pingpong_request_done(pingpong, 0); return NULL; } static const struct ei_connection_interface interface = { .disconnected = handle_msg_disconnected, .seat = handle_msg_seat, .invalid_object = handle_msg_invalid_object, .ping = handle_msg_ping, }; const struct ei_connection_interface * ei_get_interface(struct ei *ei) { return &interface; } static int lookup_object(object_id_t object_id, struct brei_object **object, void *userdata) { struct ei *ei = userdata; struct brei_object *obj; list_for_each(obj, &ei->proto_objects, link) { if (obj->id == object_id) { *object = obj; return 0; } } log_debug(ei, "Failed to find object %#" PRIx64 "", object_id); return -ENOENT; } static void connection_dispatch(struct source *source, void *userdata) { static uint8_t cleanup; struct ei *ei = userdata; enum ei_state old_state = ei->state; /* 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[] = { "NEW", "BACKEND", "CONNECTING", "CONNECTED", "DISCONNECTED", "DISCONNECTING", }; if (old_state != ei->state) log_debug(ei, "Connection dispatch: %s -> %s", states[old_state], 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'", object->id, object->interface->name, object->interface->version, object->interface->requests[opcode].name, opcode, signature); va_list args; va_start(args, nargs); _unref_(brei_result) *result = brei_marshal_message(ei->brei, object->id, opcode, signature, nargs, args); va_end(args); if (brei_result_get_reason(result) != 0) { log_warn(ei, "failed to marshal message: %s", brei_result_get_explanation(result)); return -EBADMSG; } _cleanup_iobuf_ struct iobuf *buf = brei_result_get_data(result); assert(buf); int fd = source_get_fd(ei->source); 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; } int ei_set_socket(struct ei *ei, int fd) { struct source *source = source_new(fd, connection_dispatch, ei); int rc = sink_add_source(ei->sink, source); if (rc == 0) { ei->source = source_ref(source); ei->state = EI_STATE_BACKEND; /* The server SHOULD have already sent the handshake * version, let's process that. If not ready, it'll happen * in the next dispatch. * * FIXME: this will block if O_NONBLOCK is missing */ ei_dispatch(ei); } source_unref(source); return rc < 0 ? rc : 0; } _public_ void ei_configure_name(struct ei *ei, const char *name) { if (ei->state != EI_STATE_NEW) { log_bug_client(ei,"Client is already connected"); return; } if (strlen(name) > 1024) { log_bug_client(ei, "Client name too long"); return; } free(ei->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; 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; } #ifdef _enable_tests_ #include "util-munit.h" MUNIT_TEST(test_init_unref) { struct ei *ei = ei_new(NULL); munit_assert_int(ei->state, ==, EI_STATE_NEW); munit_assert(list_empty(&ei->event_queue)); munit_assert(list_empty(&ei->seats)); munit_assert_not_null(ei->sink); struct ei *refd = ei_ref(ei); munit_assert_ptr_equal(ei, refd); munit_assert_int(ei->object.refcount, ==, 2); struct ei *unrefd = ei_unref(ei); munit_assert_null(unrefd); unrefd = ei_unref(ei); munit_assert_null(unrefd); return MUNIT_OK; } MUNIT_TEST(test_configure_name) { struct ei *ei = ei_new(NULL); ei_configure_name(ei, "foo"); munit_assert_string_equal(ei->name, "foo"); ei_configure_name(ei, "bar"); munit_assert_string_equal(ei->name, "bar"); /* ignore names that are too long */ char buf[1200] = {0}; memset(buf, 'a', sizeof(buf) - 1); ei_configure_name(ei, buf); munit_assert_string_equal(ei->name, "bar"); /* ignore names in all other states */ for (enum ei_state state = EI_STATE_NEW + 1; state <= EI_STATE_DISCONNECTED; state++) { ei->state = state; ei_configure_name(ei, "expect ignored"); munit_assert_string_equal(ei->name, "bar"); } ei_unref(ei); return MUNIT_OK; } #endif libei-1.2.1/src/libei.h000066400000000000000000001660451456005336000146410ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #ifdef __cplusplus extern "C" { #endif #include #include #include /** * @defgroup libei 🥚 EI - The client API * * libei is the client-side module. This API should be used by processes * that need to emulate devices or capture logical events from existing devices. * * For an example client see the [`tools/` directory in the libei * repository](https://gitlab.freedesktop.org/libinput/libei/-/tree/main/tools). * At its core, a libei client will * - create a context with ei_new_sender() or ei_new_receiver() * - set up a backend with ei_setup_backend_fd() or ei_setup_backend_socket() * - register the ei_get_fd() with its own event loop * - call ei_dispatch() whenever the fd triggers * - call ei_get_event() and process incoming events * * Those events are typically a sequence of * - @ref EI_EVENT_CONNECT - notification that the client was connected * - @ref EI_EVENT_SEAT_ADDED - notification that a @ref ei_seat is * available. The client should respond with ei_seat_bind_capabilities() * - @ref EI_EVENT_DEVICE_ADDED - notification that a @ref ei_device is * available in a seat. * - @ref EI_EVENT_DEVICE_RESUMED - notification that the @ref ei_device is * ready to emulate events. The client should respond with * ei_device_start_emulating() and the required events to emulate input * * 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. * * @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 * client. It is up to the implementation to disconnect clients that it does not * want to allow. See eis_client_is_sender() for details. * * @defgroup libei-log The logging API * @ingroup libei * * The API to control logging output. * * @defgroup libei-receiver API for receiver clients * @ingroup libei * * The receiver client API is available only to clients created with * ei_new_receiver(). For those clients the EIS implemententation creates * devices and sends events **to** the libei client. The primary use-case * for this is input capturing, e.g. InputLeap. * * It is a client bug to call any of these functions for a client created * with ei_new_sender(). * * @defgroup libei-sender API for sender clients * @ingroup libei * * 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. * 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 * with ei_new_receiver(). * * @defgroup libei-seat The seat API * @ingroup libei * * The API to query and interact with a struct @ref ei_seat * * @defgroup libei-device The device API * @ingroup libei * * The API to query and interact with a struct @ref ei_device * * @defgroup libei-region The region API * @ingroup libei * * The API to query a struct @ref ei_region for information * * @defgroup libei-keymap The keymap API * @ingroup libei * * The API to query a struct @ref ei_keymap for information * */ /** * @addtogroup libei * @{ */ /** * @struct ei * * The main context to interact with libei. A libei context is a single * connection to an EIS implementation and may contain multiple devices, see * @ref ei_device. * * An @ref ei context is refcounted, see ei_unref(). */ struct ei; /** * @struct ei_device * * A single device to generate input events from. A device may have multiple * capabilities. For example, a single device may be a pointer and a keyboard * and a touch device. It is up to the EIS implementation on how to handle * this case, some implementations may split a single device up into * multiple virtual devices, others may not. * * An @ref ei_device is refcounted, see ei_device_unref(). */ struct ei_device; /** * @struct ei_seat * * A logical seat for a group of devices. Seats are provided by the EIS * implementation, devices may be added to a seat. The hierarchy of objects * looks like this: *
 *    ei ---- ei_seat one ---- ei_device 1
 *       \                \
 *        \                --- ei device 2
 *         --- ei_seat two --- ei device 3
 * 
*/ struct ei_seat; /** * @struct ei_event * * An event received from the EIS implementation. See @ref ei_event_type * for the list of possible event types. * * An @ref ei_event is refcounted, see ei_event_unref(). */ struct ei_event; /** * @struct ei_keymap * * An keymap for a device with the @ref EI_DEVICE_CAP_KEYBOARD capability. * * An @ref ei_keymap is refcounted, see ei_keymap_unref(). */ struct ei_keymap; /** * @struct ei_region * @ingroup libei-region * * A rectangular region, defined by an x/y offset and a width and a height. * A region defines the area on an EIS desktop layout that is accessible by * this device - this region may not be the full area of the desktop. * Input events may only be sent for points within the regions. * * The use of regions is private to the EIS compositor and coordinates may not * match the size of the actual desktop. For example, a compositor may set a * 1920x1080 region to represent a 4K monitor and transparently map input * events into the respective true pixels. * * Absolute devices may have different regions, it is up to the libei client * to send events through the correct device to target the right pixel. For * example, a dual-head setup my have two absolute devices, the first with a * zero offset region spanning the first screen, the second with a nonzero * offset spanning the second screen. */ struct ei_region; /** * @enum ei_device_type * @ingroup libei-device * * The device type determines what the device represents. * * If the device type is @ref EI_DEVICE_TYPE_VIRTUAL, the device is a * virtual device representing input as applied on the EIS implementation's * screen. A relative virtual device generates input events in logical pixels, * an absolute virtual device generates input events in logical pixels on one * of the device's regions. Virtual devices do not have a size. * * If the device type is @ref EI_DEVICE_TYPE_PHYSICAL, the device is a * representation of a physical device as if connected to the EIS * implementation's host computer. A relative physical device generates input * events in mm, an absolute physical device generates input events in mm * within the device's specified physical size. Physical devices do not have * regions. * * @see ei_device_get_width * @see ei_device_get_height */ enum ei_device_type { EI_DEVICE_TYPE_VIRTUAL = 1, EI_DEVICE_TYPE_PHYSICAL }; /** * @enum ei_device_capability * * The set of supported capabilities. A device may have zero or more * capabilities, a device with perceived zero capabilities is typically a * device with capabilities unsupported by the client - such a device should be * @ref ei_device_close "closed" immediately by the client. * * The EIS implementation decides which capabilities may exist on a seat and * which capabilities exist on any individual seat. However, a device may only * have capabilities the client has bound to, see * ei_seat_bind_capabilities(). * * For example, a client may bind to a seat with the pointer and keyboard * capability but a given device only has the pointer capability. * Keyboard events sent through that device will be treated as client bug. * * If a client calls ei_seat_unbind_capabilities() for a capability, the EIS * implementation may remove any or all devices that have that capability. * * See ei_device_has_capability(). * */ enum ei_device_capability { /** * The device can send relative motion events */ EI_DEVICE_CAP_POINTER = (1 << 0), /** * The device can send absolute motion events */ EI_DEVICE_CAP_POINTER_ABSOLUTE = (1 << 1), /** * The device can send keyboard events */ EI_DEVICE_CAP_KEYBOARD = (1 << 2), /** * The device can send touch events */ EI_DEVICE_CAP_TOUCH = (1 << 3), /** * The device can send scroll events */ EI_DEVICE_CAP_SCROLL = (1 << 4), /** * The device can send button events */ EI_DEVICE_CAP_BUTTON = (1 << 5), }; /** * @enum ei_keymap_type * * The set of supported keymap types for a struct @ref ei_keymap. */ enum ei_keymap_type { /** * A libxkbcommon-compatible XKB keymap. */ EI_KEYMAP_TYPE_XKB = 1, }; enum ei_event_type { /** * The server has approved the connection to this client. Where the * server does not approve the connection, @ref EI_EVENT_DISCONNECT is * sent instead. * * This event is only sent once after the initial connection * request. */ EI_EVENT_CONNECT = 1, /** * The server has disconnected this client - all resources left to * reference this server are now obsolete. Once this event has been * received, the struct @ref ei and all its associated resources * should be released. * * This event may occur at any time after the connection has been * made and is the last event to be received by this ei instance. * * libei guarantees that a @ref EI_EVENT_DISCONNECT is provided to * the caller even where the server does not send one. */ EI_EVENT_DISCONNECT, /** * The server has added a seat available to this client. * * libei guarantees that any seat added has a corresponding @ref * EI_EVENT_SEAT_REMOVED event before @ref EI_EVENT_DISCONNECT. * libei guarantees that any device in this seat generates a @ref * EI_EVENT_DEVICE_REMOVED event before the @ref * EI_EVENT_SEAT_REMOVED event. */ EI_EVENT_SEAT_ADDED, /** * The server has removed a seat previously available to this * client. The caller should release the struct @ref ei_seat and * all its associated resources. No devices will be added to this seat * anymore. * * libei guarantees that any device in this seat generates a @ref * EI_EVENT_DEVICE_REMOVED event before the @ref * EI_EVENT_SEAT_REMOVED event. */ EI_EVENT_SEAT_REMOVED, /** * The server has added a device for this client. The capabilities * of the device may be a subset of the seat capabilities - it is up * to the client to verify the minimum required capabilities are * indeed set. * * libei guarantees that any device added has a corresponding @ref * EI_EVENT_DEVICE_REMOVED event before @ref EI_EVENT_DISCONNECT. */ EI_EVENT_DEVICE_ADDED, /** * The server has removed a device belonging to this client. The * caller should release the struct @ref ei_device and all its * associated resources. Any events sent through a removed device * are discarded. * * When this event is received, the device is already removed. A * caller does not need to call ei_device_close() event on this * device. */ EI_EVENT_DEVICE_REMOVED, /** * 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. */ EI_EVENT_DEVICE_PAUSED, /** * The client may send events. */ EI_EVENT_DEVICE_RESUMED, /** * The server has changed the modifier state on the device's * keymap. See * ei_event_keyboard_get_xkb_mods_depressed(), * ei_event_keyboard_get_xkb_mods_latched(), * 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 may arrive while a device is paused. */ EI_EVENT_KEYBOARD_MODIFIERS, /** * "Hardware" frame event. This event **must** be sent by the server * and notifies the client that the previous set of events belong to * the same logical hardware event. * * @note These events are only generated on a receiver ei context. * See ei_device_frame() for the sender context API. */ EI_EVENT_FRAME = 100, /** * The server is about to send events for a device. This event should * be used by the client to clear the logical state of the emulated * devices and/or provide UI to the user. * * @note These events are only generated on a receiver ei context. * See ei_device_start_emulating() for the sender context API. * * Note that a server start multiple emulating sequences * simultaneously, depending on the devices available. * For example, in a synergy-like situation, the server * may start sending pointer and keyboard once the remote device * logically entered the screen. */ EI_EVENT_DEVICE_START_EMULATING = 200, /** * The server stopped emulating events on this device, * see @ref EIS_EVENT_DEVICE_START_EMULATING. * * @note These events are only generated on a receiver ei context. * See ei_device_stop_emulating() for the sender context API. */ EI_EVENT_DEVICE_STOP_EMULATING, /** * A relative motion event with delta coordinates in logical pixels or * mm, depending on the device type. * * @note This event is only generated on a receiver ei context. * See ei_device_pointer_motion() for the sender context API. */ EI_EVENT_POINTER_MOTION = 300, /** * An absolute motion event with absolute position within the device's * regions or size, depending on the device type. * * @note This event is only generated on a receiver ei context. * See ei_device_pointer_motion_absolute() for the sender context API. */ EI_EVENT_POINTER_MOTION_ABSOLUTE = 400, /** * A button press or release event * * @note This event is only generated on a receiver ei context. * See ei_device_button_button() for the sender context API. */ EI_EVENT_BUTTON_BUTTON = 500, /** * A vertical and/or horizontal scroll event with logical-pixels * or mm precision, depending on the device type. * * @note This event is only generated on a receiver ei context. * See ei_device_scroll_delta() for the sender context API. */ EI_EVENT_SCROLL_DELTA = 600, /** * An ongoing scroll sequence stopped. * * @note This event is only generated on a receiver ei context. * See ei_device_scroll_stop() for the sender context API. */ EI_EVENT_SCROLL_STOP, /** * An ongoing scroll sequence was cancelled. * * @note This event is only generated on a receiver ei context. * See ei_device_scroll_cancel() for the sender context API. */ EI_EVENT_SCROLL_CANCEL, /** * A vertical and/or horizontal scroll event with a discrete range in * logical scroll steps, like a scroll wheel. * * @note This event is only generated on a receiver ei context. * See ei_device_scroll_discrete() for the sender context API. */ EI_EVENT_SCROLL_DISCRETE, /** * A key press or release event * * @note This event is only generated on a receiver ei context. * See ei_device_keyboard_key() for the sender context API. */ EI_EVENT_KEYBOARD_KEY = 700, /** * Event for a single touch set down on the device's logical surface. * A touch sequence is always down/up with an optional motion event in * between. On multitouch capable devices, several touchs eqeuences * may be active at any time. * * @note This event is only generated on a receiver ei context. * See ei_device_touch_new() and ei_touch_down() for the sender context API. */ EI_EVENT_TOUCH_DOWN = 800, /** * Event for a single touch released from the device's logical * surface. * * @note This event is only generated on a receiver ei context. * See ei_device_touch_new() and ei_touch_up() for the sender context API. */ EI_EVENT_TOUCH_UP, /** * Event for a single currently-down touch changing position (or other * properties). * * @note This event is only generated on a receiver ei context. * See ei_device_touch_new() and ei_touch_motion() for the sender context API. */ EI_EVENT_TOUCH_MOTION, }; /** * This is a debugging helper to return a string of the name of the event type, * or NULL if the event type is invalid. For example, for @ref EI_EVENT_TOUCH_UP this * function returns the string "EI_EVENT_TOUCH_UP". */ const char* ei_event_type_to_string(enum ei_event_type); /** * This is an alias for @ref ei_new_sender. */ struct ei * ei_new(void *user_data); /** * Create a new sender ei context. The context is refcounted and must be * released with ei_unref(). * * A sender ei context sends events to the EIS implementation but cannot * receive events. * * A context supports exactly one backend, set up with one of * ei_setup_backend_socket() or ei_setup_backend_fd(). * * @param user_data An opaque pointer to be returned with ei_get_user_data() * * @see ei_set_user_data * @see ei_get_user_data * @see ei_setup_backend_fd * @see ei_setup_backend_socket */ struct ei * ei_new_sender(void *user_data); /** * Create a new receiver ei context. The context is refcounted and must be * released with ei_unref(). * * A receiver ei context receives events from the EIS implementation but cannot * send events. * * A context supports exactly one backend, set up with one of * ei_setup_backend_socket() or ei_setup_backend_fd(). * * @param user_data An opaque pointer to be returned with ei_get_user_data() * * @see ei_set_user_data * @see ei_get_user_data * @see ei_setup_backend_fd * @see ei_setup_backend_socket */ struct ei * ei_new_receiver(void *user_data); /** * Increase the refcount of this struct by one. Use ei_unref() to decrease * the refcount. * * @return the argument passed into the function */ struct ei * ei_ref(struct ei *ei); /** * 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 */ struct ei * ei_unref(struct ei *ei); /** * Set a custom data pointer for this context. libei will not look at or * modify the pointer. Use ei_get_user_data() to retrieve a previously set * user data. */ void ei_set_user_data(struct ei *ei, void *user_data); /** * Return the custom data pointer for this context. libei will not look at or * modify the pointer. Use ei_set_user_data() to change the user data. */ void * ei_get_user_data(struct ei *ei); /** * Returns true if the context is was created with ei_new_sender() or false otherwise. */ bool ei_is_sender(struct ei *ei); /** * @ingroup libei-log */ enum ei_log_priority { EI_LOG_PRIORITY_DEBUG = 10, EI_LOG_PRIORITY_INFO = 20, EI_LOG_PRIORITY_WARNING = 30, EI_LOG_PRIORITY_ERROR = 40, }; struct ei_log_context; /** * @ingroup libei-log * @return the line number (``__LINE__``) for a given log message context. */ unsigned int ei_log_context_get_line(struct ei_log_context *ctx); /** * @ingroup libei-log * @return the file name (``__FILE__``) for a given log message context. */ const char * ei_log_context_get_file(struct ei_log_context *ctx); /** * @ingroup libei-log * @return the function name (``__func__``) for a given log message context. */ const char * ei_log_context_get_func(struct ei_log_context *ctx); /** * @ingroup libei-log * * The log handler for library logging. This handler is only called for * 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 * 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 * invocation of the log handler. * * @param ei The EI context * @param priority The log priority * @param message The log message as a null-terminated string * @param context A log message context for this message */ typedef void (*ei_log_handler)(struct ei *ei, enum ei_log_priority priority, const char *message, struct ei_log_context *context); /** * @ingroup libei-log * * Change the log handler for this context. If the log handler is NULL, the * built-in default log function is used. * * @param ei The EI context * @param log_handler The log handler or NULL to use the default log * handler. */ void ei_log_set_handler(struct ei *ei, ei_log_handler log_handler); /** * @ingroup libei-log */ void ei_log_set_priority(struct ei *ei, enum ei_log_priority priority); /** * @ingroup libei-log */ 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. * * The client name may be used for display to the user, for example in * an authorization dialog that requires the user to approve a connection to * 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(). */ void ei_configure_name(struct ei * ei, const char *name); /** * 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. * * 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 * function fails. * * 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. * * @return zero on success or a negative errno on failure */ 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 * events are available on this fd, call ei_dispatch() immediately to * process. */ int ei_get_fd(struct ei *ei); /** * Main event dispatching function. Reads events of the file descriptors * and processes them internally. Use ei_get_event() to retrieve the * events. * * Dispatching does not necessarily queue events. This function * should be called immediately once data is available on the file * descriptor returned by libei_get_fd(). */ void 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() */ struct ei_event * ei_get_event(struct ei *ei); /** * Returns the next event in the internal event queue (or `NULL`) without * removing that event from the queue; the next call to ei_get_event() * will return that same event. * * This call is useful for checking whether there is an event and/or what * type of event it is. * * Repeated calls to ei_peek_event() return the same event. * * The returned event is refcounted, use ei_event_unref() to drop the * reference. * * A caller must not call ei_get_event() while holding a ref to an event * returned by ei_peek_event(). Doing so is undefined behavior. */ struct ei_event * ei_peek_event(struct ei *ei); /** * @returns a timestamp in microseconds for the current time to pass into * ei_device_frame(). * * 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); /** * @ingroup libei-seat * * Set a custom data pointer for this context. libei will not look at or * modify the pointer. Use ei_seat_get_user_data() to retrieve a * previously set user data. */ void ei_seat_set_user_data(struct ei_seat *seat, void *user_data); /** * @ingroup libei-seat * * Return the custom data pointer for this context. libei will not look at or * modify the pointer. Use ei_seat_get_user_data() to change the user data. */ void * ei_seat_get_user_data(struct ei_seat *seat); /** * @ingroup libei-seat */ const char * ei_seat_get_name(struct ei_seat *seat); /** * @ingroup libei-seat * * Return true if the capabilitiy is available on this seat or false * otherwise. The return value of this function is not affected by * ei_seat_confirm_capability(). */ bool ei_seat_has_capability(struct ei_seat *seat, enum ei_device_capability cap); /** * @ingroup libei-seat * * Bind this client to the given seat capabilities, terminated by ``NULL``. * Once bound, the server may * create devices for the requested capability and send the respective @ref * EI_EVENT_DEVICE_ADDED events. To undo, call ei_seat_unbind_capabilities(). * * Note that binding to a capability does not guarantee a device for that * capability becomes available. Devices may be added and removed at any time. * * It is an application bug to call this function for a capability already * bound - call ei_seat_unbind_capabilities() first. * * Calling this function for a capability that does not exist on the seat is * permitted (but obviously a noop) */ void ei_seat_bind_capabilities(struct ei_seat *seat, ...) __attribute__((sentinel)); /** * @ingroup libei-seat * * Unbind a seat's capabilities, terminatd by ``NULL``. * This function indicates the the application is * no longer interested in devices with the given capability. * * If any devices with the given capability are present, libei automatically * calls ei_device_close() on those devices (and thus the server will send * @ref EI_EVENT_DEVICE_REMOVED for those devices). */ void ei_seat_unbind_capabilities(struct ei_seat *seat, ...) __attribute__((sentinel)); /** * @ingroup libei-seat */ struct ei_seat * ei_seat_ref(struct ei_seat *seat); /** * @ingroup libei-seat */ struct ei_seat * ei_seat_unref(struct ei_seat *seat); /** * @ingroup libei-seat * * Return the struct @ref ei context this seat is associated with. */ struct ei * ei_seat_get_context(struct ei_seat *seat); /** * Release resources associated with this event. This function always * returns NULL. * * 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()). */ struct ei_event * ei_event_unref(struct ei_event *event); /** * @return the type of this event */ enum ei_event_type ei_event_get_type(struct ei_event *event); /** * Return the device from this event. * * For events of type @ref EI_EVENT_CONNECT and @ref EI_EVENT_DISCONNECT, * this function returns NULL. * * This does not increase the refcount of the device. Use eis_device_ref() * to keep a reference beyond the immediate scope. */ struct ei_device * ei_event_get_device(struct ei_event *event); /** * Return the time for the event of type @ref EI_EVENT_FRAME in microseconds. * * @note Only events of type @ref EI_EVENT_FRAME carry a timestamp. For * convenience, the timestamp for other device events is retrofitted by this * library. * * @return the event time in microseconds */ uint64_t ei_event_get_time(struct ei_event *event); /** * @ingroup libei-device * * Increase the refcount of this struct by one. Use ei_device_unref() to * decrease the refcount. * * @return the argument passed into the function */ struct ei_device * 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. * * @return always NULL */ struct ei_device * ei_device_unref(struct ei_device *device); /** * @ingroup libei-device */ struct ei_seat * ei_device_get_seat(struct ei_device *device); /** * @ingroup libei-device * * Set a custom data pointer for this context. libei will not look at or * modify the pointer. Use ei_device_get_user_data() to retrieve a * previously set user data. */ void ei_device_set_user_data(struct ei_device *device, void *user_data); /** * @ingroup libei-device * * Return the custom data pointer for this context. libei will not look at or * modify the pointer. Use ei_device_get_user_data() to change the user data. */ void * ei_device_get_user_data(struct ei_device *device); /** * @ingroup libei-device * * Return the width of the device in mm if the device is of type @ref * EI_DEVICE_TYPE_PHYSICAL, otherwise zero. */ uint32_t ei_device_get_width(struct ei_device *device); /** * @ingroup libei-device * * Return the height of the device in mm if the device is of type @ref * EI_DEVICE_TYPE_PHYSICAL, otherwise zero. */ uint32_t ei_device_get_height(struct ei_device *device); /** * @ingroup libei-keymap * * @return the size of the keymap in bytes */ size_t ei_keymap_get_size(struct ei_keymap *keymap); /** * @ingroup libei-keymap * * Returns the type for this keymap. The type specifies how to interpret the * data at the file descriptor returned by ei_keymap_get_fd(). */ enum ei_keymap_type ei_keymap_get_type(struct ei_keymap *keymap); /** * @ingroup libei-keymap * * Return a memmap-able file descriptor pointing to the keymap used by the * device. The keymap is constant for the lifetime of the device and * assigned to this device individually. */ int ei_keymap_get_fd(struct ei_keymap *keymap); /** * @ingroup libei-keymap * * Return the device this keymap belongs to, or `NULL` if it has not yet * been assigned to a device. * * After processing and if the server changed the keymap or set the keymap * to NULL, this keymap may no longer be in use by the device and future * calls to this function return `NULL`. */ struct ei_device * ei_keymap_get_device(struct ei_keymap *keymap); /** * @ingroup libei-keymap * * Increase the refcount of this struct by one. Use ei_keymap_unref() to * decrease the refcount. * * @return the argument passed into the function */ struct ei_keymap * 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. * * @return always NULL */ struct ei_keymap * ei_keymap_unref(struct ei_keymap *keymap); /** * @ingroup libei-keymap */ void ei_keymap_set_user_data(struct ei_keymap *keymap, void *user_data); /** * @ingroup libei-keymap */ void * ei_keymap_get_user_data(struct ei_keymap *keymap); /** * @ingroup libei-device * * Notify the server that the client is no longer interested in * this device. * * Due to the asynchronous nature of the client-server interaction, * events for this device may still be in transit. The server will send an * @ref EI_EVENT_DEVICE_REMOVED event for this device. After that event, * device is considered removed by the server. * * A client can assume that an @ref EI_EVENT_DEVICE_REMOVED event is sent * for any device for which ei_device_close() was called before the @ref * EI_EVENT_DISCONNECT event. Where a client gets * disconnected libei will emulate that event. * * This does not release any resources associated with this device, use * ei_device_unref() for any references held by the client. */ void ei_device_close(struct ei_device *device); /** * @ingroup libei-device * * @return the name of the device (if any) or NULL */ const char * ei_device_get_name(struct ei_device *device); /** * @ingroup libei-device * * @return the device type */ enum ei_device_type ei_device_get_type(struct ei_device *device); /** * @ingroup libei-device * * Return true if the device has the requested capability. Device * capabilities are constant for the lifetime of the device and always * a subset of the capabilities bound to by ei_seat_bind_capabilities(). */ bool ei_device_has_capability(struct ei_device *device, enum ei_device_capability cap); /** * @ingroup libei-device * * Obtain a region from a device of type @ref EI_DEVICE_TYPE_VIRTUAL. The * number of regions is constant for a device and the indices of any region * remains the same for the lifetime of the device. * * Regions are shared between all capabilities. Where two capabilities need * different regions, the EIS implementation must create multiple devices with * individual capabilities and regions. For example, two touchscreens that are * mapped to two screens would typically show up as two separate devices with * one region each. * * This function returns the given region or NULL if the index is larger than * the number of regions available. * * This does not increase the refcount of the region. Use ei_region_ref() to * keep a reference beyond the immediate scope. * * Devices of type @ref EI_DEVICE_TYPE_PHYSICAL do not have regions. */ 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 */ struct ei_region * ei_region_ref(struct ei_region *region); /** * @ingroup libei-region */ struct ei_region * ei_region_unref(struct ei_region *region); /** * @ingroup libei-region */ void ei_region_set_user_data(struct ei_region *region, void *user_data); /** * @ingroup libei-region */ void * ei_region_get_user_data(struct ei_region *region); /** * @ingroup libei-region */ uint32_t ei_region_get_x(struct ei_region *region); /** * @ingroup libei-region */ uint32_t ei_region_get_y(struct ei_region *region); /** * @ingroup libei-region */ uint32_t ei_region_get_width(struct ei_region *region); /** * @ingroup libei-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 identifer 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 * * Return true if the point x/y (in desktop-wide coordinates) is within @a * region. */ bool ei_region_contains(struct ei_region *region, double x, double y); /** * @ingroup libei-region * * Convert the point x/y in a desktop-wide coordinate system into the * corresponding point relative to the offset of the given region. * If the point is inside the region, this function returns true and @a x and @a * y are set to the points with the region offset subtracted. * If the point is outside the region, this function returns false and @a x * and @a y are left unmodified. */ bool ei_region_convert_point(struct ei_region *region, double *x, double *y); /** * @ingroup libei-region * * Return the physical scale for this region. The default scale is 1.0. * * The regions' coordinate space is in logical pixels in the EIS range. The * logical pixels may or may not match the physical pixels on the output * range but the mapping from logical pixels to physical pixels is performed * by the EIS implementation. * * In some use-cases though, relative data from a remote input source needs * to be converted by the libei client into an absolute movement on an EIS * region. In that case, the physical scale provides the factor to multiply * the relative logical input to provide the expected physical relative * movement. * * For example consider the following dual-monitor setup comprising a 2k and * a 4k monitor **of the same physical size**: * The physical layout of the monitors appears like this: * @code * 2k 4k * +-------------++-------------+ * | || | * | a b || c d | * | || | * +-------------++-------------+ * @endcode * * The physical distance `ab` is the same as the physical distance `cd`. * Where the EIS implementation supports high-dpi screens, the logical * distance (in pixels) are identical too. * * Where the EIS implementation does not support high-dpi screens, the * logical layout of these two monitors appears like this: * * @code * 2k 4k * +-------------++--------------------------+ * | || | * | a b || | * | || | * +-------------+| c d | * | | * | | * | | * +--------------------------+ * @endcode * * While the two physical distances `ab` and `cd` are still identical, the * logical distance `cd` (in pixels) is twice that of `ab`. * Where a libei client receives relative deltas from an input source and * converts that relative input into an absolute position on the screen, it * needs to take this into account. * * For example, if a remote input source moves by relative 100 logical * pixels, the libei client would convert this as `a + 100 = b` on the * region for the 2k screen and send the absolute events to logically change * the position from `a` to `b`. If the same remote input source moves by * relative 100 logical pixels, the libei client would convert this as * `c + 100 * scale = d` on the region for the 4k screen to logically * change the position from `c` to `d`. While the pixel movement differs, * the physical movement as seen by the user is thus identical. * * A second possible use-case for the physical scale is to match pixels from * one region to their respective counterpart on a different region. * For example, if the bottom-right corner of the 2k screen in the * illustration above has a coordinate of ``(x, y)``, the neighbouring pixel on * the **physical** 4k screen is ``(0, y * scale)``. */ double ei_region_get_physical_scale(struct ei_region *region); /** * @ingroup libei-device * * Return the keymap for this device or `NULL`. The keymap is constant for * the lifetime of the device and applies to this device individually. * * If this function returns `NULL`, this device does not have * an individual keymap assigned. What keymap applies to the device in this * case is a server implementation detail. * * This does not increase the refcount of the keymap. Use ei_keymap_ref() to * keep a reference beyond the immediate scope. * */ /* FIXME: the current API makes it impossible to know when the keymap has * been consumed so the file stays open forever. */ struct ei_keymap * ei_device_keyboard_get_keymap(struct ei_device *device); /** * @ingroup libei-keymap * * Return the struct @ref ei_device this keymap is associated with. */ struct ei_device * ei_keymap_get_context(struct ei_keymap *keymap); /** * @ingroup libei-device * * Return the struct @ref ei context this device is associated with. */ struct ei * ei_device_get_context(struct ei_device *device); /** * @ingroup libei-sender * * Notify the EIS implementation that the given device is about to start * sending events. This should be seen more as a transactional boundary than a * time-based boundary. The primary use-cases for this are to allow for setup on * the EIS implementation side and/or UI updates to indicate that a device is * sending events now and for out-of-band information to sync with a given event * sequence. * * There is no actual requirement that events start immediately once emulation * starts and there is no requirement that a client calls * ei_device_stop_emulating() after the most recent events. * * For example, in a synergy-like use-case the client would call * ei_device_start_emulating() once the pointer moves into the the screen and * ei_device_stop_emulating() once the pointer moves out of the screen. * * Sending events before ei_device_start_emulating() or after * ei_device_stop_emulating() is a client bug. * * The sequence number identifies this transaction between start/stop emulating. * It must go up by at least 1 on each call to * ei_device_start_emulating(). Wraparound must be handled by the EIS * implementation but Callers must ensure that detection of wraparound is * reasonably. * * This method is only available on an ei sender context. */ void ei_device_start_emulating(struct ei_device *device, uint32_t sequence); /** * @ingroup libei-sender * * Notify the EIS implementation that the given device is no longer sending * events. See ei_device_start_emulating() for details. * * This method is only available on an ei sender context. */ void ei_device_stop_emulating(struct ei_device *device); /** * @ingroup libei-sender * * Generate a frame event to group the current set of events * into a logical hardware event. This function **must** be called after any * other event has been generated. * * The given timestamp applies to all events in the current frame. * The timestamp must be in microseconds of CLOCK_MONOTONIC, use the return * value of ei_now() to get a compatible timestamp. * * @note libei does not prevent a caller from passing in a future time but it * is strongly recommended that this is avoided by the caller. * * This method is only available on an ei sender context. */ void ei_device_frame(struct ei_device *device, uint64_t time); /** * @ingroup libei-sender * * Generate a relative motion event on a device with * the @ref EI_DEVICE_CAP_POINTER capability. * * This method is only available on an ei sender context. * * @param device The EI device * @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); /** * @ingroup libei-sender * * Generate an absolute motion event on a device with * the @ref EI_DEVICE_CAP_POINTER_ABSOLUTE capability. * * The x/y coordinate must be within the device's regions or the event is * silently discarded. * * This method is only available on an ei sender context. * * @param device The EI device * @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, double x, double y); /** * @ingroup libei-sender * * Generate a button event on a device with * the @ref EI_DEVICE_CAP_BUTTON capability. * * Button codes must match the defines in ``linux/input-event-codes.h`` * * This method is only available on an ei sender context. * * @param device The EI device * @param button The button code * @param is_press true for button press, false for button release */ void ei_device_button_button(struct ei_device *device, uint32_t button, bool is_press); /** * @ingroup libei-sender * * Generate a smooth (pixel-precise) scroll event on a device with * the @ref EI_DEVICE_CAP_SCROLL capability. * * @note The server is responsible for emulating discrete scrolling based * on the pixel value, do not call ei_device_scroll_discrete() for * the same input event. * * This method is only available on an ei sender context. * * @param device The EI device * @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 */ void ei_device_scroll_delta(struct ei_device *device, double x, double y); /** * @ingroup libei-sender * * Generate a discrete scroll event on a device with * the @ref EI_DEVICE_CAP_SCROLL capability. * * A discrete scroll event is based logical scroll units (equivalent to one * mouse wheel click). The value for one scroll unit is 120, a fraction or * multiple thereof represents a fraction or multiple of a wheel click. * * @note The server is responsible for emulating pixel-based scrolling based * on the discrete value, do not call ei_device_scroll_delta() for the * same input event. * * This method is only available on an ei sender context. * * @param device The EI device * @param x The x scroll distance in fractions or multiples of 120 * @param y The y scroll distance in fractions or multiples of 120 * * @see ei_device_scroll_delta */ void ei_device_scroll_discrete(struct ei_device *device, int32_t x, int32_t y); /** * @ingroup libei-sender * * Generate a scroll stop event on a device with the * @ref EI_DEVICE_CAP_SCROLL capability. * * A scroll stop event notifies the server that the interaction causing a * scroll motion previously triggered with ei_device_scroll_delta() or * ei_device_scroll_discrete() has stopped. For example, if all * fingers are lifted off a touchpad, two-finger scrolling has logically * stopped. * * The server may use this information to e.g. start kinetic scrolling * previously based on the previous finger speed. * * Use ei_device_scroll_cancel() to signal that the scroll motion has * completely stopped. * * Calling ei_device_scroll_stop() after * ei_device_scroll_cancel() without any of ei_device_scroll_delta() * or ei_device_scroll_discrete() in between indicates a client logic bug. * * libei keeps track of the scroll axis and filters duplicate calls to * ei_device_scroll_stop() for the same axis. A nonzero scroll or * scroll-discrete value is required for the given axis to re-start scrolling * for that axis. * * This method is only available on an ei sender context. */ void ei_device_scroll_stop(struct ei_device *device, bool stop_x, bool stop_y); /** * @ingroup libei-sender * * Generate a scroll cancel event on a device with the * @ref EI_DEVICE_CAP_SCROLL capability. * * A scroll cancel event notifies the server that a scroll motion previously * triggered with ei_device_scroll_delta() or * ei_device_scroll_discrete() has ceased and no further events should * be sent. * * This event indicates that the interaction has stopped to the point where * further (server-emulated) scroll events from this device are wrong. * * Use ei_device_scroll_stop() to signal that the interaction has * stopped but a server may emulate further scroll events. * * Calling ei_device_scroll_cancel() after * ei_device_scroll_stop() without any of ei_device_scroll_delta() * or ei_device_scroll_discrete() in between iis permitted. * * libei keeps track of the scroll axis and filters duplicate calls to * ei_device_scroll_cancel() for the same axis. A nonzero scroll or * scroll-discrete value is required for the given axis to re-start scrolling * for that axis. * * This method is only available on an ei sender context. */ void ei_device_scroll_cancel(struct ei_device *device, bool cancel_x, bool cancel_y); /** * @ingroup libei-sender * * Generate a key event on a device with * the @ref EI_DEVICE_CAP_KEYBOARD capability. * * Keys use the evdev scan codes as defined in * ``linux/input-event-codes.h``. * * 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_keyboard_get_keymap() and * generate the appropriate keycodes. * * This method is only available on an ei sender context. * * @param device The EI device * @param keycode The key code * @param is_press true for key down, false for key up */ void ei_device_keyboard_key(struct ei_device *device, uint32_t keycode, bool is_press); /** * @ingroup libei-sender * * Initiate a new touch on a device with the @ref EI_DEVICE_CAP_TOUCH * capability. This touch does not immediately send events, use * ei_touch_down(), ei_touch_motion(), and ei_touch_up(). * * The returned touch has a refcount of at least 1, use ei_touch_unref() to * release resources associated with this touch * * This method is only available on an ei sender context. */ struct ei_touch * ei_device_touch_new(struct ei_device *device); /** * @ingroup libei-sender * * This function can only be called once on an ei_touch object. Further * calls to ei_touch_down() on the same object are silently ignored. * * The x/y coordinate must be within the device's regions or the event is * silently discarded. * * @param touch A newly created touch * @param x The x position in logical pixels * @param y The y position in logical pixels */ void ei_touch_down(struct ei_touch *touch, double x, double y); /** * @ingroup libei-sender * * Move this touch to the new coordinates. */ void ei_touch_motion(struct ei_touch *touch, double x, double y); /** * @ingroup libei-sender * * Release 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(). */ void ei_touch_up(struct ei_touch *touch); /** * @ingroup libei-sender * * Increase the refcount of this struct by one. Use ei_touch_unref() to * decrease the refcount. * * @return the argument passed into the function */ struct ei_touch * 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. * * @return always NULL */ struct ei_touch * ei_touch_unref(struct ei_touch *touch); /** * @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. */ void ei_touch_set_user_data(struct ei_touch *touch, void *user_data); /** * @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. */ void * ei_touch_get_user_data(struct ei_touch *touch); /** * @ingroup libei-sender * * @return the device this touch originates on */ struct ei_device * ei_touch_get_device(struct ei_touch *touch); /** * Return the seat from this event. * * For events of type @ref EI_EVENT_CONNECT and @ref EI_EVENT_DISCONNECT, * this function returns NULL. * * This does not increase the refcount of the seat. Use eis_seat_ref() * to keep a reference beyond the immediate scope. */ struct ei_seat * ei_event_get_seat(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_DEVICE_START_EMULATING, return the * sequence number set by the EIS implementation. * * See eis_device_start_emulating() for details. */ uint32_t ei_event_emulating_get_sequence(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the * mask of currently logically pressed-down modifiers. * See ei_device_keyboard_get_keymap() for the corresponding keymap. */ uint32_t ei_event_keyboard_get_xkb_mods_depressed(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the * mask of currently logically latched modifiers. * See ei_device_keyboard_get_keymap() for the corresponding keymap. */ uint32_t ei_event_keyboard_get_xkb_mods_latched(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the * mask of currently logically locked modifiers. * See ei_device_keyboard_get_keymap() for the corresponding keymap. */ uint32_t 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_keyboard_get_keymap() for the corresponding keymap. */ uint32_t ei_event_keyboard_get_xkb_group(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_POINTER_MOTION return the relative x * movement in logical pixels or mm, depending on the device type. */ double ei_event_pointer_get_dx(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_POINTER_MOTION return the relative y * movement in logical pixels or mm, depending on the device type. */ double ei_event_pointer_get_dy(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_POINTER_MOTION_ABSOLUTE return the x * position in logical pixels or mm, depending on the device type. */ double ei_event_pointer_get_absolute_x(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_POINTER_MOTION_ABSOLUTE return the y * position in logical pixels or mm, depending on the device type. */ double ei_event_pointer_get_absolute_y(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_BUTTON_BUTTON return the button * code as defined in linux/input-event-codes.h */ uint32_t ei_event_button_get_button(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_BUTTON_BUTTON return true if the * event is a button press, false for a release. */ bool ei_event_button_get_is_press(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_SCROLL_DELTA return the x scroll * distance in logical pixels or mm, depending on the device type. */ double ei_event_scroll_get_dx(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_SCROLL_DELTA return the y scroll * distance in logical pixels or mm, depending on the device type. */ double ei_event_scroll_get_dy(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_SCROLL_CANCEL return whether the * x axis has cancelled scrolling. */ bool ei_event_scroll_get_stop_x(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_SCROLL_STOP return whether the * y axis has stopped scrolling. */ bool ei_event_scroll_get_stop_y(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_SCROLL_DISCRETE return the x * scroll distance in fractions or multiples of 120. */ int32_t ei_event_scroll_get_discrete_dx(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_SCROLL_DISCRETE return the y * scroll distance in fractions or multiples of 120. */ int32_t ei_event_scroll_get_discrete_dy(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_KEYBOARD_KEY return the key code (as * defined in include/linux/input-event-codes.h). */ uint32_t ei_event_keyboard_get_key(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_KEYBOARD_KEY return true if the * event is a key down, false for a release. */ bool ei_event_keyboard_get_key_is_press(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_TOUCH_DOWN, @ref * EI_EVENT_TOUCH_MOTION, or @ref EI_EVENT_TOUCH_UP, return the tracking * ID of the touch. * * The tracking ID is a unique identifier for a touch and is valid from * touch down through to touch up but may be re-used in the future. * The tracking ID is randomly assigned to a touch, a client * must not expect any specific value. */ uint32_t ei_event_touch_get_id(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_TOUCH_DOWN, or @ref * EI_EVENT_TOUCH_MOTION, return the x coordinate of the touch * in logical pixels or mm, depending on the device type. */ double ei_event_touch_get_x(struct ei_event *event); /** * @ingroup libei-receiver * * For an event of type @ref EI_EVENT_TOUCH_DOWN, or @ref * EI_EVENT_TOUCH_MOTION, return the y coordinate of the touch * in logical pixels or mm, depending on the device type. */ double ei_event_touch_get_y(struct ei_event *event); /** * @} */ #ifdef __cplusplus } #endif libei-1.2.1/src/libeis-button.c000066400000000000000000000062511456005336000163200ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 button WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_button_destroy(struct eis_button *button) { struct eis_client * client = eis_button_get_client(button); eis_client_unregister_object(client, &button->proto_object); } OBJECT_IMPLEMENT_REF(eis_button); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_button); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_button, proto_object, const struct brei_object *); static OBJECT_IMPLEMENT_CREATE(eis_button); static OBJECT_IMPLEMENT_PARENT(eis_button, eis_device); uint32_t eis_button_get_version(struct eis_button *button) { return button->proto_object.version; } object_id_t eis_button_get_id(struct eis_button *button) { return button->proto_object.id; } struct eis_device * eis_button_get_device(struct eis_button *button) { return eis_button_parent(button); } struct eis_client* eis_button_get_client(struct eis_button *button) { return eis_device_get_client(eis_button_get_device(button)); } struct eis* eis_button_get_context(struct eis_button *button) { struct eis_client *client = eis_button_get_client(button); return eis_client_get_context(client); } const struct eis_button_interface * eis_button_get_interface(struct eis_button *button) { return eis_device_get_button_interface(eis_button_get_device(button)); } struct eis_button * eis_button_new(struct eis_device *device) { struct eis_button *button = eis_button_create(&device->object); struct eis_client *client = eis_device_get_client(device); button->proto_object.id = eis_client_get_new_id(client); button->proto_object.implementation = button; button->proto_object.interface = &eis_button_proto_interface; button->proto_object.version = client->interface_versions.ei_button; list_init(&button->proto_object.link); eis_client_register_object(client, &button->proto_object); return button; /* ref owned by caller */ } libei-1.2.1/src/libeis-button.h000066400000000000000000000037601456005336000163270ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 button WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "brei-shared.h" #include "libeis-client.h" struct eis; struct eis_client; /* This is a protocol-only object, not exposed in the API */ struct eis_button { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(eis_button, context, struct eis *); OBJECT_DECLARE_GETTER(eis_button, device, struct eis_device *); OBJECT_DECLARE_GETTER(eis_button, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_button, id, object_id_t); OBJECT_DECLARE_GETTER(eis_button, version, uint32_t); OBJECT_DECLARE_GETTER(eis_button, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_button, interface, const struct eis_button_interface *); OBJECT_DECLARE_REF(eis_button); OBJECT_DECLARE_UNREF(eis_button); struct eis_button * eis_button_new(struct eis_device *device); libei-1.2.1/src/libeis-callback.c000066400000000000000000000062551456005336000165450ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 callback WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_callback_destroy(struct eis_callback *callback) { struct eis_client * client = eis_callback_get_client(callback); eis_client_unregister_object(client, &callback->proto_object); } OBJECT_IMPLEMENT_REF(eis_callback); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_callback); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_callback, proto_object, const struct brei_object *); OBJECT_IMPLEMENT_GETTER(eis_callback, user_data, void *); OBJECT_IMPLEMENT_SETTER(eis_callback, user_data, void *); static OBJECT_IMPLEMENT_CREATE(eis_callback); static OBJECT_IMPLEMENT_PARENT(eis_callback, eis_client); struct eis_client* eis_callback_get_client(struct eis_callback *callback) { return eis_callback_parent(callback); } struct eis* eis_callback_get_context(struct eis_callback *callback) { struct eis_client *client = eis_callback_parent(callback); return eis_client_get_context(client); } object_id_t eis_callback_get_id(struct eis_callback *callback) { return callback->proto_object.id; } uint32_t eis_callback_get_version(struct eis_callback *callback) { return callback->proto_object.version; } static const struct eis_callback_interface interface = { }; const struct eis_callback_interface * eis_callback_get_interface(struct eis_callback *callback) { return &interface; } struct eis_callback * eis_callback_new(struct eis_client *client, uint32_t new_id, uint32_t version) { struct eis_callback *callback = eis_callback_create(&client->object); callback->proto_object.id = new_id; callback->proto_object.implementation = callback; callback->proto_object.interface = &eis_callback_proto_interface; callback->proto_object.version = version; list_init(&callback->proto_object.link); eis_client_register_object(client, &callback->proto_object); return callback; /* ref owned by caller */ } libei-1.2.1/src/libeis-callback.h000066400000000000000000000050521456005336000165440ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 callback WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "brei-shared.h" struct eis; struct eis_client; struct eis_callback; typedef void (*eis_callback_func)(struct eis_callback *callback, void *callback_data, uint32_t proto_data); /* This is a protocol-only object, not exposed in the API */ struct eis_callback { struct object object; struct brei_object proto_object; void *user_data; /* Note: user-data is attached to the object */ struct list link; /* for use by the callers, if needed */ eis_callback_func func; void *callback_data; /* Note: callback-data is attached to the callback */ }; OBJECT_DECLARE_GETTER(eis_callback, context, struct eis *); OBJECT_DECLARE_GETTER(eis_callback, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_callback, id, object_id_t); OBJECT_DECLARE_GETTER(eis_callback, version, uint32_t); OBJECT_DECLARE_GETTER(eis_callback, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_callback, interface, const struct eis_callback_interface *); OBJECT_DECLARE_GETTER(eis_callback, user_data, void*); OBJECT_DECLARE_SETTER(eis_callback, user_data, void*); OBJECT_DECLARE_REF(eis_callback); OBJECT_DECLARE_UNREF(eis_callback); struct eis_callback * eis_callback_new(struct eis_client *client, uint32_t new_id, uint32_t version); struct eis_callback * eis_callback_new_with_callback(struct eis_client *eis_client, eis_callback_func func, void *callback_data); libei-1.2.1/src/libeis-client.c000066400000000000000000000332211456005336000162600ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include "util-bits.h" #include "util-io.h" #include "util-macros.h" #include "util-mem.h" #include "util-sources.h" #include "util-strings.h" #include "util-structs.h" #include "util-tristate.h" #include "util-time.h" #include "util-version.h" #include "libeis-private.h" #include "brei-shared.h" #include "eis-proto.h" DEFINE_TRISTATE(started, finished, connected); DEFINE_UNREF_CLEANUP_FUNC(brei_result); static void client_drop_seats(struct eis_client *client); static void eis_client_destroy(struct eis_client *client) { client_drop_seats(client); eis_handshake_unref(client->setup); eis_connection_unref(client->connection); free(client->name); brei_context_unref(client->brei); source_remove(client->source); source_unref(client->source); list_remove(&client->link); } static OBJECT_IMPLEMENT_CREATE(eis_client); static OBJECT_IMPLEMENT_PARENT(eis_client, eis); _public_ OBJECT_IMPLEMENT_REF(eis_client); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_client); _public_ OBJECT_IMPLEMENT_GETTER(eis_client, name, const char*); _public_ OBJECT_IMPLEMENT_SETTER(eis_client, user_data, void*); _public_ OBJECT_IMPLEMENT_GETTER(eis_client, user_data, void*); uint32_t eis_client_get_next_serial(struct eis_client *client) { return ++client->serial; } void eis_client_update_client_serial(struct eis_client *client, uint32_t serial) { client->last_client_serial = serial; } _public_ struct eis* eis_client_get_context(struct eis_client *client) { return eis_client_parent(client); } const struct brei_object * eis_client_get_proto_object(struct eis_client *client) { return &client->connection->proto_object; } object_id_t eis_client_get_new_id(struct eis_client *client) { static const uint64_t mask = -1 >> 8; static const uint64_t offset = 0xff00000000000000; return offset | (client->next_object_id++ & mask); } bool eis_client_update_client_object_id(struct eis_client *client, object_id_t id) { if (id <= client->last_client_object_id) return false; client->last_client_object_id = id; return true; } 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); list_append(&client->proto_objects, &object->link); } 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); list_remove(&object->link); } struct eis_client * eis_client_get_client(struct eis_client *client) { return client; } _public_ bool eis_client_is_sender(struct eis_client *client) { return client->is_sender; } 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'", object->id, object->interface->name, object->interface->version, object->interface->events[opcode].name, opcode, signature); va_list args; va_start(args, nargs); _unref_(brei_result) *result = brei_marshal_message(client->brei, object->id, opcode, signature, nargs, args); va_end(args); if (brei_result_get_reason(result) != 0) { log_warn(eis, "failed to marshal message: %s", brei_result_get_explanation(result)); return -EBADMSG; } _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); return rc < 0 ? rc : 0; } static int client_send_seat_added(struct eis_client *client, struct eis_seat *seat) { /* Client didn't announce ei_seat */ if (client->interface_versions.ei_seat == 0) return 0; return eis_connection_event_seat(client->connection, eis_seat_get_id(seat), eis_seat_get_version(seat)); } _public_ void eis_client_connect(struct eis_client *client) { switch(client->state) { case EIS_CLIENT_STATE_DISCONNECTED: return; case EIS_CLIENT_STATE_CONNECTING: break; default: log_bug_client(eis_client_get_context(client), "%s: client already connected", __func__); return; } client->state = EIS_CLIENT_STATE_CONNECTED; } static void client_drop_seats(struct eis_client *client) { struct eis_seat *s; list_for_each_safe(s, &client->seats, link) { eis_seat_drop(s); } } static void client_disconnect(struct eis_client *client, enum eis_connection_disconnect_reason reason, const char *explanation) { switch(client->state) { case EIS_CLIENT_STATE_DISCONNECTED: /* Client already disconnected? don't bother sending an * event */ return; case EIS_CLIENT_STATE_REQUESTED_DISCONNECT: /* Drop the seats again (see client_msg_disconnect) because * the server may have added seats between the client requesting the * disconnect and EIS actually processing that event */ client_drop_seats(client); /* DISCONNECTED event is already in the EIS queue */ /* Must not send disconnected to the client */ client->connection = eis_connection_unref(client->connection); client->state = EIS_CLIENT_STATE_DISCONNECTED; source_remove(client->source); break; case EIS_CLIENT_STATE_CONNECTING: case EIS_CLIENT_STATE_CONNECTED: client_drop_seats(client); eis_queue_disconnect_event(client); eis_connection_event_disconnected(client->connection, client->last_client_serial, reason, explanation); client->connection = eis_connection_unref(client->connection); client->state = EIS_CLIENT_STATE_DISCONNECTED; source_remove(client->source); break; case EIS_CLIENT_STATE_NEW: client->state = EIS_CLIENT_STATE_DISCONNECTED; source_remove(client->source); break; } eis_client_unref(client); } _public_ void eis_client_disconnect(struct eis_client *client) { client_disconnect(client, EIS_CONNECTION_DISCONNECT_REASON_DISCONNECTED, NULL); } void eis_client_disconnect_with_reason(struct eis_client *client, enum eis_connection_disconnect_reason reason, const char *explanation) { client_disconnect(client, reason, explanation); } void eis_client_setup_done(struct eis_client *client, const char *name, bool is_sender, const struct eis_client_interface_versions *versions) { client->setup = NULL; /* connection object cleans itself up */ client->name = xstrdup(name); client->is_sender = is_sender; client->interface_versions = *versions; client->state = EIS_CLIENT_STATE_CONNECTING; /* We don't queue the connect event yet because we send an extra ping/pong * out, just to make sure that part of the protocol works. * See pong() in libeis-client.c */ } #define DISCONNECT_IF_INVALID_ID(client_, id_) do { \ if (!brei_is_client_id(id_) || !eis_client_update_client_object_id(client, id_)) { \ struct eis *_ctx = eis_client_get_context(client_); \ log_bug_client(_ctx, "Invalid object id %#" PRIx64 ". Disconnecting client", id_); \ return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Invalid object id %#" PRIx64 ".", new_id); \ } \ } while(0) static struct brei_result * client_msg_disconnect(struct eis_connection *connection) { struct eis_client * client = eis_connection_get_client(connection); /* We need to drop the seats because that unrolls the EIS device state * so that EIS gets the correct DEVICE_REMOVED sequence, etc. * Then queue the DISCONNECTED event and wait for EIS to call * eis_client_disconnect() */ client_drop_seats(client); eis_queue_disconnect_event(client); client->state = EIS_CLIENT_STATE_REQUESTED_DISCONNECT; return NULL; } static struct brei_result * 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, version); 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); } static const struct eis_connection_interface intf_state_new = { .sync = client_msg_sync, .disconnect = client_msg_disconnect, }; /* Client is waiting for us, shouldn't send anything except disconnect */ static const struct eis_connection_interface intf_state_connecting = { .sync = client_msg_sync, .disconnect = client_msg_disconnect, }; static const struct eis_connection_interface intf_state_connected = { .sync = client_msg_sync, .disconnect = client_msg_disconnect, }; static const struct eis_connection_interface *interfaces[] = { [EIS_CLIENT_STATE_NEW] = &intf_state_new, [EIS_CLIENT_STATE_CONNECTING] = &intf_state_connecting, [EIS_CLIENT_STATE_CONNECTED] = &intf_state_connected, [EIS_CLIENT_STATE_REQUESTED_DISCONNECT] = NULL, [EIS_CLIENT_STATE_DISCONNECTED] = NULL, }; const struct eis_connection_interface * eis_client_get_interface(struct eis_client *client) { assert(client->state < ARRAY_LENGTH(interfaces)); return interfaces[client->state]; } static int lookup_object(object_id_t object_id, struct brei_object **object, void *userdata) { struct eis_client *client = userdata; struct brei_object *obj; list_for_each(obj, &client->proto_objects, link) { if (obj->id == object_id) { *object = obj; return 0; } } log_debug(eis_client_get_context(client), "Failed to find object %#" PRIx64 "", object_id); if (client->connection) eis_connection_event_invalid_object(client->connection, client->last_client_serial, object_id); return -ENOENT; } static void 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)); 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[] = { "NEW", "CONNECTING", "CONNECTED", "REQUESTED_DISCONNECT", "DISCONNECTED", }; if (old_state != client->state) { assert(old_state < ARRAY_LENGTH(client_states)); assert(client->state < ARRAY_LENGTH(client_states)); log_debug(eis_client_get_context(client), "Client dispatch: %s -> %s", client_states[old_state], client_states[client->state]); } } struct eis_client * eis_client_new(struct eis *eis, int fd) { static uint32_t client_id; struct eis_client *client = eis_client_create(&eis->object); client->brei = brei_context_new(client); brei_context_set_log_context(client->brei, eis); brei_context_set_log_func(client->brei, (brei_logfunc_t)eis_log_msg_va); client->is_sender = true; client->id = ++client_id; list_init(&client->seats); list_init(&client->seats_pending); list_init(&client->proto_objects); 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(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), }; struct source *s = source_new(fd, client_dispatch, client); int rc = sink_add_source(eis->sink, s); if (rc != 0) { source_unref(s); return NULL; } client->source = source_ref(s); client->state = EIS_CLIENT_STATE_NEW; eis_add_client(eis, eis_client_ref(client)); source_unref(s); client->setup = eis_handshake_new(client, &client->interface_versions); return client; } void eis_client_add_seat(struct eis_client *client, struct eis_seat *seat) { /* remove from the pending list first */ list_remove(&seat->link); /* We own this seat now */ eis_seat_ref(seat); list_append(&client->seats, &seat->link); client_send_seat_added(client, seat); } libei-1.2.1/src/libeis-client.h000066400000000000000000000100461456005336000162650ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "libeis.h" #include "brei-shared.h" #include "util-object.h" #include "util-list.h" #include "eis-proto.h" enum eis_client_state { EIS_CLIENT_STATE_NEW, /* socket just handed over */ EIS_CLIENT_STATE_CONNECTING, /* client completed setup but hasn't been accepted yet */ EIS_CLIENT_STATE_CONNECTED, /* caller has done eis_client_connect */ EIS_CLIENT_STATE_REQUESTED_DISCONNECT, /* caller has disconnected */ EIS_CLIENT_STATE_DISCONNECTED, }; struct eis_client_interface_versions { uint32_t ei_connection; uint32_t ei_handshake; uint32_t ei_callback; uint32_t ei_pingpong; uint32_t ei_seat; uint32_t ei_device; uint32_t ei_pointer; uint32_t ei_pointer_absolute; uint32_t ei_scroll; uint32_t ei_button; uint32_t ei_keyboard; uint32_t ei_touchscreen; }; struct eis_client { struct object object; struct brei_context *brei; struct eis_connection *connection; struct list proto_objects; /* struct brei_objects list */ object_id_t next_object_id; object_id_t last_client_object_id; uint32_t serial; uint32_t last_client_serial; struct eis_handshake *setup; struct eis_client_interface_versions interface_versions; void *user_data; struct list link; struct source *source; uint32_t version; uint32_t configure_version; uint32_t id; enum eis_client_state state; char *name; bool is_sender; struct list seats; struct list seats_pending; }; OBJECT_DECLARE_GETTER(eis_client, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_client, interface, const struct eis_connection_interface *); OBJECT_DECLARE_GETTER(eis_client, proto_object, const struct brei_object *); struct eis_client * eis_client_new(struct eis *eis, int fd); uint32_t eis_client_get_next_serial(struct eis_client *client); void eis_client_update_client_serial(struct eis_client *client, uint32_t serial); object_id_t eis_client_get_new_id(struct eis_client *client); bool eis_client_update_client_object_id(struct eis_client *client, object_id_t id); void eis_client_register_object(struct eis_client *client, struct brei_object *object); void eis_client_unregister_object(struct eis_client *client, struct brei_object *object); void eis_add_client(struct eis *eis, struct eis_client *client); void eis_client_setup_done(struct eis_client *client, const char *name, bool is_sender, const struct eis_client_interface_versions *versions); int eis_client_send_message(struct eis_client *client, const struct brei_object *obj, uint32_t opcode, const char *signature, size_t nargs, ...); void eis_client_add_seat(struct eis_client *client, struct eis_seat *seat); void eis_client_keyboard_modifiers(struct eis_client *client, struct eis_device *device, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group); void eis_client_disconnect_with_reason(struct eis_client *client, enum eis_connection_disconnect_reason reason, const char *explanation); libei-1.2.1/src/libeis-connection.c000066400000000000000000000113101456005336000171340ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void 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); } } OBJECT_IMPLEMENT_REF(eis_connection); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_connection); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_connection, proto_object, const struct brei_object *); static OBJECT_IMPLEMENT_CREATE(eis_connection); static OBJECT_IMPLEMENT_PARENT(eis_connection, eis_client); uint32_t eis_connection_get_version(struct eis_connection *connection) { return connection->proto_object.version; } object_id_t eis_connection_get_id(struct eis_connection *connection) { return connection->proto_object.id; } struct eis_client* eis_connection_get_client(struct eis_connection *connection) { return eis_connection_parent(connection); } struct eis* eis_connection_get_context(struct eis_connection *connection) { struct eis_client *client = eis_connection_parent(connection); return eis_client_get_context(client); } const struct eis_connection_interface * eis_connection_get_interface(struct eis_connection *connection) { struct eis_client *client = eis_connection_parent(connection); return eis_client_get_interface(client); } struct eis_connection * eis_connection_new(struct eis_client *client) { struct eis_connection *connection = eis_connection_create(&client->object); connection->proto_object.id = eis_client_get_new_id(client); connection->proto_object.implementation = connection; connection->proto_object.interface = &eis_connection_proto_interface; connection->proto_object.version = client->interface_versions.ei_connection; eis_client_register_object(client, &connection->proto_object); list_init(&connection->pending_pingpongs); return connection; /* ref owned by caller */ } struct pingpong_user_data { eis_connection_ping_callback_t cb; void *user_data; }; static void ping_pingpong(struct eis_pingpong *pingpong, void *pingpong_data, uint64_t proto_data) { struct eis_connection *connection = pingpong_data; _cleanup_free_ struct pingpong_user_data *data = eis_pingpong_get_user_data(pingpong); if (data->cb) data->cb(connection, data->user_data); /* remove from pending callbacks */ list_remove(&pingpong->link); eis_pingpong_unref(pingpong); } void eis_connection_ping(struct eis_connection *connection, eis_connection_ping_callback_t cb, void *user_data) { 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, * 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); list_append(&connection->pending_pingpongs, &pingpong->link); eis_connection_event_ping(connection, eis_pingpong_get_id(pingpong), eis_pingpong_get_version(pingpong)); } libei-1.2.1/src/libeis-connection.h000066400000000000000000000045111456005336000171460ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-object.h" #include "brei-shared.h" struct eis; struct eis_client; /* This is a protocol-only object, not exposed in the API */ struct eis_connection { struct object object; struct brei_object proto_object; struct list pending_pingpongs; }; OBJECT_DECLARE_GETTER(eis_connection, context, struct eis *); OBJECT_DECLARE_GETTER(eis_connection, id, object_id_t); OBJECT_DECLARE_GETTER(eis_connection, version, uint32_t); OBJECT_DECLARE_GETTER(eis_connection, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_connection, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_connection, interface, const struct eis_connection_interface *); OBJECT_DECLARE_REF(eis_connection); OBJECT_DECLARE_UNREF(eis_connection); struct eis_connection * eis_connection_new(struct eis_client *client); /** * Called when the ei_callback.done request is received after * an ei_connection_ping() event. */ typedef void (*eis_connection_ping_callback_t)(struct eis_connection *connection, void *user_data); void eis_connection_ping(struct eis_connection *connection, eis_connection_ping_callback_t callback, void *user_data); libei-1.2.1/src/libeis-device.c000066400000000000000000001142721456005336000162470ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include "util-macros.h" #include "util-bits.h" #include "util-io.h" #include "util-time.h" #include "libeis-private.h" #include "eis-proto.h" static_assert((int)EIS_DEVICE_TYPE_VIRTUAL == EIS_DEVICE_DEVICE_TYPE_VIRTUAL, "ABI mismatch"); static_assert((int)EIS_DEVICE_TYPE_PHYSICAL == EIS_DEVICE_DEVICE_TYPE_PHYSICAL, "ABI mismatch"); _public_ OBJECT_IMPLEMENT_REF(eis_keymap); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_keymap); _public_ OBJECT_IMPLEMENT_GETTER(eis_keymap, type, enum eis_keymap_type); _public_ OBJECT_IMPLEMENT_GETTER(eis_keymap, fd, int); _public_ OBJECT_IMPLEMENT_GETTER(eis_keymap, size, size_t); _public_ OBJECT_IMPLEMENT_GETTER(eis_keymap, device, struct eis_device *); _public_ OBJECT_IMPLEMENT_GETTER(eis_keymap, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(eis_keymap, user_data, void *); static void eis_keymap_destroy(struct eis_keymap *keymap) { if (!keymap->assigned) eis_device_unref(keymap->device); xclose(keymap->fd); } static OBJECT_IMPLEMENT_CREATE(eis_keymap); _public_ struct eis_keymap * eis_device_new_keymap(struct eis_device *device, enum eis_keymap_type type, int fd, size_t size) { switch (type) { case EIS_KEYMAP_TYPE_XKB: break; default: return NULL; } if (fd < 0 || size == 0) return NULL; int newfd = xdup(fd); if (newfd < 0) return NULL; struct eis_keymap *keymap = eis_keymap_create(NULL); keymap->device = eis_device_ref(device); keymap->fd = newfd; keymap->type = type; keymap->size = size; return keymap; } _public_ struct eis * eis_device_get_context(struct eis_device *device) { return eis_client_get_context(eis_device_get_client(device)); } _public_ void eis_keymap_add(struct eis_keymap *keymap) { struct eis_device *device = eis_keymap_get_device(keymap); if (device->state != EIS_DEVICE_STATE_NEW) { log_bug_client(eis_device_get_context(device), "%s: device already (dis)connected", __func__); return; } if (device->keymap) { log_bug_client(eis_device_get_context(device), "%s: only one keymap can only be assigned and only once", __func__); return; } /* New keymap holds ref to the device, for assigned keymap the device * holds the ref to the keymap instead */ device->keymap = eis_keymap_ref(keymap); keymap->assigned = true; eis_device_unref(keymap->device); } _public_ struct eis_keymap * eis_device_keyboard_get_keymap(struct eis_device *device) { return device->keymap; } static void eis_device_destroy(struct eis_device *device) { struct eis_region *r; struct eis_event *event; list_for_each_safe(r, &device->regions, link) eis_region_unref(r); /* regions_new does not own a ref */ eis_keymap_unref(device->keymap); list_for_each_safe(event, &device->pending_event_queue, link) { list_remove(&event->link); eis_event_unref(event); } eis_pointer_unref(device->pointer); eis_touchscreen_unref(device->touchscreen); eis_keyboard_unref(device->keyboard); free(device->name); } _public_ OBJECT_IMPLEMENT_REF(eis_device); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_device); static OBJECT_IMPLEMENT_CREATE(eis_device); static OBJECT_IMPLEMENT_PARENT(eis_device, eis_seat); _public_ OBJECT_IMPLEMENT_GETTER(eis_device, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(eis_device, user_data, void *); _public_ OBJECT_IMPLEMENT_GETTER(eis_device, name, const char *); _public_ OBJECT_IMPLEMENT_GETTER(eis_device, type, enum eis_device_type); _public_ OBJECT_IMPLEMENT_GETTER(eis_device, width, uint32_t); _public_ OBJECT_IMPLEMENT_GETTER(eis_device, height, uint32_t); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_device, proto_object, const struct brei_object *); object_id_t eis_device_get_id(struct eis_device *device) { return device->proto_object.id; } _public_ struct eis_seat * eis_device_get_seat(struct eis_device *device) { return eis_device_parent(device); } _public_ struct eis_region * 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) { return eis_seat_get_client(eis_device_get_seat(device)); } static inline bool eis_device_in_region(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 true; } return false; } static struct brei_result * client_msg_release(struct eis_device *device) { eis_device_closed_by_client(device); return NULL; } #define DISCONNECT_IF_RECEIVER_CONTEXT(device_) do { \ struct eis_client *client_ = eis_device_get_client(device_); \ if (!eis_client_is_sender(client_)) { \ struct eis *_ctx = eis_client_get_context(client_); \ log_bug_client(_ctx, "Invalid event from receiver ei context. Disconnecting client"); \ return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_MODE, "Invalid event from receiver ei context"); \ } \ } while(0) static struct brei_result * client_msg_start_emulating(struct eis_device *device, uint32_t serial, uint32_t sequence) { struct brei_result *result = NULL; eis_client_update_client_serial(eis_device_get_client(device), serial); DISCONNECT_IF_RECEIVER_CONTEXT(device); switch (device->state) { case EIS_DEVICE_STATE_DEAD: case EIS_DEVICE_STATE_CLOSED_BY_CLIENT: case EIS_DEVICE_STATE_NEW: 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); break; case EIS_DEVICE_STATE_RESUMED: eis_queue_device_start_emulating_event(device, sequence); device->state = EIS_DEVICE_STATE_EMULATING; break; case EIS_DEVICE_STATE_PAUSED: /* race condition, that's fine */ break; } return result; } static struct brei_result * client_msg_stop_emulating(struct eis_device *device, uint32_t serial) { struct brei_result *result = NULL; eis_client_update_client_serial(eis_device_get_client(device), serial); DISCONNECT_IF_RECEIVER_CONTEXT(device); switch (device->state) { case EIS_DEVICE_STATE_DEAD: case EIS_DEVICE_STATE_CLOSED_BY_CLIENT: case EIS_DEVICE_STATE_NEW: 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); break; case EIS_DEVICE_STATE_EMULATING: eis_queue_device_stop_emulating_event(device); device->state = EIS_DEVICE_STATE_RESUMED; break; case EIS_DEVICE_STATE_PAUSED: /* race condition, that's fine */ break; } return result; } static struct brei_result * maybe_error_on_device_state(struct eis_device *device, const char *event_type) { switch (device->state) { case EIS_DEVICE_STATE_RESUMED: /* could be a race condition, but it's unlikely unless the * EIS implementation pauses and resumes immediately without * giving the client a chance to catch up. So let's * treat this as error until we see real issues. */ break; case EIS_DEVICE_STATE_PAUSED: /* we paused the device but the client sent us an event * - most likely a race condition, so let's ignore it */ return NULL; case EIS_DEVICE_STATE_EMULATING: return NULL; case EIS_DEVICE_STATE_NEW: 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); } static struct brei_result * client_msg_frame(struct eis_device *device, uint32_t serial, uint64_t time) { eis_client_update_client_serial(eis_device_get_client(device), serial); DISCONNECT_IF_RECEIVER_CONTEXT(device); if (device->state == EIS_DEVICE_STATE_EMULATING) { eis_queue_frame_event(device, time); return NULL; } return maybe_error_on_device_state(device, "frame"); } 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, }; const struct eis_device_interface * eis_device_get_interface(struct eis_device *device) { return &interface; } static struct brei_result * client_msg_pointer_rel(struct eis_pointer *pointer, float x, float y) { struct eis_device *device = eis_pointer_get_device(pointer); DISCONNECT_IF_RECEIVER_CONTEXT(device); if (!eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER)) { return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Pointer rel event for non-pointer device"); } if (device->state == EIS_DEVICE_STATE_EMULATING) { eis_queue_pointer_rel_event(device, x, y); return NULL; } return maybe_error_on_device_state(device, "pointer rel"); } static struct brei_result * client_msg_pointer_abs(struct eis_pointer_absolute *pointer, float x, float y) { struct eis_device *device = eis_pointer_absolute_get_device(pointer); DISCONNECT_IF_RECEIVER_CONTEXT(device); if (!eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) { return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Pointer abs event for non-pointer device"); } if (device->state == EIS_DEVICE_STATE_EMULATING) { if (eis_device_in_region(device, x, y)) eis_queue_pointer_abs_event(device, x, y); return NULL; } return maybe_error_on_device_state(device, "pointer abs"); } static struct brei_result * client_msg_button(struct eis_button *button, uint32_t btn, uint32_t state) { struct eis_device *device = eis_button_get_device(button); DISCONNECT_IF_RECEIVER_CONTEXT(device); if (!eis_device_has_capability(device, EIS_DEVICE_CAP_BUTTON)) { return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Button event for non-button device"); } if (device->state == EIS_DEVICE_STATE_EMULATING) { eis_queue_pointer_button_event(device, btn, !!state); return NULL; } return maybe_error_on_device_state(device, "pointer button"); } static struct brei_result * client_msg_scroll(struct eis_scroll *scroll, float x, float y) { struct eis_device *device = eis_scroll_get_device(scroll); DISCONNECT_IF_RECEIVER_CONTEXT(device); if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Scroll event for non-scroll device"); } if (device->state == EIS_DEVICE_STATE_EMULATING) { eis_queue_pointer_scroll_event(device, x, y); return NULL; } return maybe_error_on_device_state(device, "pointer scroll"); } static struct brei_result * client_msg_scroll_discrete(struct eis_scroll *scroll, int32_t x, int32_t y) { struct eis_device *device = eis_scroll_get_device(scroll); DISCONNECT_IF_RECEIVER_CONTEXT(device); if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Scroll discrete event for non-scroll device"); } if (device->state == EIS_DEVICE_STATE_EMULATING) { eis_queue_pointer_scroll_discrete_event(device, x, y); return NULL; } return maybe_error_on_device_state(device, "pointer scroll discrete"); } static struct brei_result * client_msg_scroll_stop(struct eis_scroll *scroll, uint32_t x, uint32_t y, uint32_t is_cancel) { struct eis_device *device = eis_scroll_get_device(scroll); DISCONNECT_IF_RECEIVER_CONTEXT(device); if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Scroll stop event for non-scroll device"); } if (device->state == EIS_DEVICE_STATE_EMULATING) { if (is_cancel) eis_queue_pointer_scroll_cancel_event(device, !!x, !!y); else eis_queue_pointer_scroll_stop_event(device, !!x, !!y); return NULL; } return maybe_error_on_device_state(device, "pointer scroll stop"); } static struct brei_result * client_msg_pointer_release(struct eis_pointer *pointer) { struct eis_device *device = eis_pointer_get_device(pointer); eis_pointer_event_destroyed(device->pointer, eis_client_get_next_serial(eis_device_get_client(device))); eis_pointer_unref(steal(&device->pointer)); return 0; } static struct brei_result * client_msg_pointer_absolute_release(struct eis_pointer_absolute *pointer) { struct eis_device *device = eis_pointer_absolute_get_device(pointer); eis_pointer_absolute_event_destroyed(device->pointer_absolute, eis_client_get_next_serial(eis_device_get_client(device))); eis_pointer_absolute_unref(steal(&device->pointer_absolute)); return 0; } static struct brei_result * client_msg_scroll_release(struct eis_scroll *scroll) { struct eis_device *device = eis_scroll_get_device(scroll); eis_scroll_event_destroyed(device->scroll, eis_client_get_next_serial(eis_device_get_client(device))); eis_scroll_unref(steal(&device->scroll)); return 0; } static struct brei_result * client_msg_button_release(struct eis_button *button) { struct eis_device *device = eis_button_get_device(button); eis_button_event_destroyed(device->button, eis_client_get_next_serial(eis_device_get_client(device))); eis_button_unref(steal(&device->button)); return 0; } static const struct eis_pointer_interface pointer_interface = { .release = client_msg_pointer_release, .motion_relative = client_msg_pointer_rel, }; static const struct eis_pointer_absolute_interface pointer_absolute_interface = { .release = client_msg_pointer_absolute_release, .motion_absolute = client_msg_pointer_abs, }; static const struct eis_scroll_interface scroll_interface = { .release = client_msg_scroll_release, .scroll = client_msg_scroll, .scroll_discrete = client_msg_scroll_discrete, .scroll_stop = client_msg_scroll_stop, }; static const struct eis_button_interface button_interface = { .release = client_msg_button_release, .button = client_msg_button, }; const struct eis_pointer_interface * eis_device_get_pointer_interface(struct eis_device *device) { return &pointer_interface; } const struct eis_pointer_absolute_interface * eis_device_get_pointer_absolute_interface(struct eis_device *device) { return &pointer_absolute_interface; } const struct eis_scroll_interface * eis_device_get_scroll_interface(struct eis_device *device) { return &scroll_interface; } const struct eis_button_interface * eis_device_get_button_interface(struct eis_device *device) { return &button_interface; } static struct brei_result * client_msg_keyboard_key(struct eis_keyboard *keyboard, uint32_t key, uint32_t state) { struct eis_device *device = eis_keyboard_get_device(keyboard); DISCONNECT_IF_RECEIVER_CONTEXT(device); if (!eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)) { return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Key event for non-keyboard device"); } if (device->state == EIS_DEVICE_STATE_EMULATING) { eis_queue_keyboard_key_event(device, key, !!state); return NULL; } return maybe_error_on_device_state(device, "key"); } static struct brei_result * client_msg_keyboard_release(struct eis_keyboard *keyboard) { struct eis_device *device = eis_keyboard_get_device(keyboard); eis_keyboard_event_destroyed(device->keyboard, eis_client_get_next_serial(eis_device_get_client(device))); eis_keyboard_unref(steal(&device->keyboard)); return 0; } static const struct eis_keyboard_interface keyboard_interface = { .release = client_msg_keyboard_release, .key = client_msg_keyboard_key, }; const struct eis_keyboard_interface * eis_device_get_keyboard_interface(struct eis_device *device) { return &keyboard_interface; } static struct brei_result * client_msg_touch_down(struct eis_touchscreen *touchscreen, uint32_t touchid, float x, float y) { 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 down event for non-touch device"); } if (device->state == EIS_DEVICE_STATE_EMULATING) { eis_queue_touch_down_event(device, touchid, x, y); return NULL; } return maybe_error_on_device_state(device, "touch down"); } static struct brei_result * client_msg_touch_motion(struct eis_touchscreen *touchscreen, uint32_t touchid, float x, float y) { 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 motion event for non-touch device"); } if (device->state == EIS_DEVICE_STATE_EMULATING) { eis_queue_touch_motion_event(device, touchid, x, y); return NULL; } return maybe_error_on_device_state(device, "touch motion"); } static struct brei_result * client_msg_touch_up(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 up event for non-touch device"); } 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_touchscreen_release(struct eis_touchscreen *touchscreen) { struct eis_device *device = eis_touchscreen_get_device(touchscreen); eis_touchscreen_event_destroyed(device->touchscreen, eis_client_get_next_serial(eis_device_get_client(device))); eis_touchscreen_unref(steal(&device->touchscreen)); return NULL; } static const struct eis_touchscreen_interface touchscreen_interface = { .release = client_msg_touchscreen_release, .down = client_msg_touch_down, .motion = client_msg_touch_motion, .up = client_msg_touch_up, }; const struct eis_touchscreen_interface * eis_device_get_touchscreen_interface(struct eis_device *device) { return &touchscreen_interface; } _public_ struct eis_device * eis_seat_new_device(struct eis_seat *seat) { struct eis_device *device = eis_device_create(&seat->object); struct eis_client *client = eis_seat_get_client(seat); device->proto_object.id = eis_client_get_new_id(client); device->proto_object.implementation = device; device->proto_object.interface = &eis_device_proto_interface; device->proto_object.version = client->interface_versions.ei_device; assert(device->proto_object.version != 0); list_init(&device->proto_object.link); device->name = xstrdup("unnamed device"); device->capabilities = 0; device->state = EIS_DEVICE_STATE_NEW; device->type = EIS_DEVICE_TYPE_VIRTUAL; list_init(&device->regions); list_init(&device->regions_new); list_init(&device->pending_event_queue); list_append(&seat->devices, &device->link); return eis_device_ref(device); } _public_ void eis_device_configure_type(struct eis_device *device, enum eis_device_type type) { if (device->state != EIS_DEVICE_STATE_NEW) return; switch (type) { case EIS_DEVICE_TYPE_VIRTUAL: case EIS_DEVICE_TYPE_PHYSICAL: break; default: log_bug_client(eis_device_get_context(device), "Invalid device type %u", type); return; } device->type = type; } _public_ void eis_device_configure_name(struct eis_device *device, const char *name) { if (device->state != EIS_DEVICE_STATE_NEW) return; free(device->name); device->name = xstrdup(name); } _public_ void eis_device_configure_capability(struct eis_device *device, enum eis_device_capability cap) { if (device->state != EIS_DEVICE_STATE_NEW) return; if (!eis_seat_has_capability(eis_device_get_seat(device), cap)) return; switch (cap) { case EIS_DEVICE_CAP_POINTER: case EIS_DEVICE_CAP_POINTER_ABSOLUTE: case EIS_DEVICE_CAP_KEYBOARD: case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: mask_add(device->capabilities, cap); break; } } _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 required for size"); return; } if (width > 2000 || height > 2000) log_warn(eis_device_get_context(device), "Suspicious device size: %ux%umm", width, height); device->width = width; device->height = height; } _public_ void eis_device_add(struct eis_device *device) { struct eis_client *client = eis_device_get_client(device); struct eis_seat *seat = eis_device_get_seat(device); if (device->state != EIS_DEVICE_STATE_NEW) { log_bug_client(eis_device_get_context(device), "%s: device already (dis)connected", __func__); return; } if (!device->capabilities) { log_bug_client(eis_device_get_context(device), "%s: adding device without capabilities", __func__); } device->state = EIS_DEVICE_STATE_PAUSED; 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); if (rc < 0) goto out; rc = eis_device_event_device_type(device, device->type); if (rc < 0) goto out; if (device->type == EIS_DEVICE_TYPE_PHYSICAL) { rc = eis_device_event_dimensions(device, device->width, device->height); if (rc < 0) goto out; } 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; } } if (eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER)) { device->pointer = eis_pointer_new(device); rc = eis_device_event_interface(device, eis_pointer_get_id(device->pointer), EIS_POINTER_INTERFACE_NAME, eis_pointer_get_version(device->pointer)); if (rc < 0) goto out; } if (eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) { device->pointer_absolute = eis_pointer_absolute_new(device); rc = eis_device_event_interface(device, eis_pointer_absolute_get_id(device->pointer_absolute), EIS_POINTER_ABSOLUTE_INTERFACE_NAME, eis_pointer_absolute_get_version(device->pointer_absolute)); if (rc < 0) goto out; } if (eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { device->scroll = eis_scroll_new(device); rc = eis_device_event_interface(device, eis_scroll_get_id(device->scroll), EIS_SCROLL_INTERFACE_NAME, eis_scroll_get_version(device->scroll)); if (rc < 0) goto out; } if (eis_device_has_capability(device, EIS_DEVICE_CAP_BUTTON)) { device->button = eis_button_new(device); rc = eis_device_event_interface(device, eis_button_get_id(device->button), EIS_BUTTON_INTERFACE_NAME, eis_button_get_version(device->button)); if (rc < 0) goto out; } if (eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)) { device->keyboard = eis_keyboard_new(device); rc = eis_device_event_interface(device, eis_keyboard_get_id(device->keyboard), EIS_KEYBOARD_INTERFACE_NAME, eis_keyboard_get_version(device->keyboard)); if (rc < 0) goto out; if (device->keymap) rc = eis_keyboard_event_keymap(device->keyboard, device->keymap->type, device->keymap->size, device->keymap->fd); if (rc < 0) goto out; } if (eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) { device->touchscreen = eis_touchscreen_new(device); rc = eis_device_event_interface(device, eis_touchscreen_get_id(device->touchscreen), EIS_TOUCHSCREEN_INTERFACE_NAME, eis_touchscreen_get_version(device->touchscreen)); if (rc < 0) goto out; } rc = eis_device_event_done(device); out: if (rc < 0) { log_error(eis_client_get_context(client), "Failed to add device, disconnecting client"); eis_client_disconnect(client); } return; } _public_ void eis_device_remove(struct eis_device *device) { struct eis_client *client = eis_device_get_client(device); if (device->state == EIS_DEVICE_STATE_DEAD) return; if (device->state == EIS_DEVICE_STATE_EMULATING && !eis_client_is_sender(eis_device_get_client(device))) eis_device_stop_emulating(device); if (device->pointer) { eis_pointer_event_destroyed(device->pointer, eis_client_get_next_serial(client)); eis_pointer_unref(steal(&device->pointer)); } if (device->pointer_absolute) { eis_pointer_absolute_event_destroyed(device->pointer_absolute, eis_client_get_next_serial(client)); eis_pointer_absolute_unref(steal(&device->pointer_absolute)); } if (device->button) { eis_button_event_destroyed(device->button, eis_client_get_next_serial(client)); eis_button_unref(steal(&device->button)); } if (device->scroll) { eis_scroll_event_destroyed(device->scroll, eis_client_get_next_serial(client)); eis_scroll_unref(steal(&device->scroll)); } if (device->touchscreen) { eis_touchscreen_event_destroyed(device->touchscreen, eis_client_get_next_serial(client)); eis_touchscreen_unref(steal(&device->touchscreen)); } if (device->keyboard) { eis_keyboard_event_destroyed(device->keyboard, eis_client_get_next_serial(client)); eis_keyboard_unref(steal(&device->keyboard)); } if (device->state != EIS_DEVICE_STATE_NEW) eis_device_event_destroyed(device, eis_client_get_next_serial(client)); device->state = EIS_DEVICE_STATE_DEAD; eis_client_unregister_object(client, &device->proto_object); list_remove(&device->link); eis_device_unref(device); } _public_ bool eis_device_has_capability(struct eis_device *device, enum eis_device_capability cap) { switch (cap) { case EIS_DEVICE_CAP_POINTER: case EIS_DEVICE_CAP_POINTER_ABSOLUTE: case EIS_DEVICE_CAP_KEYBOARD: case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: return mask_all(device->capabilities, cap); } return false; } static void eis_device_frame_now(struct eis_device *device) { uint64_t now = eis_now(eis_device_get_context(device)); eis_device_frame(device, now); } static void _flush_frame(struct eis_device *device, const char *func) { if (device->send_frame_event) { log_bug_client(eis_device_get_context(device), "%s: missing call to eis_device_frame()", func); eis_device_frame_now(device); } } #define eis_device_flush_frame(d_) _flush_frame(d_, __func__) _public_ void eis_device_start_emulating(struct eis_device *device, uint32_t sequence) { struct eis_client *client = eis_device_get_client(device); if (device->state != EIS_DEVICE_STATE_RESUMED) return; assert(!device->send_frame_event); device->state = EIS_DEVICE_STATE_EMULATING; eis_device_event_start_emulating(device, eis_client_get_next_serial(client), sequence); } _public_ void eis_device_stop_emulating(struct eis_device *device) { struct eis_client *client = eis_device_get_client(device); if (device->state != EIS_DEVICE_STATE_EMULATING) return; eis_device_flush_frame(device); device->state = EIS_DEVICE_STATE_RESUMED; eis_device_event_stop_emulating(device, eis_client_get_next_serial(client)); } _public_ void eis_device_pointer_motion(struct eis_device *device, double x, double y) { if (!eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER)) { log_bug_client(eis_device_get_context(device), "%s: device is not a pointer", __func__); return; } if (device->state != EIS_DEVICE_STATE_EMULATING) return; device->send_frame_event = true; eis_pointer_event_motion_relative(device->pointer, x, y); } _public_ void eis_device_pointer_motion_absolute(struct eis_device *device, double x, double y) { if (!eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) { log_bug_client(eis_device_get_context(device), "%s: device is not an absolute pointer", __func__); return; } 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; } } device->send_frame_event = true; eis_pointer_absolute_event_motion_absolute(device->pointer_absolute, x, y); } _public_ void eis_device_button_button(struct eis_device *device, uint32_t button, bool is_press) { if (!eis_device_has_capability(device, EIS_DEVICE_CAP_BUTTON)) { log_bug_client(eis_device_get_context(device), "%s: device is not a button device", __func__); return; } if (device->state != EIS_DEVICE_STATE_EMULATING) return; /* Ignore anything < BTN_MOUSE. Avoids the common error of sending * numerical buttons instead of BTN_LEFT and friends. */ if (button < 0x110) { log_bug_client(eis_device_get_context(device), "%s: button code must be one of BTN_*", __func__); return; } device->send_frame_event = true; eis_button_event_button(device->button, button, is_press); } static inline void eis_device_resume_scrolling(struct eis_device *device, double x, double y) { if (x) { device->scroll_state.x_is_stopped = false; device->scroll_state.x_is_cancelled = false; } if (y) { device->scroll_state.y_is_stopped = false; device->scroll_state.y_is_cancelled = false; } } _public_ void eis_device_scroll_delta(struct eis_device *device, double x, double y) { if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { log_bug_client(eis_device_get_context(device), "%s: device is not a scroll device", __func__); } if (device->state != EIS_DEVICE_STATE_EMULATING) return; eis_device_resume_scrolling(device, x, y); device->send_frame_event = true; eis_scroll_event_scroll(device->scroll, x, y); } _public_ void eis_device_scroll_stop(struct eis_device *device, bool x, bool y) { if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { log_bug_client(eis_device_get_context(device), "%s: device is not a scroll device", __func__); } if (device->state != EIS_DEVICE_STATE_EMULATING) return; /* Filter out duplicate scroll stop requests */ if (x && !device->scroll_state.x_is_stopped) device->scroll_state.x_is_stopped = true; else x = false; if (y && !device->scroll_state.y_is_stopped) device->scroll_state.y_is_stopped = true; else y = false; if (x || y) { device->send_frame_event = true; eis_scroll_event_scroll_stop(device->scroll, x, y, false); } } _public_ void eis_device_scroll_cancel(struct eis_device *device, bool x, bool y) { if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { log_bug_client(eis_device_get_context(device), "%s: device is not a scroll device", __func__); } if (device->state != EIS_DEVICE_STATE_EMULATING) return; /* Filter out duplicate scroll cancelled requests */ if (x && !device->scroll_state.x_is_cancelled) { device->scroll_state.x_is_stopped = true; device->scroll_state.x_is_cancelled = true; } else { x = false; } if (y && !device->scroll_state.y_is_cancelled) { device->scroll_state.y_is_stopped = true; device->scroll_state.y_is_cancelled = true; } else { y = false; } if (x || y) { device->send_frame_event = true; eis_scroll_event_scroll_stop(device->scroll, x, y, true); } } _public_ void eis_device_scroll_discrete(struct eis_device *device, int32_t x, int32_t y) { if (!eis_device_has_capability(device, EIS_DEVICE_CAP_SCROLL)) { log_bug_client(eis_device_get_context(device), "%s: device is not a scroll device", __func__); } 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; eis_scroll_event_scroll_discrete(device->scroll, x, y); } _public_ void eis_device_keyboard_key(struct eis_device *device, uint32_t key, bool is_press) { if (!eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)) { log_bug_client(eis_device_get_context(device), "%s: device is not a keyboard", __func__); return; } if (device->state != EIS_DEVICE_STATE_EMULATING) return; device->send_frame_event = true; eis_keyboard_event_key(device->keyboard, key, is_press); } _public_ OBJECT_IMPLEMENT_REF(eis_touch); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_touch); _public_ OBJECT_IMPLEMENT_GETTER(eis_touch, device, struct eis_device*); _public_ OBJECT_IMPLEMENT_GETTER(eis_touch, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(eis_touch, user_data, void *); static void eis_touch_destroy(struct eis_touch *touch) { if (touch->state == TOUCH_IS_DOWN) eis_touch_up(touch); /* Enforce a frame, otherwise we're just pending. If the client * doesn't want this, it needs to eis_touch_up() */ eis_device_frame_now(touch->device); eis_device_unref(touch->device); } static OBJECT_IMPLEMENT_CREATE(eis_touch); _public_ struct eis_touch * eis_device_touch_new(struct eis_device *device) { static uint32_t tracking_id = 0; /* Not using the device as parent object because we need a ref * to it */ struct eis_touch *touch = eis_touch_create(NULL); touch->device = eis_device_ref(device); touch->state = TOUCH_IS_NEW; touch->tracking_id = ++tracking_id; return touch; } _public_ void eis_touch_down(struct eis_touch *touch, double x, double y) { struct eis_device *device = eis_touch_get_device(touch); if (touch->state != TOUCH_IS_NEW) { log_bug_client(eis_device_get_context(device), "%s: touch %u already down or up", __func__, touch->tracking_id); 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; } } touch->state = TOUCH_IS_DOWN; device->send_frame_event = true; eis_touchscreen_event_down(device->touchscreen, touch->tracking_id, x, y); } _public_ void eis_touch_motion(struct eis_touch *touch, double x, double y) { if (touch->state != TOUCH_IS_DOWN) 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; } } device->send_frame_event = true; eis_touchscreen_event_motion(device->touchscreen, touch->tracking_id, x, y); } _public_ void eis_touch_up(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; eis_touchscreen_event_up(device->touchscreen, touch->tracking_id); } _public_ void eis_device_frame(struct eis_device *device, uint64_t time) { struct eis_client *client = eis_device_get_client(device); if (device->state != EIS_DEVICE_STATE_EMULATING) return; if (!device->send_frame_event) return; device->send_frame_event = false; eis_device_event_frame(device, eis_client_get_next_serial(client), time); } void eis_device_closed_by_client(struct eis_device *device) { switch (device->state) { case EIS_DEVICE_STATE_DEAD: case EIS_DEVICE_STATE_CLOSED_BY_CLIENT: /* libei bug, ignore */ break; case EIS_DEVICE_STATE_EMULATING: if (!eis_client_is_sender(eis_device_get_client(device))) eis_queue_device_stop_emulating_event(device); _fallthrough_; case EIS_DEVICE_STATE_NEW: case EIS_DEVICE_STATE_PAUSED: case EIS_DEVICE_STATE_RESUMED: eis_queue_device_closed_event(device); device->state = EIS_DEVICE_STATE_CLOSED_BY_CLIENT; break; } } _public_ void eis_device_pause(struct eis_device *device) { struct eis_client *client = eis_device_get_client(device); if (device->state != EIS_DEVICE_STATE_RESUMED) return; device->state = EIS_DEVICE_STATE_PAUSED; eis_device_event_paused(device, eis_client_get_next_serial(client)); } _public_ void eis_device_resume(struct eis_device *device) { struct eis_client *client = eis_device_get_client(device); if (device->state != EIS_DEVICE_STATE_PAUSED) return; device->state = EIS_DEVICE_STATE_RESUMED; eis_device_event_resumed(device, eis_client_get_next_serial(client)); } _public_ void eis_device_keyboard_send_xkb_modifiers(struct eis_device *device, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { if (!eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)) { log_bug_client(eis_device_get_context(device), "%s: device is not a keyboard", __func__); return; } eis_keyboard_event_modifiers(device->keyboard, eis_client_get_next_serial(eis_device_get_client(device)), depressed, locked, latched, group); } libei-1.2.1/src/libeis-device.h000066400000000000000000000072261456005336000162540ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "libeis.h" #include "util-object.h" #include "util-list.h" #include "brei-shared.h" enum eis_device_state { EIS_DEVICE_STATE_NEW, EIS_DEVICE_STATE_PAUSED, EIS_DEVICE_STATE_RESUMED, EIS_DEVICE_STATE_EMULATING, EIS_DEVICE_STATE_CLOSED_BY_CLIENT, EIS_DEVICE_STATE_DEAD, }; struct eis_device { struct object object; /* parent is ei_seat, and we have a ref to it */ struct list link; struct brei_object proto_object; struct eis_pointer *pointer; struct eis_pointer_absolute *pointer_absolute; struct eis_scroll *scroll; struct eis_button *button; struct eis_keyboard *keyboard; struct eis_touchscreen *touchscreen; char *name; enum eis_device_state state; uint32_t capabilities; void *user_data; enum eis_device_type type; uint32_t width, height; struct list regions; struct list regions_new; /* not yet added */ struct eis_keymap *keymap; struct list pending_event_queue; /* incoming events waiting for a frame */ bool send_frame_event; struct { bool x_is_stopped, y_is_stopped; bool x_is_cancelled, y_is_cancelled; } scroll_state; }; struct eis_touch { struct object object; struct eis_device *device; void *user_data; uint32_t tracking_id; enum { TOUCH_IS_NEW, TOUCH_IS_DOWN, TOUCH_IS_UP, } state; double x, y; }; struct eis_keymap { struct object object; struct eis_device *device; void *user_data; enum eis_keymap_type type; int fd; size_t size; bool assigned; }; struct eis_xkb_modifiers { uint32_t depressed; uint32_t locked; uint32_t latched; uint32_t group; }; OBJECT_DECLARE_GETTER(eis_device, id, object_id_t); 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 *); OBJECT_DECLARE_GETTER(eis_device, pointer_absolute_interface, const struct eis_pointer_absolute_interface *); OBJECT_DECLARE_GETTER(eis_device, scroll_interface, const struct eis_scroll_interface *); OBJECT_DECLARE_GETTER(eis_device, button_interface, const struct eis_button_interface *); OBJECT_DECLARE_GETTER(eis_device, keyboard_interface, const struct eis_keyboard_interface *); OBJECT_DECLARE_GETTER(eis_device, touchscreen_interface, const struct eis_touchscreen_interface *); void eis_device_set_client_keymap(struct eis_device *device, enum eis_keymap_type type, int keymap_fd, size_t size); void eis_device_closed_by_client(struct eis_device *device); libei-1.2.1/src/libeis-event.c000066400000000000000000000234101456005336000161220ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include "util-bits.h" #include "util-object.h" #include "util-macros.h" #include "libeis-private.h" static void eis_event_destroy(struct eis_event *event) { bool handled = false; switch (event->type) { case EIS_EVENT_CLIENT_CONNECT: case EIS_EVENT_CLIENT_DISCONNECT: case EIS_EVENT_SEAT_BIND: case EIS_EVENT_DEVICE_CLOSED: case EIS_EVENT_DEVICE_START_EMULATING: case EIS_EVENT_DEVICE_STOP_EMULATING: case EIS_EVENT_BUTTON_BUTTON: case EIS_EVENT_POINTER_MOTION: case EIS_EVENT_POINTER_MOTION_ABSOLUTE: case EIS_EVENT_SCROLL_DELTA: case EIS_EVENT_SCROLL_STOP: case EIS_EVENT_SCROLL_CANCEL: case EIS_EVENT_SCROLL_DISCRETE: case EIS_EVENT_KEYBOARD_KEY: case EIS_EVENT_TOUCH_DOWN: case EIS_EVENT_TOUCH_MOTION: case EIS_EVENT_TOUCH_UP: case EIS_EVENT_FRAME: handled = true; break; } if (!handled) abort(); /* not yet implemented */ event->device = eis_device_unref(event->device); event->seat = eis_seat_unref(event->seat); event->client = eis_client_unref(event->client); } static OBJECT_IMPLEMENT_CREATE(eis_event); struct eis_event * eis_event_new_for_client(struct eis_client *client) { struct eis *eis = eis_client_get_context(client); struct eis_event *e = eis_event_create(&eis->object); e->client = eis_client_ref(client); return e; } struct eis_event * eis_event_new_for_seat(struct eis_seat *seat) { struct eis_client *client = eis_seat_get_client(seat); struct eis *eis = eis_client_get_context(client); struct eis_event *e = eis_event_create(&eis->object); e->client = eis_client_ref(client); e->seat = eis_seat_ref(seat); return e; } struct eis_event * eis_event_new_for_device(struct eis_device *device) { struct eis_client *client = eis_device_get_client(device); struct eis *eis = eis_client_get_context(client); struct eis_event *e = eis_event_create(&eis->object); e->client = eis_client_ref(client); e->seat = eis_seat_ref(eis_device_get_seat(device)); e->device = eis_device_ref(device); return e; } /* this one is not public */ OBJECT_IMPLEMENT_REF(eis_event); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_event); _public_ OBJECT_IMPLEMENT_GETTER(eis_event, type, enum eis_event_type); _public_ OBJECT_IMPLEMENT_GETTER(eis_event, client, struct eis_client*); _public_ OBJECT_IMPLEMENT_GETTER(eis_event, seat, struct eis_seat*); _public_ OBJECT_IMPLEMENT_GETTER(eis_event, device, struct eis_device*); static OBJECT_IMPLEMENT_PARENT(eis_event, eis); struct eis * eis_event_get_context(struct eis_event *event) { return eis_event_parent(event); } static inline bool check_event_type(struct eis_event *event, const char *function_name, ...) { bool rc = false; va_list args; unsigned int type_permitted; enum eis_event_type type = eis_event_get_type(event); va_start(args, function_name); type_permitted = va_arg(args, unsigned int); while (type_permitted != (unsigned int)-1) { if (type_permitted == type) { rc = true; break; } type_permitted = va_arg(args, unsigned int); } va_end(args); if (!rc) log_bug_client(eis_event_get_context(event), "Invalid event type %u passed to %s()", type, function_name); return rc; } #define require_event_type(event_, retval_, ...) \ if (!check_event_type(event_, __func__, __VA_ARGS__, -1)) \ return retval_; \ _public_ uint64_t eis_event_get_time(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_STOP, EIS_EVENT_SCROLL_CANCEL, EIS_EVENT_SCROLL_DISCRETE, EIS_EVENT_KEYBOARD_KEY, EIS_EVENT_TOUCH_DOWN, EIS_EVENT_TOUCH_UP, EIS_EVENT_TOUCH_MOTION, EIS_EVENT_FRAME); return event->timestamp; } _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); switch (cap) { case EIS_DEVICE_CAP_POINTER: case EIS_DEVICE_CAP_POINTER_ABSOLUTE: case EIS_DEVICE_CAP_KEYBOARD: case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: return mask_all(event->bind.capabilities, cap); } return false; } _public_ uint32_t eis_event_emulating_get_sequence(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_DEVICE_START_EMULATING); return event->start_emulating.sequence; } _public_ double eis_event_pointer_get_dx(struct eis_event *event) { require_event_type(event, 0.0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.dx; } _public_ double eis_event_pointer_get_dy(struct eis_event *event) { require_event_type(event, 0.0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.dy; } _public_ double eis_event_pointer_get_absolute_x(struct eis_event *event) { require_event_type(event, 0.0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.absx; } _public_ double eis_event_pointer_get_absolute_y(struct eis_event *event) { require_event_type(event, 0.0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.absy; } _public_ uint32_t eis_event_button_get_button(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.button; } _public_ bool eis_event_button_get_is_press(struct eis_event *event) { require_event_type(event, false, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.button_is_press; } _public_ double eis_event_scroll_get_dx(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.sx; } _public_ double eis_event_scroll_get_dy(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.sy; } _public_ int32_t eis_event_scroll_get_discrete_dx(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.sdx; } _public_ int32_t eis_event_scroll_get_discrete_dy(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_POINTER_MOTION, EIS_EVENT_POINTER_MOTION_ABSOLUTE, EIS_EVENT_BUTTON_BUTTON, EIS_EVENT_SCROLL_DELTA, EIS_EVENT_SCROLL_DISCRETE); return event->pointer.sdy; } _public_ bool eis_event_scroll_get_stop_x(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_SCROLL_STOP, EIS_EVENT_SCROLL_CANCEL); return event->pointer.stop_x; } _public_ bool eis_event_scroll_get_stop_y(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_SCROLL_STOP, EIS_EVENT_SCROLL_CANCEL); return event->pointer.stop_y; } _public_ uint32_t eis_event_keyboard_get_key(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_KEYBOARD_KEY); return event->keyboard.key; } _public_ bool eis_event_keyboard_get_key_is_press(struct eis_event *event) { require_event_type(event, false, EIS_EVENT_KEYBOARD_KEY); return event->keyboard.key_is_press; } _public_ uint32_t eis_event_touch_get_id(struct eis_event *event) { require_event_type(event, 0, EIS_EVENT_TOUCH_DOWN, EIS_EVENT_TOUCH_UP, EIS_EVENT_TOUCH_MOTION); return event->touch.touchid; } _public_ double eis_event_touch_get_x(struct eis_event *event) { require_event_type(event, 0.0, EIS_EVENT_TOUCH_DOWN, EIS_EVENT_TOUCH_MOTION); return event->touch.x; } _public_ double eis_event_touch_get_y(struct eis_event *event) { require_event_type(event, 0.0, EIS_EVENT_TOUCH_DOWN, EIS_EVENT_TOUCH_MOTION); return event->touch.y; } libei-1.2.1/src/libeis-event.h000066400000000000000000000044101456005336000161260ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "libeis.h" #include "util-object.h" #include "util-list.h" struct eis_event { struct object object; enum eis_event_type type; struct list link; struct eis_client *client; struct eis_seat *seat; struct eis_device *device; uint64_t timestamp; union { struct { uint32_t capabilities; } bind; struct { double dx, dy; /* relative motion */ double absx, absy; /* absolute motion */ double sx, sy; /* scroll */ int32_t sdx, sdy; /* discrete scroll */ bool stop_x, stop_y; /* scroll stop */ uint32_t button; bool button_is_press; } pointer; struct { uint32_t key; bool key_is_press; } keyboard; struct { uint32_t touchid; double x, y; } touch; struct { uint32_t sequence; } start_emulating; }; }; struct eis_event * eis_event_new_for_client(struct eis_client *client); struct eis_event * eis_event_new_for_seat(struct eis_seat *seat); struct eis_event * 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); libei-1.2.1/src/libeis-fd.c000066400000000000000000000044771456005336000154060ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include "util-mem.h" #include "util-macros.h" #include "util-sources.h" #include "util-strings.h" #include "libeis.h" #include "libeis-private.h" struct eis_fd { struct object object; }; static inline void eis_fd_destroy(struct eis_fd *eis_fd) { } static OBJECT_IMPLEMENT_CREATE(eis_fd); static OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_fd); static void interface_fd_destroy(struct eis *eis, void *backend) { _unref_(eis_fd) *fd = backend; } static const struct eis_backend_interface interface = { .destroy = interface_fd_destroy, }; _public_ int eis_setup_backend_fd(struct eis *eis) { assert(eis); assert(!eis->backend); eis->backend = eis_fd_create(&eis->object); eis->backend_interface = interface; return 0; } _public_ int eis_backend_fd_add_client(struct eis *eis) { assert(eis); assert(eis->backend); int fds[2]; int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, fds); if (rc == -1) return -errno; struct eis_client *client = eis_client_new(eis, fds[0]); if (client == NULL) return -ENOMEM; eis_client_unref(client); return fds[1]; } libei-1.2.1/src/libeis-handshake.c000066400000000000000000000230311456005336000167260ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_handshake_destroy(struct eis_handshake *setup) { struct eis_client * client = eis_handshake_get_client(setup); eis_client_unregister_object(client, &setup->proto_object); free(setup->name); } OBJECT_IMPLEMENT_REF(eis_handshake); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_handshake); OBJECT_IMPLEMENT_GETTER(eis_handshake, version, uint32_t); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_handshake, proto_object, const struct brei_object *); static OBJECT_IMPLEMENT_CREATE(eis_handshake); static OBJECT_IMPLEMENT_PARENT(eis_handshake, eis_client); struct eis_client* eis_handshake_get_client(struct eis_handshake *setup) { return eis_handshake_parent(setup); } struct eis* eis_handshake_get_context(struct eis_handshake *setup) { struct eis_client *client = eis_handshake_parent(setup); return eis_client_get_context(client); } object_id_t eis_handshake_get_id(struct eis_handshake *setup) { return setup->proto_object.id; } static void pong(struct eis_connection *connection, void *user_data) { struct eis_client *client = eis_connection_get_client(connection); eis_queue_connect_event(client); } static struct brei_result* client_msg_handshake_version(struct eis_handshake *setup, uint32_t version) { struct eis_client *client = eis_handshake_get_client(setup); struct eis *eis = eis_client_get_context(client); log_debug(eis, "client %#x supports handshake version %u", client->id, version); if (version == 0) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE, "Invalid handshake version %u", version); if (setup->client_versions.ei_handshake != 0) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Duplicate handshake version"); if (version > setup->server_versions.ei_handshake) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Invalid handshake version %ud", version); setup->client_versions.ei_handshake = min(setup->server_versions.ei_handshake, version); return 0; } static struct brei_result * client_msg_finish(struct eis_handshake *setup) { struct eis_client *client = eis_handshake_get_client(setup); /* Required interfaces - immediate disconnection if missing */ if (setup->client_versions.ei_handshake == 0 || setup->client_versions.ei_connection == 0 || setup->client_versions.ei_callback == 0 || setup->client_versions.ei_pingpong == 0) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Missing versions for required interfaces"); /* 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_) \ 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_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); client->connection = eis_connection_new(client); eis_handshake_event_connection(setup, eis_client_get_next_serial(client), eis_connection_get_id(client->connection), eis_connection_get_version(client->connection)); /* These aren't required but libei is pointless without them, so let's enforce them * by establishing the connection and immediately sending the disconnect */ if (setup->client_versions.ei_seat == 0 || setup->client_versions.ei_device == 0) { eis_client_disconnect(client); } else { /* 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); } client->setup = eis_handshake_unref(setup); return NULL; } static struct brei_result * client_msg_name(struct eis_handshake *setup, const char *name) { if (setup->client_versions.ei_handshake == 0) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Missing handshake versions"); if (setup->name) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Duplicate client name"); setup->name = xstrdup(name); return 0; } static struct brei_result * client_msg_context_type(struct eis_handshake *setup, uint32_t type) { if (setup->client_versions.ei_handshake == 0) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Missing handshake versions"); switch(type) { case EIS_HANDSHAKE_CONTEXT_TYPE_SENDER: setup->is_sender = true; return NULL; case EIS_HANDSHAKE_CONTEXT_TYPE_RECEIVER: setup->is_sender = false; return NULL; } return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE, "Invalid context type %u", type); } static struct brei_result * client_msg_interface_version(struct eis_handshake *setup, const char *name, uint32_t version) { if (setup->client_versions.ei_handshake == 0) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Missing handshake versions"); if (streq(name, EIS_HANDSHAKE_INTERFACE_NAME)) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "%s may not be used in interface_version", name); struct eis_client *client = eis_handshake_get_client(setup); struct eis *eis = eis_client_get_context(client); struct v { const char *name; uint32_t* client_version; uint32_t* server_version; } version_map[] = { #define VERSION_ENTRY(name_) { \ .name = #name_, \ .client_version = &setup->client_versions.name_, \ .server_version = &setup->server_versions.name_, \ } VERSION_ENTRY(ei_callback), VERSION_ENTRY(ei_pingpong), VERSION_ENTRY(ei_connection), /* ei_handshake is not handled here */ VERSION_ENTRY(ei_seat), VERSION_ENTRY(ei_device), VERSION_ENTRY(ei_pointer), VERSION_ENTRY(ei_pointer_absolute), VERSION_ENTRY(ei_button), VERSION_ENTRY(ei_scroll), VERSION_ENTRY(ei_keyboard), VERSION_ENTRY(ei_touchscreen), #undef VERSION_ENTRY }; log_debug(eis, "client %#x supports %s version %u", client->id, name, version); if (version == 0) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE, "Invalid %s version %u", name, version); struct v *v; ARRAY_FOR_EACH(version_map, v) { if (streq(v->name, name)) { /* Versions must not be set twice */ if (*v->client_version != 0) return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Duplicate %s version", name); *v->client_version = min(*v->server_version, version); return 0; } } /* Unknown interfaces are ignored */ return 0; } static const struct eis_handshake_interface interface = { .handshake_version = client_msg_handshake_version, .finish = client_msg_finish, .context_type = client_msg_context_type, .name = client_msg_name, .interface_version = client_msg_interface_version, }; const struct eis_handshake_interface * eis_handshake_get_interface(struct eis_handshake *setup) { return &interface; } struct eis_handshake * eis_handshake_new(struct eis_client *client, const struct eis_client_interface_versions *versions) { struct eis_handshake *setup = eis_handshake_create(&client->object); setup->proto_object.id = 0; setup->proto_object.implementation = setup; setup->proto_object.interface = &eis_handshake_proto_interface; /* This object is always v1 until the client tells us otherwise */ setup->proto_object.version = VERSION_V(1); list_init(&setup->proto_object.link); setup->version = VERSION_V(1); /* our ei-handshake version */ setup->server_versions = *versions; eis_client_register_object(client, &setup->proto_object); eis_handshake_event_handshake_version(setup, versions->ei_handshake); return setup; /* ref owned by caller */ } libei-1.2.1/src/libeis-handshake.h000066400000000000000000000042641456005336000167420ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-object.h" #include "brei-shared.h" #include "libeis-client.h" struct eis; struct eis_client; /* This is a protocol-only object, not exposed in the API */ struct eis_handshake { struct object object; struct brei_object proto_object; uint32_t version; char *name; bool is_sender; struct eis_client_interface_versions client_versions; struct eis_client_interface_versions server_versions; }; OBJECT_DECLARE_GETTER(eis_handshake, context, struct eis *); OBJECT_DECLARE_GETTER(eis_handshake, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_handshake, id, object_id_t); OBJECT_DECLARE_GETTER(eis_handshake, version, uint32_t); OBJECT_DECLARE_GETTER(eis_handshake, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_handshake, interface, const struct eis_handshake_interface *); OBJECT_DECLARE_REF(eis_handshake); OBJECT_DECLARE_UNREF(eis_handshake); struct eis_handshake * eis_handshake_new(struct eis_client *client, const struct eis_client_interface_versions *versions); libei-1.2.1/src/libeis-keyboard.c000066400000000000000000000064331456005336000166070ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 keyboard WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_keyboard_destroy(struct eis_keyboard *keyboard) { struct eis_client * client = eis_keyboard_get_client(keyboard); eis_client_unregister_object(client, &keyboard->proto_object); } OBJECT_IMPLEMENT_REF(eis_keyboard); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_keyboard); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_keyboard, proto_object, const struct brei_object *); static OBJECT_IMPLEMENT_CREATE(eis_keyboard); static OBJECT_IMPLEMENT_PARENT(eis_keyboard, eis_device); uint32_t eis_keyboard_get_version(struct eis_keyboard *keyboard) { return keyboard->proto_object.version; } object_id_t eis_keyboard_get_id(struct eis_keyboard *keyboard) { return keyboard->proto_object.id; } struct eis_device * eis_keyboard_get_device(struct eis_keyboard *keyboard) { return eis_keyboard_parent(keyboard); } struct eis_client* eis_keyboard_get_client(struct eis_keyboard *keyboard) { return eis_device_get_client(eis_keyboard_get_device(keyboard)); } struct eis* eis_keyboard_get_context(struct eis_keyboard *keyboard) { struct eis_client *client = eis_keyboard_get_client(keyboard); return eis_client_get_context(client); } const struct eis_keyboard_interface * eis_keyboard_get_interface(struct eis_keyboard *keyboard) { return eis_device_get_keyboard_interface(eis_keyboard_get_device(keyboard)); } struct eis_keyboard * eis_keyboard_new(struct eis_device *device) { struct eis_keyboard *keyboard = eis_keyboard_create(&device->object); struct eis_client *client = eis_device_get_client(device); keyboard->proto_object.id = eis_client_get_new_id(client); keyboard->proto_object.implementation = keyboard; keyboard->proto_object.interface = &eis_keyboard_proto_interface; keyboard->proto_object.version = client->interface_versions.ei_keyboard; list_init(&keyboard->proto_object.link); eis_client_register_object(client, &keyboard->proto_object); return keyboard; /* ref owned by caller */ } libei-1.2.1/src/libeis-keyboard.h000066400000000000000000000040141456005336000166050ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 keyboard WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "brei-shared.h" #include "libeis-client.h" struct eis; struct eis_client; /* This is a protocol-only object, not exposed in the API */ struct eis_keyboard { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(eis_keyboard, context, struct eis *); OBJECT_DECLARE_GETTER(eis_keyboard, device, struct eis_device *); OBJECT_DECLARE_GETTER(eis_keyboard, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_keyboard, id, object_id_t); OBJECT_DECLARE_GETTER(eis_keyboard, version, uint32_t); OBJECT_DECLARE_GETTER(eis_keyboard, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_keyboard, interface, const struct eis_keyboard_interface *); OBJECT_DECLARE_REF(eis_keyboard); OBJECT_DECLARE_UNREF(eis_keyboard); struct eis_keyboard * eis_keyboard_new(struct eis_device *device); libei-1.2.1/src/libeis-log.c000066400000000000000000000147551456005336000155760ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include "util-macros.h" #include "util-color.h" #include "util-object.h" #include "util-strings.h" #include "libeis-private.h" struct eis_log_context { const char *file; const char *func; int line; }; _public_ OBJECT_IMPLEMENT_GETTER(eis_log_context, file, const char *); _public_ OBJECT_IMPLEMENT_GETTER(eis_log_context, func, const char *); _public_ OBJECT_IMPLEMENT_GETTER(eis_log_context, line, unsigned int); static void eis_default_log_handler(struct eis *eis, enum eis_log_priority priority, const char *message, struct eis_log_context *ctx) { struct lut { const char *color; const char *prefix; } lut[] = { { .color = ansi_colorcode[RED], .prefix = "", }, /* debug starts at 10 */ { .color = ansi_colorcode[HIGHLIGHT], .prefix = "DEBUG", }, { .color = ansi_colorcode[GREEN], .prefix = "INFO", }, { .color = ansi_colorcode[BLUE], .prefix = "WARN", }, { .color = ansi_colorcode[RED], .prefix = "ERROR", }, }; static time_t last_time = 0; const char *reset_code = ansi_colorcode[RESET]; run_only_once { if (!isatty(STDERR_FILENO)) { struct lut *l; ARRAY_FOR_EACH(lut, l) l->color = ""; reset_code = ""; } } time_t now = time(NULL); char timestamp[64]; if (last_time != now) { struct tm *tm = localtime(&now); strftime(timestamp, sizeof(timestamp), "%T", tm); } else { xsnprintf(timestamp, sizeof(timestamp), "..."); } size_t idx = priority/10; assert(idx < ARRAY_LENGTH(lut)); fprintf(stderr, " EIS: %8s | %s%4s%s | %s\n", timestamp, lut[idx].color, lut[idx].prefix, reset_code, message); last_time = now; } _public_ void eis_log_set_handler(struct eis *eis, eis_log_handler log_handler) { eis->log.handler = log_handler ? log_handler : eis_default_log_handler; } _public_ void eis_log_set_priority(struct eis *eis, enum eis_log_priority priority) { switch (priority) { case EIS_LOG_PRIORITY_DEBUG: case EIS_LOG_PRIORITY_INFO: case EIS_LOG_PRIORITY_WARNING: case EIS_LOG_PRIORITY_ERROR: break; default: abort(); } eis->log.priority = priority; } _public_ enum eis_log_priority eis_log_get_priority(const struct eis *eis) { return eis->log.priority; } void eis_log_msg(struct eis *eis, enum eis_log_priority priority, const char *file, int lineno, const char *func, const char *format, ...) { va_list args; va_start(args, format); eis_log_msg_va(eis, priority, file, lineno, func, format, args); va_end(args); } void eis_log_msg_va(struct eis *eis, enum eis_log_priority priority, const char *file, int lineno, const char *func, const char *format, va_list ap) { if (eis->log.priority > priority) return; _cleanup_free_ char *message = xvaprintf(format, ap); struct eis_log_context ctx = { .file = file, .line = lineno, .func = func, }; eis->log.handler(eis, priority, message, &ctx); } #ifdef _enable_tests_ #include "util-munit.h" struct log_handler_check { enum eis_log_priority min_priority; const char *expected_message; }; static void test_loghandler(struct eis *eis, enum eis_log_priority priority, const char *message, struct eis_log_context *ctx) { struct log_handler_check *check = eis_get_user_data(eis); munit_assert_int(priority, >=, check->min_priority); munit_assert_string_equal(message, check->expected_message); munit_assert_ptr_not_null(ctx); /* Second arg is the line number, if this code ever moves further up, * this test may fail */ munit_assert_int(eis_log_context_get_line(ctx), >, 170); munit_assert_true(strendswith(eis_log_context_get_file(ctx), "libeis-log.c")); munit_assert_string_equal(eis_log_context_get_func(ctx), "test_log_handler"); } MUNIT_TEST(test_log_handler) { struct log_handler_check check = {0}; struct eis *eis = eis_new(&check); munit_assert_ptr_equal(eis->log.handler, eis_default_log_handler); munit_assert_int(eis->log.priority, ==, EIS_LOG_PRIORITY_INFO); eis_log_set_handler(eis, test_loghandler); check.min_priority = EIS_LOG_PRIORITY_INFO; /* default */ check.expected_message = "info message"; log_debug(eis, "default is below this level"); log_info(eis, "info message"); check.expected_message = "error message"; log_error(eis, "error message"); check.min_priority = EIS_LOG_PRIORITY_ERROR; eis_log_set_priority(eis, EIS_LOG_PRIORITY_ERROR); munit_assert_int(eis->log.priority, ==, EIS_LOG_PRIORITY_ERROR); log_error(eis, "error message"); log_warn(eis, "warn message"); log_info(eis, "info message"); log_debug(eis, "debug message"); eis_log_set_priority(eis, EIS_LOG_PRIORITY_DEBUG); /* Make sure they come through at the right priority */ check.min_priority = EIS_LOG_PRIORITY_ERROR; check.expected_message = "error message"; log_error(eis, "error message"); check.min_priority = EIS_LOG_PRIORITY_WARNING; check.expected_message = "warn message"; log_warn(eis, "warn message"); check.min_priority = EIS_LOG_PRIORITY_INFO; check.expected_message = "info message"; log_info(eis, "info message"); check.min_priority = EIS_LOG_PRIORITY_DEBUG; check.expected_message = "debug message"; log_debug(eis, "debug message"); /* Can't capture this easily, so this is mostly just a crash test */ eis_log_set_handler(eis, NULL); munit_assert_ptr_equal(eis->log.handler, eis_default_log_handler); log_debug(eis, "ignore this, testing NULL log handler"); eis_unref(eis); return MUNIT_OK; } #endif libei-1.2.1/src/libeis-pingpong.c000066400000000000000000000067511456005336000166330ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_pingpong_destroy(struct eis_pingpong *pingpong) { struct eis_client * client = eis_pingpong_get_client(pingpong); eis_client_unregister_object(client, &pingpong->proto_object); } OBJECT_IMPLEMENT_REF(eis_pingpong); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_pingpong); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_pingpong, proto_object, const struct brei_object *); OBJECT_IMPLEMENT_GETTER(eis_pingpong, user_data, void *); OBJECT_IMPLEMENT_SETTER(eis_pingpong, user_data, void *); static OBJECT_IMPLEMENT_CREATE(eis_pingpong); static OBJECT_IMPLEMENT_PARENT(eis_pingpong, eis_client); struct eis_client* eis_pingpong_get_client(struct eis_pingpong *pingpong) { return eis_pingpong_parent(pingpong); } struct eis* eis_pingpong_get_context(struct eis_pingpong *pingpong) { struct eis_client *client = eis_pingpong_parent(pingpong); return eis_client_get_context(client); } object_id_t eis_pingpong_get_id(struct eis_pingpong *pingpong) { return pingpong->proto_object.id; } uint32_t eis_pingpong_get_version(struct eis_pingpong *pingpong) { return pingpong->proto_object.version; } static struct brei_result * client_msg_done(struct eis_pingpong *pingpong, uint64_t pingpong_data) { pingpong->func(pingpong, pingpong->pingpong_data, pingpong_data); return NULL; } static const struct eis_pingpong_interface interface = { .done = client_msg_done, }; const struct eis_pingpong_interface * eis_pingpong_get_interface(struct eis_pingpong *pingpong) { return &interface; } struct eis_pingpong * eis_pingpong_new(struct eis_client *client, eis_pingpong_func func, void *pingpong_data) { struct eis_pingpong *pingpong = eis_pingpong_create(&client->object); pingpong->proto_object.id = eis_client_get_new_id(client); pingpong->proto_object.implementation = pingpong; pingpong->proto_object.interface = &eis_pingpong_proto_interface; pingpong->proto_object.version = VERSION_V(1); /* FIXME */ pingpong->pingpong_data = pingpong_data; eis_client_register_object(client, &pingpong->proto_object); pingpong->func = func; list_init(&pingpong->link); return pingpong; /* ref owned by caller */ } libei-1.2.1/src/libeis-pingpong.h000066400000000000000000000047031456005336000166330ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-object.h" #include "brei-shared.h" struct eis; struct eis_client; struct eis_pingpong; typedef void (*eis_pingpong_func)(struct eis_pingpong *pingpong, void *pingpong_data, uint64_t proto_data); /* This is a protocol-only object, not exposed in the API */ struct eis_pingpong { struct object object; struct brei_object proto_object; void *user_data; /* Note: user-data is attached to the object */ struct list link; /* for use by the callers, if needed */ eis_pingpong_func func; void *pingpong_data; /* Note: pingpong-data is attached to the pingpong */ }; OBJECT_DECLARE_GETTER(eis_pingpong, context, struct eis *); OBJECT_DECLARE_GETTER(eis_pingpong, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_pingpong, id, object_id_t); OBJECT_DECLARE_GETTER(eis_pingpong, version, uint32_t); OBJECT_DECLARE_GETTER(eis_pingpong, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_pingpong, interface, const struct eis_pingpong_interface *); OBJECT_DECLARE_GETTER(eis_pingpong, user_data, void*); OBJECT_DECLARE_SETTER(eis_pingpong, user_data, void*); OBJECT_DECLARE_REF(eis_pingpong); OBJECT_DECLARE_UNREF(eis_pingpong); struct eis_pingpong * eis_pingpong_new(struct eis_client *eis_client, eis_pingpong_func func, void *pingpong_data); libei-1.2.1/src/libeis-pointer-absolute.c000066400000000000000000000073461456005336000203070ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_pointer_absolute_destroy(struct eis_pointer_absolute *pointer_absolute) { struct eis_client * client = eis_pointer_absolute_get_client(pointer_absolute); eis_client_unregister_object(client, &pointer_absolute->proto_object); } OBJECT_IMPLEMENT_REF(eis_pointer_absolute); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_pointer_absolute); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_pointer_absolute, proto_object, const struct brei_object *); static OBJECT_IMPLEMENT_CREATE(eis_pointer_absolute); static OBJECT_IMPLEMENT_PARENT(eis_pointer_absolute, eis_device); uint32_t eis_pointer_absolute_get_version(struct eis_pointer_absolute *pointer_absolute) { return pointer_absolute->proto_object.version; } object_id_t eis_pointer_absolute_get_id(struct eis_pointer_absolute *pointer_absolute) { return pointer_absolute->proto_object.id; } struct eis_device * eis_pointer_absolute_get_device(struct eis_pointer_absolute *pointer_absolute) { return eis_pointer_absolute_parent(pointer_absolute); } struct eis_client* eis_pointer_absolute_get_client(struct eis_pointer_absolute *pointer_absolute) { return eis_device_get_client(eis_pointer_absolute_get_device(pointer_absolute)); } struct eis* eis_pointer_absolute_get_context(struct eis_pointer_absolute *pointer_absolute) { struct eis_client *client = eis_pointer_absolute_get_client(pointer_absolute); return eis_client_get_context(client); } const struct eis_pointer_absolute_interface * eis_pointer_absolute_get_interface(struct eis_pointer_absolute *pointer_absolute) { return eis_device_get_pointer_absolute_interface( eis_pointer_absolute_get_device(pointer_absolute)); } struct eis_pointer_absolute * eis_pointer_absolute_new(struct eis_device *device) { struct eis_pointer_absolute *pointer_absolute = eis_pointer_absolute_create(&device->object); struct eis_client *client = eis_device_get_client(device); pointer_absolute->proto_object.id = eis_client_get_new_id(client); pointer_absolute->proto_object.implementation = pointer_absolute; pointer_absolute->proto_object.interface = &eis_pointer_absolute_proto_interface; pointer_absolute->proto_object.version = client->interface_versions.ei_pointer_absolute; list_init(&pointer_absolute->proto_object.link); eis_client_register_object(client, &pointer_absolute->proto_object); return pointer_absolute; /* ref owned by caller */ } libei-1.2.1/src/libeis-pointer-absolute.h000066400000000000000000000041741456005336000203100ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "brei-shared.h" #include "libeis-client.h" struct eis; struct eis_client; /* This is a protocol-only object, not exposed in the API */ struct eis_pointer_absolute { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(eis_pointer_absolute, context, struct eis *); OBJECT_DECLARE_GETTER(eis_pointer_absolute, device, struct eis_device *); OBJECT_DECLARE_GETTER(eis_pointer_absolute, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_pointer_absolute, id, object_id_t); OBJECT_DECLARE_GETTER(eis_pointer_absolute, version, uint32_t); OBJECT_DECLARE_GETTER(eis_pointer_absolute, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_pointer_absolute, interface, const struct eis_pointer_absolute_interface *); OBJECT_DECLARE_REF(eis_pointer_absolute); OBJECT_DECLARE_UNREF(eis_pointer_absolute); struct eis_pointer_absolute * eis_pointer_absolute_new(struct eis_device *device); libei-1.2.1/src/libeis-pointer.c000066400000000000000000000063421456005336000164660ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 pointer WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_pointer_destroy(struct eis_pointer *pointer) { struct eis_client * client = eis_pointer_get_client(pointer); eis_client_unregister_object(client, &pointer->proto_object); } OBJECT_IMPLEMENT_REF(eis_pointer); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_pointer); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_pointer, proto_object, const struct brei_object *); static OBJECT_IMPLEMENT_CREATE(eis_pointer); static OBJECT_IMPLEMENT_PARENT(eis_pointer, eis_device); uint32_t eis_pointer_get_version(struct eis_pointer *pointer) { return pointer->proto_object.version; } object_id_t eis_pointer_get_id(struct eis_pointer *pointer) { return pointer->proto_object.id; } struct eis_device * eis_pointer_get_device(struct eis_pointer *pointer) { return eis_pointer_parent(pointer); } struct eis_client* eis_pointer_get_client(struct eis_pointer *pointer) { return eis_device_get_client(eis_pointer_get_device(pointer)); } struct eis* eis_pointer_get_context(struct eis_pointer *pointer) { struct eis_client *client = eis_pointer_get_client(pointer); return eis_client_get_context(client); } const struct eis_pointer_interface * eis_pointer_get_interface(struct eis_pointer *pointer) { return eis_device_get_pointer_interface(eis_pointer_get_device(pointer)); } struct eis_pointer * eis_pointer_new(struct eis_device *device) { struct eis_pointer *pointer = eis_pointer_create(&device->object); struct eis_client *client = eis_device_get_client(device); pointer->proto_object.id = eis_client_get_new_id(client); pointer->proto_object.implementation = pointer; pointer->proto_object.interface = &eis_pointer_proto_interface; pointer->proto_object.version = client->interface_versions.ei_pointer; list_init(&pointer->proto_object.link); eis_client_register_object(client, &pointer->proto_object); return pointer; /* ref owned by caller */ } libei-1.2.1/src/libeis-pointer.h000066400000000000000000000037761456005336000165030ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 pointer WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "brei-shared.h" #include "libeis-client.h" struct eis; struct eis_client; /* This is a protocol-only object, not exposed in the API */ struct eis_pointer { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(eis_pointer, context, struct eis *); OBJECT_DECLARE_GETTER(eis_pointer, device, struct eis_device *); OBJECT_DECLARE_GETTER(eis_pointer, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_pointer, id, object_id_t); OBJECT_DECLARE_GETTER(eis_pointer, version, uint32_t); OBJECT_DECLARE_GETTER(eis_pointer, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_pointer, interface, const struct eis_pointer_interface *); OBJECT_DECLARE_REF(eis_pointer); OBJECT_DECLARE_UNREF(eis_pointer); struct eis_pointer * eis_pointer_new(struct eis_device *device); libei-1.2.1/src/libeis-private.h000066400000000000000000000130301456005336000164550ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include "util-object.h" #include "libeis.h" #include "brei-shared.h" #include "util-macros.h" #include "util-list.h" #include "util-sources.h" #include "util-structs.h" #include "libeis-button.h" #include "libeis-callback.h" #include "libeis-client.h" #include "libeis-connection.h" #include "libeis-device.h" #include "libeis-event.h" #include "libeis-handshake.h" #include "libeis-keyboard.h" #include "libeis-pingpong.h" #include "libeis-pingpong.h" #include "libeis-pointer-absolute.h" #include "libeis-pointer.h" #include "libeis-region.h" #include "libeis-scroll.h" #include "libeis-seat.h" #include "libeis-touchscreen.h" struct eis_backend_interface { void (*destroy)(struct eis *eis, void *backend); }; struct eis { struct object object; void *user_data; struct sink *sink; struct list clients; struct eis_backend_interface backend_interface; void *backend; struct list event_queue; struct { eis_log_handler handler; enum eis_log_priority priority; } log; eis_clock_now_func clock_now; const struct eis_proto_requests *requests; }; void eis_init_object(struct eis *eis, struct object *parent); int eis_init(struct eis *eis); struct eis * eis_get_context(struct eis *eis); void eis_queue_connect_event(struct eis_client *client); void eis_queue_disconnect_event(struct eis_client *client); void eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities); void eis_queue_device_closed_event(struct eis_device *device); void eis_queue_frame_event(struct eis_device *device, uint64_t time); void eis_queue_device_start_emulating_event(struct eis_device *device, uint32_t sequence); void eis_queue_device_stop_emulating_event(struct eis_device *device); void eis_queue_pointer_rel_event(struct eis_device *device, double x, double y); void eis_queue_pointer_abs_event(struct eis_device *device, double x, double y); void eis_queue_pointer_button_event(struct eis_device *device, uint32_t button, bool is_press); void eis_queue_pointer_scroll_event(struct eis_device *device, double x, double y); void eis_queue_pointer_scroll_discrete_event(struct eis_device *device, int32_t x, int32_t y); void eis_queue_pointer_scroll_stop_event(struct eis_device *device, bool x, bool y); void eis_queue_pointer_scroll_cancel_event(struct eis_device *device, bool x, bool y); void eis_queue_keyboard_key_event(struct eis_device *device, uint32_t key, bool is_press); void eis_queue_touch_down_event(struct eis_device *device, uint32_t touchid, double x, double y); void eis_queue_touch_motion_event(struct eis_device *device, uint32_t touchid, double x, double y); void eis_queue_touch_up_event(struct eis_device *device, uint32_t touchid); _printf_(6, 7) void eis_log_msg(struct eis *eis, enum eis_log_priority priority, const char *file, int lineno, const char *func, const char *format, ...); _printf_(6, 0) void eis_log_msg_va(struct eis *eis, enum eis_log_priority priority, const char *file, int lineno, const char *func, const char *format, va_list args); #define log_debug(T_, ...) \ eis_log_msg((T_), EIS_LOG_PRIORITY_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_info(T_, ...) \ eis_log_msg((T_), EIS_LOG_PRIORITY_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_warn(T_, ...) \ eis_log_msg((T_), EIS_LOG_PRIORITY_WARNING, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_error(T_, ...) \ eis_log_msg((T_), EIS_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_bug(T_, ...) \ 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) libei-1.2.1/src/libeis-region.c000066400000000000000000000130401456005336000162620ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "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); } _public_ OBJECT_IMPLEMENT_REF(eis_region); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_region); _public_ OBJECT_IMPLEMENT_GETTER(eis_region, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(eis_region, user_data, void *); _public_ OBJECT_IMPLEMENT_GETTER(eis_region, x, uint32_t); _public_ OBJECT_IMPLEMENT_GETTER(eis_region, y, uint32_t); _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); _public_ struct eis_region * eis_device_new_region(struct eis_device *device) { switch (device->type) { case EIS_DEVICE_TYPE_VIRTUAL: break; case EIS_DEVICE_TYPE_PHYSICAL: log_bug_client(eis_device_get_context(device), "Regions on physical devices are not supported"); return NULL; } struct eis_region *region = eis_region_create(NULL); region->device = eis_device_ref(device); region->physical_scale = 1.0; list_append(&device->regions_new, ®ion->link); /* initial refcount is owned by caller, so an unref in the caller will * destroy the region immediately */ return region; } _public_ void eis_region_set_offset(struct eis_region *region, uint32_t x, uint32_t y) { if (region->added_to_device) return; region->x = x; region->y = y; } _public_ void eis_region_set_size(struct eis_region *region, uint32_t w, uint32_t h) { if (region->added_to_device) return; region->width = w; region->height = h; } _public_ void eis_region_set_physical_scale(struct eis_region *region, double scale) { if (region->added_to_device) return; if (scale > 0.0) 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) { struct eis_device *device = region->device; if (device->state != EIS_DEVICE_STATE_NEW) { log_bug_client(eis_device_get_context(device), "%s: device already (dis)connected", __func__); return; } if (region->added_to_device) return; region->added_to_device = true; list_remove(®ion->link); list_append(&device->regions, ®ion->link); /* The device now owns a ref to the region and the region no longer * needs a device ref, it will be cleaned up when the device dies */ eis_region_ref(region); eis_device_unref(region->device); } _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 libei-1.2.1/src/libeis-region.h000066400000000000000000000027221456005336000162740ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "libeis.h" #include "util-object.h" #include "util-list.h" struct eis_region { struct object object; struct eis_device *device; void *user_data; bool added_to_device; struct list link; uint32_t x, y; uint32_t width, height; double physical_scale; char *mapping_id; }; libei-1.2.1/src/libeis-scroll.c000066400000000000000000000062511456005336000163030ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 scroll WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_scroll_destroy(struct eis_scroll *scroll) { struct eis_client * client = eis_scroll_get_client(scroll); eis_client_unregister_object(client, &scroll->proto_object); } OBJECT_IMPLEMENT_REF(eis_scroll); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_scroll); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_scroll, proto_object, const struct brei_object *); static OBJECT_IMPLEMENT_CREATE(eis_scroll); static OBJECT_IMPLEMENT_PARENT(eis_scroll, eis_device); uint32_t eis_scroll_get_version(struct eis_scroll *scroll) { return scroll->proto_object.version; } object_id_t eis_scroll_get_id(struct eis_scroll *scroll) { return scroll->proto_object.id; } struct eis_device * eis_scroll_get_device(struct eis_scroll *scroll) { return eis_scroll_parent(scroll); } struct eis_client* eis_scroll_get_client(struct eis_scroll *scroll) { return eis_device_get_client(eis_scroll_get_device(scroll)); } struct eis* eis_scroll_get_context(struct eis_scroll *scroll) { struct eis_client *client = eis_scroll_get_client(scroll); return eis_client_get_context(client); } const struct eis_scroll_interface * eis_scroll_get_interface(struct eis_scroll *scroll) { return eis_device_get_scroll_interface(eis_scroll_get_device(scroll)); } struct eis_scroll * eis_scroll_new(struct eis_device *device) { struct eis_scroll *scroll = eis_scroll_create(&device->object); struct eis_client *client = eis_device_get_client(device); scroll->proto_object.id = eis_client_get_new_id(client); scroll->proto_object.implementation = scroll; scroll->proto_object.interface = &eis_scroll_proto_interface; scroll->proto_object.version = client->interface_versions.ei_scroll; list_init(&scroll->proto_object.link); eis_client_register_object(client, &scroll->proto_object); return scroll; /* ref owned by caller */ } libei-1.2.1/src/libeis-scroll.h000066400000000000000000000037601456005336000163120ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 scroll WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "brei-shared.h" #include "libeis-client.h" struct eis; struct eis_client; /* This is a protocol-only object, not exposed in the API */ struct eis_scroll { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(eis_scroll, context, struct eis *); OBJECT_DECLARE_GETTER(eis_scroll, device, struct eis_device *); OBJECT_DECLARE_GETTER(eis_scroll, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_scroll, id, object_id_t); OBJECT_DECLARE_GETTER(eis_scroll, version, uint32_t); OBJECT_DECLARE_GETTER(eis_scroll, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_scroll, interface, const struct eis_scroll_interface *); OBJECT_DECLARE_REF(eis_scroll); OBJECT_DECLARE_UNREF(eis_scroll); struct eis_scroll * eis_scroll_new(struct eis_device *device); libei-1.2.1/src/libeis-seat.c000066400000000000000000000226461456005336000157470ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-macros.h" #include "util-bits.h" #include "util-strings.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_seat_destroy(struct eis_seat *seat) { struct eis_device *d; /* We expect those to have been removed already*/ list_for_each(d, &seat->devices, link) { assert(!"device list not empty"); } free(seat->name); } _public_ OBJECT_IMPLEMENT_REF(eis_seat); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_seat); static OBJECT_IMPLEMENT_CREATE(eis_seat); static OBJECT_IMPLEMENT_PARENT(eis_seat, eis_client); _public_ OBJECT_IMPLEMENT_GETTER(eis_seat, user_data, void *); _public_ OBJECT_IMPLEMENT_SETTER(eis_seat, user_data, void *); _public_ OBJECT_IMPLEMENT_GETTER(eis_seat, name, const char *); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_seat, proto_object, const struct brei_object *); object_id_t eis_seat_get_id(struct eis_seat *seat) { return seat->proto_object.id; } uint32_t eis_seat_get_version(struct eis_seat *seat) { return seat->proto_object.version; } _public_ struct eis_client * eis_seat_get_client(struct eis_seat *seat) { return eis_seat_parent(seat); } static struct brei_result * client_msg_release(struct eis_seat *seat) { /* There is no public API in libei to remove a seat, and there's no * public API in libeis to know the client has released the seat. it's * too niche to care about. So here we simply pretend it's bound to 0 * and remove it, that should do the trick. */ eis_seat_drop(seat); return NULL; } static struct brei_result * client_msg_bind(struct eis_seat *seat, 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; if (caps & bit(EIS_POINTER_ABSOLUTE_INTERFACE_INDEX)) capabilities |= EIS_DEVICE_CAP_POINTER_ABSOLUTE; if (caps & bit(EIS_KEYBOARD_INTERFACE_INDEX)) capabilities |= EIS_DEVICE_CAP_KEYBOARD; if (caps & bit(EIS_TOUCHSCREEN_INTERFACE_INDEX)) capabilities |= EIS_DEVICE_CAP_TOUCH; if (caps & bit(EIS_BUTTON_INTERFACE_INDEX)) capabilities |= EIS_DEVICE_CAP_BUTTON; if (caps & bit(EIS_SCROLL_INTERFACE_INDEX)) capabilities |= EIS_DEVICE_CAP_SCROLL; eis_seat_bind(seat, capabilities); return NULL; } static const struct eis_seat_interface interface = { .release = client_msg_release, .bind = client_msg_bind, }; const struct eis_seat_interface * 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) { struct eis_seat *seat = eis_seat_create(&client->object); seat->proto_object.id = eis_client_get_new_id(client); seat->proto_object.implementation = seat; seat->proto_object.interface = &eis_seat_proto_interface; seat->proto_object.version = client->interface_versions.ei_seat; list_init(&seat->proto_object.link); seat->state = EIS_SEAT_STATE_PENDING; seat->name = xstrdup(name); list_init(&seat->devices); /* seat is owned by caller until it's added */ list_append(&client->seats_pending, &seat->link); return seat; } _public_ void eis_seat_add(struct eis_seat *seat) { struct eis_client *client = eis_seat_get_client(seat); switch (seat->state) { case EIS_SEAT_STATE_PENDING: break; case EIS_SEAT_STATE_ADDED: case EIS_SEAT_STATE_BOUND: case EIS_SEAT_STATE_REMOVED: case EIS_SEAT_STATE_REMOVED_INTERNALLY: case EIS_SEAT_STATE_DEAD: log_bug_client(eis_client_get_context(client), "%s: seat already added/removed/dead", __func__); return; } seat->state = EIS_SEAT_STATE_ADDED; eis_client_register_object(client, &seat->proto_object); eis_client_add_seat(client, seat); eis_seat_event_name(seat, seat->name); if (seat->capabilities.c_mask & EIS_DEVICE_CAP_POINTER && client->interface_versions.ei_pointer > 0) { uint64_t mask = bit(EIS_POINTER_INTERFACE_INDEX); eis_seat_event_capability(seat, mask, EIS_POINTER_INTERFACE_NAME); mask_add(seat->capabilities.proto_mask, mask); } if (seat->capabilities.c_mask & EIS_DEVICE_CAP_POINTER_ABSOLUTE && client->interface_versions.ei_pointer_absolute > 0) { uint64_t mask = bit(EIS_POINTER_ABSOLUTE_INTERFACE_INDEX); eis_seat_event_capability(seat, mask, EIS_POINTER_ABSOLUTE_INTERFACE_NAME); 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)) { 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); eis_seat_event_capability(seat, mask, EIS_BUTTON_INTERFACE_NAME); mask_add(seat->capabilities.proto_mask, mask); } if (seat->capabilities.c_mask & EIS_DEVICE_CAP_KEYBOARD && client->interface_versions.ei_keyboard > 0) { uint64_t mask = bit(EIS_KEYBOARD_INTERFACE_INDEX); eis_seat_event_capability(seat, mask, EIS_KEYBOARD_INTERFACE_NAME); mask_add(seat->capabilities.proto_mask, mask); } if (seat->capabilities.c_mask & EIS_DEVICE_CAP_TOUCH && client->interface_versions.ei_touchscreen > 0) { uint64_t mask = bit(EIS_TOUCHSCREEN_INTERFACE_INDEX); eis_seat_event_capability(seat, mask, EIS_TOUCHSCREEN_INTERFACE_NAME); mask_add(seat->capabilities.proto_mask, mask); } eis_seat_event_done(seat); } void eis_seat_bind(struct eis_seat *seat, uint32_t caps) { struct eis_client *client = eis_seat_get_client(seat); switch (seat->state) { case EIS_SEAT_STATE_ADDED: case EIS_SEAT_STATE_BOUND: break; case EIS_SEAT_STATE_PENDING: case EIS_SEAT_STATE_REMOVED: case EIS_SEAT_STATE_REMOVED_INTERNALLY: case EIS_SEAT_STATE_DEAD: log_bug_client(eis_client_get_context(client), "%s: seat cannot be bound", __func__); return; } caps &= seat->capabilities.c_mask; seat->state = EIS_SEAT_STATE_BOUND; uint32_t old_caps = seat->capabilities.bound; seat->capabilities.bound = caps; if (old_caps != caps) eis_queue_seat_bind_event(seat, caps); } void eis_seat_drop(struct eis_seat *seat) { if (seat->state == EIS_SEAT_STATE_BOUND) eis_seat_bind(seat, 0); struct eis_device *d; list_for_each_safe(d, &seat->devices, link) { eis_device_remove(d); } eis_seat_event_destroyed(seat, eis_client_get_next_serial(eis_seat_get_client(seat))); seat->state = EIS_SEAT_STATE_REMOVED; list_remove(&seat->link); seat->state = EIS_SEAT_STATE_REMOVED_INTERNALLY; struct eis_client *client = eis_seat_get_client(seat); eis_client_unregister_object(client, &seat->proto_object); eis_seat_unref(seat); } _public_ void eis_seat_remove(struct eis_seat *seat) { struct eis_client *client = eis_seat_get_client(seat); _unref_(eis_seat) *s = eis_seat_ref(seat); switch (seat->state) { case EIS_SEAT_STATE_PENDING: case EIS_SEAT_STATE_ADDED: case EIS_SEAT_STATE_BOUND: eis_seat_drop(s); s->state = EIS_SEAT_STATE_REMOVED; break; case EIS_SEAT_STATE_REMOVED_INTERNALLY: s->state = EIS_SEAT_STATE_REMOVED; break; case EIS_SEAT_STATE_REMOVED: case EIS_SEAT_STATE_DEAD: log_bug_client(eis_client_get_context(client), "%s: seat already removed", __func__); return; } } _public_ void eis_seat_configure_capability(struct eis_seat *seat, enum eis_device_capability cap) { if (seat->state != EIS_SEAT_STATE_PENDING) return; switch (cap) { case EIS_DEVICE_CAP_POINTER: case EIS_DEVICE_CAP_POINTER_ABSOLUTE: case EIS_DEVICE_CAP_KEYBOARD: case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: mask_add(seat->capabilities.c_mask, cap); break; } } _public_ bool eis_seat_has_capability(struct eis_seat *seat, enum eis_device_capability cap) { switch (cap) { case EIS_DEVICE_CAP_POINTER: case EIS_DEVICE_CAP_POINTER_ABSOLUTE: case EIS_DEVICE_CAP_KEYBOARD: case EIS_DEVICE_CAP_TOUCH: case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: return mask_all(seat->capabilities.c_mask, cap); } return false; } libei-1.2.1/src/libeis-seat.h000066400000000000000000000044671456005336000157550ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include "util-object.h" #include "util-list.h" #include "brei-shared.h" enum eis_seat_state { EIS_SEAT_STATE_PENDING, EIS_SEAT_STATE_ADDED, EIS_SEAT_STATE_BOUND, EIS_SEAT_STATE_REMOVED_INTERNALLY, /* Removed internally but eis_seat_remove() may be called */ EIS_SEAT_STATE_REMOVED, /* Removed but still waiting for some devices to be removed */ EIS_SEAT_STATE_DEAD, /* Removed from our list */ }; struct eis_seat { struct object object; /* parent is ei_client */ struct brei_object proto_object; struct list link; void *user_data; enum eis_seat_state state; char *name; 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; }; OBJECT_DECLARE_GETTER(eis_seat, id, object_id_t); OBJECT_DECLARE_GETTER(eis_seat, version, uint32_t); OBJECT_DECLARE_GETTER(eis_seat, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_seat, interface, const struct eis_seat_interface *); void eis_seat_bind(struct eis_seat *seat, uint32_t cap); void eis_seat_drop(struct eis_seat *seat); libei-1.2.1/src/libeis-socket.c000066400000000000000000000115101456005336000162670ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include #include #include "util-io.h" #include "util-object.h" #include "util-mem.h" #include "util-macros.h" #include "util-sources.h" #include "util-strings.h" #include "libeis.h" #include "libeis-private.h" struct eis_socket { struct object object; struct source *listener; char *socketpath; char *lockpath; int lockfd; }; static inline void eis_socket_destroy(struct eis_socket *socket) { source_remove(socket->listener); socket->listener = source_unref(socket->listener); if (socket->lockpath) { unlink(socket->lockpath); xclose(socket->lockfd); free(socket->lockpath); } if (socket->socketpath) { unlink(socket->socketpath); free(socket->socketpath); } } static OBJECT_IMPLEMENT_CREATE(eis_socket); static OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_socket); static OBJECT_IMPLEMENT_PARENT(eis_socket, eis); static void interface_socket_destroy(struct eis *eis, void *backend) { struct eis_socket *socket = backend; eis_socket_unref(socket); } static const struct eis_backend_interface interface = { .destroy = interface_socket_destroy, }; static void listener_dispatch(struct source *source, void *data) { struct eis_socket *socket = data; struct eis *eis = eis_socket_parent(socket); log_debug(eis, "New client connection waiting"); int fd = accept4(source_get_fd(source), NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); if (fd == -1) return; struct eis_client *client = eis_client_new(eis, fd); eis_client_unref(client); } _public_ int eis_setup_backend_socket(struct eis *eis, const char *socketpath) { assert(eis); assert(!eis->backend); assert(socketpath); assert(socketpath[0] != '\0'); _unref_(eis_socket) *eis_socket = eis_socket_create(&eis->object); _cleanup_free_ char *path = NULL; if (socketpath[0] == '/') { path = xstrdup(socketpath); } else { const char *xdg = getenv("XDG_RUNTIME_DIR"); if (!xdg) return -ENOTDIR; path = xaprintf("%s/%s", xdg, socketpath); } /* Create a lockfile first, if that succeeds but the real socket path exists * it's a leftover from an unclean shutdown and we can remove the old * socket file. */ _cleanup_free_ char *lockfile = xaprintf("%s.lock", path); _cleanup_close_ int lockfd = open(lockfile, O_CREAT|O_CLOEXEC|O_RDWR, (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)); int rc = flock(lockfd, LOCK_EX | LOCK_NB); if (rc < 0) { log_error(eis, "Failed to create lockfile %s, is another EIS running?", lockfile); return -errno; } struct stat st; rc = lstat(path, &st); if (rc < 0) { if (errno != ENOENT) { log_error(eis, "Failed to stat socket path %s (%s)", path, strerror(errno)); return -errno; } } else if (st.st_mode & (S_IWUSR|S_IWGRP)) { unlink(path); } /* Lockfile succeeded and path is unlinked (if it existed), let's set * up the socket */ struct sockaddr_un addr = { .sun_family = AF_UNIX, .sun_path = {0}, }; if (!xsnprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path)) return -EINVAL; _cleanup_close_ int sockfd = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0); if (sockfd == -1) return -errno; if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) return -errno; if (listen(sockfd, 2) == -1) return -errno; struct source *s = source_new(sockfd, listener_dispatch, eis_socket); rc = sink_add_source(eis->sink, s); if (rc == 0) { eis_socket->listener = source_ref(s); eis_socket->socketpath = steal(&path); eis_socket->lockpath = steal(&lockfile); eis_socket->lockfd = lockfd; eis->backend = steal(&eis_socket); eis->backend_interface = interface; } source_unref(s); sockfd = -1; lockfd = -1; return rc; } libei-1.2.1/src/libeis-touchscreen.c000066400000000000000000000067061456005336000173340ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2023 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 touchscreen WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "util-bits.h" #include "util-macros.h" #include "util-mem.h" #include "util-io.h" #include "util-strings.h" #include "util-version.h" #include "libeis-private.h" #include "eis-proto.h" static void eis_touchscreen_destroy(struct eis_touchscreen *touchscreen) { struct eis_client * client = eis_touchscreen_get_client(touchscreen); eis_client_unregister_object(client, &touchscreen->proto_object); } OBJECT_IMPLEMENT_REF(eis_touchscreen); OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_touchscreen); OBJECT_IMPLEMENT_GETTER_AS_REF(eis_touchscreen, proto_object, const struct brei_object *); static OBJECT_IMPLEMENT_CREATE(eis_touchscreen); static OBJECT_IMPLEMENT_PARENT(eis_touchscreen, eis_device); uint32_t eis_touchscreen_get_version(struct eis_touchscreen *touchscreen) { return touchscreen->proto_object.version; } object_id_t eis_touchscreen_get_id(struct eis_touchscreen *touchscreen) { return touchscreen->proto_object.id; } struct eis_device * eis_touchscreen_get_device(struct eis_touchscreen *touchscreen) { return eis_touchscreen_parent(touchscreen); } struct eis_client* eis_touchscreen_get_client(struct eis_touchscreen *touchscreen) { return eis_device_get_client(eis_touchscreen_get_device(touchscreen)); } struct eis* eis_touchscreen_get_context(struct eis_touchscreen *touchscreen) { struct eis_client *client = eis_touchscreen_get_client(touchscreen); return eis_client_get_context(client); } const struct eis_touchscreen_interface * eis_touchscreen_get_interface(struct eis_touchscreen *touchscreen) { return eis_device_get_touchscreen_interface(eis_touchscreen_get_device(touchscreen)); } struct eis_touchscreen * eis_touchscreen_new(struct eis_device *device) { struct eis_touchscreen *touchscreen = eis_touchscreen_create(&device->object); struct eis_client *client = eis_device_get_client(device); touchscreen->proto_object.id = eis_client_get_new_id(client); touchscreen->proto_object.implementation = touchscreen; touchscreen->proto_object.interface = &eis_touchscreen_proto_interface; touchscreen->proto_object.version = client->interface_versions.ei_touchscreen; list_init(&touchscreen->proto_object.link); eis_client_register_object(client, &touchscreen->proto_object); return touchscreen; /* ref owned by caller */ } libei-1.2.1/src/libeis-touchscreen.h000066400000000000000000000040661456005336000173360ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 touchscreen WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "util-object.h" #include "brei-shared.h" #include "libeis-client.h" struct eis; struct eis_client; /* This is a protocol-only object, not exposed in the API */ struct eis_touchscreen { struct object object; struct brei_object proto_object; }; OBJECT_DECLARE_GETTER(eis_touchscreen, context, struct eis *); OBJECT_DECLARE_GETTER(eis_touchscreen, device, struct eis_device *); OBJECT_DECLARE_GETTER(eis_touchscreen, client, struct eis_client *); OBJECT_DECLARE_GETTER(eis_touchscreen, id, object_id_t); OBJECT_DECLARE_GETTER(eis_touchscreen, version, uint32_t); OBJECT_DECLARE_GETTER(eis_touchscreen, proto_object, const struct brei_object *); OBJECT_DECLARE_GETTER(eis_touchscreen, interface, const struct eis_touchscreen_interface *); OBJECT_DECLARE_REF(eis_touchscreen); OBJECT_DECLARE_UNREF(eis_touchscreen); struct eis_touchscreen * eis_touchscreen_new(struct eis_device *device); libei-1.2.1/src/libeis.c000066400000000000000000000263421456005336000150120ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include "util-macros.h" #include "util-object.h" #include "util-sources.h" #include "util-strings.h" #include "util-time.h" #include "libeis.h" #include "libeis-private.h" #include "eis-proto.h" _Static_assert(sizeof(enum eis_device_capability) == sizeof(int), "Invalid enum size"); _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"); static void eis_destroy(struct eis *eis) { struct eis_client *c; list_for_each_safe(c, &eis->clients, link) { eis_client_disconnect(c); } struct eis_event *e; while ((e = eis_get_event(eis)) != NULL) eis_event_unref(e); if (eis->backend_interface.destroy) eis->backend_interface.destroy(eis, eis->backend); sink_unref(eis->sink); } static OBJECT_IMPLEMENT_CREATE(eis); _public_ OBJECT_IMPLEMENT_REF(eis); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(eis); _public_ OBJECT_IMPLEMENT_SETTER(eis, user_data, void *); _public_ OBJECT_IMPLEMENT_GETTER(eis, user_data, void *); _public_ struct eis * eis_new(void *user_data) { _unref_(eis) *eis = eis_create(NULL); list_init(&eis->clients); list_init(&eis->event_queue); eis_log_set_handler(eis, NULL); eis_log_set_priority(eis, EIS_LOG_PRIORITY_INFO); eis->sink = sink_new(); if (!eis->sink) return NULL; eis->user_data = user_data; return steal(&eis); } _public_ int eis_get_fd(struct eis *eis) { return sink_get_fd(eis->sink); } struct eis * eis_get_context(struct eis *eis) { return eis; } _public_ void eis_dispatch(struct eis *eis) { sink_dispatch(eis->sink); } _public_ const char * eis_event_type_to_string(enum eis_event_type type) { switch(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_DEVICE_CLOSED); CASE_RETURN_STRING(EIS_EVENT_DEVICE_START_EMULATING); CASE_RETURN_STRING(EIS_EVENT_DEVICE_STOP_EMULATING); CASE_RETURN_STRING(EIS_EVENT_POINTER_MOTION); CASE_RETURN_STRING(EIS_EVENT_POINTER_MOTION_ABSOLUTE); CASE_RETURN_STRING(EIS_EVENT_BUTTON_BUTTON); CASE_RETURN_STRING(EIS_EVENT_SCROLL_DELTA); CASE_RETURN_STRING(EIS_EVENT_SCROLL_STOP); CASE_RETURN_STRING(EIS_EVENT_SCROLL_CANCEL); CASE_RETURN_STRING(EIS_EVENT_SCROLL_DISCRETE); CASE_RETURN_STRING(EIS_EVENT_KEYBOARD_KEY); CASE_RETURN_STRING(EIS_EVENT_TOUCH_DOWN); CASE_RETURN_STRING(EIS_EVENT_TOUCH_UP); CASE_RETURN_STRING(EIS_EVENT_TOUCH_MOTION); CASE_RETURN_STRING(EIS_EVENT_FRAME); } return NULL; } static void update_event_timestamp(struct eis_event *event, uint64_t time) { switch (event->type) { case EIS_EVENT_POINTER_MOTION: case EIS_EVENT_POINTER_MOTION_ABSOLUTE: case EIS_EVENT_BUTTON_BUTTON: case EIS_EVENT_SCROLL_DELTA: case EIS_EVENT_SCROLL_STOP: case EIS_EVENT_SCROLL_CANCEL: case EIS_EVENT_SCROLL_DISCRETE: case EIS_EVENT_KEYBOARD_KEY: case EIS_EVENT_TOUCH_DOWN: case EIS_EVENT_TOUCH_UP: case EIS_EVENT_TOUCH_MOTION: if (event->timestamp != 0) { log_bug(eis_event_get_context(event), "Unexpected timestamp for event of type: %s", eis_event_type_to_string(event->type)); return; } event->timestamp = time; break; default: log_bug(eis_event_get_context(event), "Unexpected event %s in pending queue event", eis_event_type_to_string(event->type)); return; } } static void eis_queue_event(struct eis_event *event) { struct eis *eis = eis_event_get_context(event); struct eis_device *device = eis_event_get_device(event); struct list *queue = &eis->event_queue; const char *prefix = ""; switch (event->type) { case EIS_EVENT_POINTER_MOTION: case EIS_EVENT_POINTER_MOTION_ABSOLUTE: case EIS_EVENT_BUTTON_BUTTON: case EIS_EVENT_SCROLL_DELTA: case EIS_EVENT_SCROLL_STOP: case EIS_EVENT_SCROLL_CANCEL: case EIS_EVENT_SCROLL_DISCRETE: case EIS_EVENT_KEYBOARD_KEY: case EIS_EVENT_TOUCH_DOWN: case EIS_EVENT_TOUCH_UP: case EIS_EVENT_TOUCH_MOTION: prefix = "pending "; queue = &device->pending_event_queue; break; case EIS_EVENT_FRAME: { /* silently discard empty frames */ if (list_empty(&device->pending_event_queue)) return; struct eis_event *pending; list_for_each_safe(pending, &device->pending_event_queue, link) { update_event_timestamp(pending, event->timestamp); list_remove(&pending->link); list_append(&eis->event_queue, &pending->link); } break; } default: if (device && !list_empty(&device->pending_event_queue)) eis_queue_frame_event(device, eis_now(eis)); break; } log_debug(eis, "queuing %sevent type %s (%u)", prefix, eis_event_type_to_string(event->type), event->type); list_append(queue, &event->link); } void eis_queue_connect_event(struct eis_client *client) { struct eis_event *e = eis_event_new_for_client(client); e->type = EIS_EVENT_CLIENT_CONNECT; eis_queue_event(e); } void eis_queue_disconnect_event(struct eis_client *client) { struct eis_event *e = eis_event_new_for_client(client); e->type = EIS_EVENT_CLIENT_DISCONNECT; eis_queue_event(e); } void eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities) { struct eis_event *e = eis_event_new_for_seat(seat); e->type = EIS_EVENT_SEAT_BIND; e->bind.capabilities = capabilities; eis_queue_event(e); } void eis_queue_device_closed_event(struct eis_device *device) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_DEVICE_CLOSED; eis_queue_event(e); } void eis_queue_frame_event(struct eis_device *device, uint64_t time) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_FRAME; e->timestamp = time; eis_queue_event(e); } void eis_queue_device_start_emulating_event(struct eis_device *device, uint32_t sequence) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_DEVICE_START_EMULATING; eis_queue_event(e); } void eis_queue_device_stop_emulating_event(struct eis_device *device) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_DEVICE_STOP_EMULATING; eis_queue_event(e); } void eis_queue_pointer_rel_event(struct eis_device *device, double dx, double dy) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_POINTER_MOTION; e->pointer.dx = dx; e->pointer.dy = dy; eis_queue_event(e); } void eis_queue_pointer_abs_event(struct eis_device *device, double x, double y) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_POINTER_MOTION_ABSOLUTE; e->pointer.absx = x; e->pointer.absy = y; eis_queue_event(e); } void eis_queue_pointer_button_event(struct eis_device *device, uint32_t button, bool is_press) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_BUTTON_BUTTON; e->pointer.button = button; e->pointer.button_is_press = is_press; eis_queue_event(e); } void eis_queue_pointer_scroll_event(struct eis_device *device, double x, double y) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_SCROLL_DELTA; e->pointer.sx = x; e->pointer.sy = y; eis_queue_event(e); } void eis_queue_pointer_scroll_discrete_event(struct eis_device *device, int32_t x, int32_t y) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_SCROLL_DISCRETE; e->pointer.sdx = x; e->pointer.sdy = y; eis_queue_event(e); } void eis_queue_pointer_scroll_stop_event(struct eis_device *device, bool x, bool y) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_SCROLL_STOP; e->pointer.stop_x = x; e->pointer.stop_y = y; eis_queue_event(e); } void eis_queue_pointer_scroll_cancel_event(struct eis_device *device, bool x, bool y) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_SCROLL_CANCEL; e->pointer.stop_x = x; e->pointer.stop_y = y; eis_queue_event(e); } void eis_queue_keyboard_key_event(struct eis_device *device, uint32_t key, bool is_press) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_KEYBOARD_KEY; e->keyboard.key = key; e->keyboard.key_is_press = is_press; eis_queue_event(e); } void eis_queue_touch_down_event(struct eis_device *device, uint32_t touchid, double x, double y) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_TOUCH_DOWN; e->touch.touchid = touchid, e->touch.x = x; e->touch.y = y; eis_queue_event(e); } void eis_queue_touch_motion_event(struct eis_device *device, uint32_t touchid, double x, double y) { struct eis_event *e = eis_event_new_for_device(device); e->type = EIS_EVENT_TOUCH_MOTION; e->touch.touchid = touchid, e->touch.x = x; e->touch.y = y; eis_queue_event(e); } void 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, eis_queue_event(e); } _public_ struct eis_event* eis_get_event(struct eis *eis) { if (list_empty(&eis->event_queue)) return NULL; struct eis_event *e = list_first_entry(&eis->event_queue, e, link); list_remove(&e->link); return e; } _public_ struct eis_event * eis_peek_event(struct eis *eis) { if (list_empty(&eis->event_queue)) return NULL; struct eis_event *e = list_first_entry(&eis->event_queue, e, link); return eis_event_ref(e); } void 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; 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; } libei-1.2.1/src/libeis.h000066400000000000000000001250301456005336000150110ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #ifdef __cplusplus extern "C" { #endif #include #include #include /** * @defgroup libeis 🍦 EIS - The server API * * libeis is the server-side module. This API should be used by processes * that have control over input devices, e.g. Wayland compositors. * * For an example EIS implementation see the [`tools/` directory in the libei * repository](https://gitlab.freedesktop.org/libinput/libei/-/tree/main/tools). * At its core, an EIS implementation will * - create a context with eis_new() * - set up a backend with eis_setup_backend_fd() or eis_setup_backend_socket() * - register the eis_get_fd() with its own event loop * - call eis_dispatch() whenever the fd triggers * - call eis_get_event() and process incoming events * * And for each client: * - accept new clients with eis_client_connect() * - create one or more seats for the client with eis_client_new_seat() * - wait for @ref EIS_EVENT_SEAT_BIND and then * - create one or more devices with the bound capabilities, see eis_seat_new_device() * * libei clients come in "sender" and "receiver" modes, depending on whether * the client sends or receives events. A libeis context however may accept * both sender and receiver clients, the EIS implementation works 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. * * @defgroup libeis-log The logging API * @ingroup libeis * * The API to control logging output. * * @defgroup libeis-receiver API for receiver clients * @ingroup libeis * * The receiver API is available only to clients that are not * eis_client_is_sender(). For those clients the EIS implemententation creates * devices and sends events **to** the libei client. IOW the EIS implementation * acts as the sender. The primary use-case * for this is input capturing, e.g. InputLeap. * * It is a client bug to call any of these functions for a client created * with ei_new_sender(). * * @defgroup libeis-sender API for sender clients * @ingroup libeis * * This API is available only on clients that are * eis_client_is_sender(). For those clients the EIS implemententation creates * devices and but it is the libei client that sends events **to** EIS implementation. IOW the EIS implementation acts as the receiver. * 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 * with ei_new_receiver(). * * @defgroup libeis-seat The seat API * @ingroup libeis * * The API to query and interact with a struct @ref eis_seat * * @defgroup libeis-device The device API * @ingroup libeis * * The API to query and interact with a struct @ref eis_device * * @defgroup libeis-region The region API * @ingroup libeis * * The API to query a struct @ref eis_region for information * * @defgroup libeis-keymap The keymap API * @ingroup libeis * * The API to query a struct @ref eis_keymap for information * */ /** * @addtogroup libeis * @{ */ /** * @struct eis */ struct eis; /** * @struct eis_client */ struct eis_client; /** * @struct eis_device */ struct eis_device; /** * @struct eis_seat */ struct eis_seat; /** * @struct eis_event */ struct eis_event; /** * @struct eis_keymap */ struct eis_keymap; /** * @struct eis_touch */ struct eis_touch; /** * @struct eis_region * @ingroup libeis-region * * Regions are only available on devices of type @ref EIS_DEVICE_TYPE_VIRTUAL. * * A rectangular region, defined by an x/y offset and a width and a height. * A region defines the area on an EIS desktop layout that is accessible by * this device - this region may not be the full area of the desktop. * Input events may only be sent for points within the regions. * * The use of regions is private to the EIS compositor and coordinates do not * need match the size of the actual desktop. For example, a compositor may * set a 1920x1080 region to represent a 4K monitor and transparently map * input events into the respective true pixels. * * Absolute devices may have different regions, it is up to the libei client * to send events through the correct device to target the right pixel. For * example, a dual-head setup my have two absolute devices, the first with a * zero offset region spanning the first screen, the second with a nonzero * offset spanning the second screen. * * Regions should not overlap, no behavior for overlapping regions has yet * been defined but this may change in the future. * * Regions must be assigned when the device is created and are static for the * lifetime of the device. See eis_device_new_region() and eis_region_add(). */ struct eis_region; /** * @enum eis_device_type * @ingroup libeis-device * * The device type determines what the device represents. * * If the device type is @ref EIS_DEVICE_TYPE_VIRTUAL, the device is a * virtual device representing input as applied on the EIS implementation's * screen. A relative virtual device generates input events in logical pixels, * an absolute virtual device generates input events in logical pixels on one * of the device's regions. Virtual devices do not have a size. * * If the device type is @ref EIS_DEVICE_TYPE_PHYSICAL, the device is a * representation of a physical device as if connected to the EIS * implementation's host computer. A relative physical device generates input * events in mm, an absolute physical device generates input events in mm * within the device's specified physical size. Physical devices do not have * regions. * * @see eis_device_get_width * @see eis_device_get_height */ enum eis_device_type { EIS_DEVICE_TYPE_VIRTUAL = 1, EIS_DEVICE_TYPE_PHYSICAL }; /** * @enum eis_device_capability * @ingroup libeis-device */ enum eis_device_capability { EIS_DEVICE_CAP_POINTER = (1 << 0), EIS_DEVICE_CAP_POINTER_ABSOLUTE = (1 << 1), EIS_DEVICE_CAP_KEYBOARD = (1 << 2), EIS_DEVICE_CAP_TOUCH = (1 << 3), EIS_DEVICE_CAP_SCROLL = (1 << 4), EIS_DEVICE_CAP_BUTTON = (1 << 5), }; /** * @enum eis_keymap_type * @ingroup libeis-keymap */ enum eis_keymap_type { EIS_KEYMAP_TYPE_XKB = 1, }; /** * @enum eis_event_type */ 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(). */ EIS_EVENT_CLIENT_CONNECT = 1, /** * The client has disconnected, any pending requests for this client * should be discarded. */ EIS_EVENT_CLIENT_DISCONNECT, /** * The client wants to bind or unbind a capability on this seat. * Devices associated with this seat should be sent to the client or * removed if the capability is no longer bound */ EIS_EVENT_SEAT_BIND, /** * The client no longer listens to events from this device. The caller * should released resources associated with this device. */ EIS_EVENT_DEVICE_CLOSED, /** * "Hardware" frame event. This event **must** be sent by the client * and notifies the server that the previous set of events belong to * the same logical hardware event. * * These events are only generated on a receiving EIS context. * * This event is most commonly used to implement multitouch (multiple * touches may update within the same hardware scanout cycle). */ EIS_EVENT_FRAME = 100, /** * The client is about to send events for a device. This event should * be used by the server to clear the logical state of the emulated * devices and/or provide UI to the user. * * These events are only generated on a receiving EIS context. * * Note that a client start multiple emulating sequences * simultaneously, depending on the devices it received from the * server. For example, in a synergy-like situation, the client * may start emulating of pointer and keyboard once the remote device * logically entered the screen. * * The server can cancel an ongoing emulating by suspending the * device, see eis_device_suspend(). */ EIS_EVENT_DEVICE_START_EMULATING = 200, /** * Stop emulating events on this device, see @ref EIS_EVENT_DEVICE_START_EMULATING. */ EIS_EVENT_DEVICE_STOP_EMULATING, /* These events are only generated on a receiving EIS context */ /** * A relative motion event with delta coordinates in logical pixels or * mm, depending on the device type. */ EIS_EVENT_POINTER_MOTION = 300, /** * An absolute motion event with absolute position within the device's * regions or size, depending on the device type. */ EIS_EVENT_POINTER_MOTION_ABSOLUTE = 400, /** * A button press or release event */ 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 = 600, /** * An ongoing scroll sequence stopped. */ EIS_EVENT_SCROLL_STOP, /** * An ongoing scroll sequence was cancelled. */ EIS_EVENT_SCROLL_CANCEL, /** * A vertical and/or horizontal scroll event with a discrete range in * logical scroll steps, like a scroll wheel. */ EIS_EVENT_SCROLL_DISCRETE, /** * A key press or release event */ EIS_EVENT_KEYBOARD_KEY = 700, /** * Event for a single touch set down on the device's logical surface. * A touch sequence is always down/up with an optional motion event in * between. On multitouch capable devices, several touchs eqeuences * may be active at any time. */ EIS_EVENT_TOUCH_DOWN = 800, /** * Event for a single touch released from the device's logical * surface. */ EIS_EVENT_TOUCH_UP, /** * Event for a single currently-down touch changing position (or other * properties). */ EIS_EVENT_TOUCH_MOTION, }; /** * This is a debugging helper to return a string of the name of the event type, * or NULL if the event type is invalid. For example, for @ref EIS_EVENT_TOUCH_UP this * function returns the string "EIS_EVENT_TOUCH_UP". */ const char* eis_event_type_to_string(enum eis_event_type); /** * Create a new libeis context with a refcount of 1. */ struct eis * eis_new(void *user_data); /** * @ingroup libeis-log */ enum eis_log_priority { EIS_LOG_PRIORITY_DEBUG = 10, EIS_LOG_PRIORITY_INFO = 20, EIS_LOG_PRIORITY_WARNING = 30, EIS_LOG_PRIORITY_ERROR = 40, }; /** * @ingroup libeis-log */ struct eis_log_context; /** * @ingroup libeis-log * @return the line number (``__LINE__``) for a given log message context. */ unsigned int eis_log_context_get_line(struct eis_log_context *ctx); /** * @ingroup libeis-log * @return the file name (``__FILE__``) for a given log message context. */ const char * eis_log_context_get_file(struct eis_log_context *ctx); /** * @ingroup libeis-log * @return the function name (``__func__``) for a given log message context. */ const char * eis_log_context_get_func(struct eis_log_context *ctx); /** * @ingroup libeis-log * * The log handler for library logging. This handler is only called for * messages with a log level equal or greater than than the one set in * eis_log_set_priority(). * * @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 */ typedef void (*eis_log_handler)(struct eis *eis, enum eis_log_priority priority, const char *message, struct eis_log_context *ctx); /** * @ingroup libeis-log * * Change the log handler for this context. If the log handler is NULL, the * built-in default log function is used. * * @param eis The EIS context * @param log_handler The log handler or NULL to use the default log * handler. */ void eis_log_set_handler(struct eis *eis, eis_log_handler log_handler); /** * @ingroup libeis-log */ void eis_log_set_priority(struct eis *eis, enum eis_log_priority priority); /** * @ingroup libeis-log */ 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); struct eis * eis_unref(struct eis *eis); void * eis_get_user_data(struct eis *eis); 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. */ int eis_setup_backend_fd(struct eis *ctx); /** * Add a new client to a context set up with eis_setup_backend_fd(). Returns * a file descriptor to be passed to ei_setup_backend_fd(), or a negative * errno on failure. */ int eis_backend_fd_add_client(struct eis *ctx); /** * 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 * events are available on this fd, call eis_dispatch() immediately to * process. */ int eis_get_fd(struct eis *eis); /** * Main event dispatching function. Reads events of the file descriptors * and processes them internally. Use eis_get_event() to retrieve the * events. * * Dispatching does not necessarily queue events. This function * should be called immediately once data is available on the file * descriptor returned by eis_get_fd(). */ void eis_dispatch(struct eis *eis); /** * 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. * * You must not call this function while holding a reference to an event * returned by eis_peek_event(). */ struct eis_event * eis_get_event(struct eis *eis); /** * Returns the next event in the internal event queue (or NULL) without * removing that event from the queue, i.e. the next call to eis_get_event() * will return that same event. * * This call is useful for checking whether there is an event and/or what * type of event it is. * * Repeated calls to eis_peek_event() return the same event. * * The returned event is refcounted, use eis_event_unref() to drop the * reference. * * A caller must not call eis_get_event() while holding a ref to an event * returned by eis_peek_event(). */ struct eis_event * eis_peek_event(struct eis *eis); /** * Release resources associated with this event. This function always * returns NULL. * * 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()). */ struct eis_event * eis_event_unref(struct eis_event *event); enum eis_event_type eis_event_get_type(struct eis_event *event); struct eis_client * eis_event_get_client(struct eis_event *event); 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. * * 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); /** * Return the device from this event. * * This does not increase the refcount of the device. Use eis_device_ref() * to keep a reference beyond the immediate scope. */ struct eis_device * eis_event_get_device(struct eis_event *event); /** * Return the time for the event of type @ref EIS_EVENT_FRAME in microseconds. * * @note: Only events of type @ref EIS_EVENT_FRAME carry a timestamp. For * convenience, the timestamp for other device events is retrofitted by this * library. * * @return the event time in microseconds */ uint64_t eis_event_get_time(struct eis_event *event); struct eis_client * eis_client_ref(struct eis_client *client); struct eis_client * eis_client_unref(struct eis_client *client); void * eis_client_get_user_data(struct eis_client *eis_client); void eis_client_set_user_data(struct eis_client *eis_client, void *user_data); struct eis* eis_client_get_context(struct eis_client *client); /** * Returns true if the client is a sender, false otherwise. A sender client may * send events to the EIS implementation, a receiver client expects to receive * events from the EIS implementation. */ bool eis_client_is_sender(struct eis_client *client); /** * Return the name set by this client. The server is under no obligation to * use this name. */ const char * eis_client_get_name(struct eis_client *client); /** * Allow connection from the client. This can only be done once, further * calls to this functions are ignored. * * When receiving an event of type @ref EIS_EVENT_CLIENT_CONNECT, the server * should connect client as soon as possible to allow communication with the * server. If the client is not authorized to talk to the server, call * eis_client_disconnect(). */ void eis_client_connect(struct eis_client *client); /** * Disconnect this client. Once disconnected the client may no longer talk * to this context, all resources associated with this client should be * released. * * It is not necessary to call this function when an @ref * EIS_EVENT_CLIENT_DISCONNECT event is received. */ void eis_client_disconnect(struct eis_client *client); /** * Create a new logical seat with a given name. Devices available to a * client belong to a bound seat, or in other words: a client cannot receive * events from a device until it binds to a seat and receives all devices from * that seat. * * This seat is not immediately active, use eis_seat_add() to bind this * seat on the client and notify the client of it's availability. * * The returned seat is refcounted, use eis_seat_unref() to drop the * reference. */ struct eis_seat * eis_client_new_seat(struct eis_client *client, const char *name); /** * @ingroup libeis-seat */ struct eis_seat * eis_seat_ref(struct eis_seat *seat); /** * @ingroup libeis-seat */ struct eis_seat * eis_seat_unref(struct eis_seat *seat); /** * @ingroup libeis-seat */ struct eis_client * eis_seat_get_client(struct eis_seat *eis_seat); /** * @ingroup libeis-seat */ const char * eis_seat_get_name(struct eis_seat *eis_seat); /** * @ingroup libeis-seat */ void * eis_seat_get_user_data(struct eis_seat *eis_seat); /** * @ingroup libeis-seat */ void eis_seat_set_user_data(struct eis_seat *eis_seat, void *user_data); /** * @ingroup libeis-seat */ bool eis_seat_has_capability(struct eis_seat *seat, enum eis_device_capability cap); /** * @ingroup libeis-seat * * Allow a capability on the seat. This indicates to the client * that it may create devices with with the given capabilities, though the * EIS implementation may restrict the of capabilities on a device to a * subset of those in the seat, see eis_device_allow_capability(). * * This function must be called before eis_seat_add(). * * This function has no effect if called after eis_seat_add() */ void eis_seat_configure_capability(struct eis_seat *seat, enum eis_device_capability cap); /** * @ingroup libeis-seat * * Add this seat to its client and notify the client of the seat's * availability. This allows the client to create a device within this seat. */ void eis_seat_add(struct eis_seat *seat); /** * @ingroup libeis-seat * * Remove this seat and all its remaining devices. */ 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 */ struct eis_client * eis_device_get_client(struct eis_device *device); /** * @ingroup libeis-device */ struct eis_seat * eis_device_get_seat(struct eis_device *device); /** * @ingroup libeis-device */ struct eis_device * eis_device_ref(struct eis_device *device); /** * @ingroup libeis-device */ struct eis_device * eis_device_unref(struct eis_device *device); /** * @ingroup libeis-device */ void * eis_device_get_user_data(struct eis_device *eis_device); /** * @ingroup libeis-device */ void eis_device_set_user_data(struct eis_device *eis_device, void *user_data); /** * @ingroup libeis-device * * Return the name of the device. The return value of this function may change after * eis_device_configure_name(), a caller should keep a copy of it where * required rather than the pointer value. */ const char * eis_device_get_name(struct eis_device *device); /** * @ingroup libeis-device */ bool eis_device_has_capability(struct eis_device *device, enum eis_device_capability cap); /** * @ingroup libeis-device * * Return the width in mm of a device of type @ref EIS_DEVICE_TYPE_PHYSICAL, * or zero otherwise. */ uint32_t eis_device_get_width(struct eis_device *device); /** * @ingroup libeis-device * * Return the height in mm of a device of type @ref EIS_DEVICE_TYPE_PHYSICAL, * or zero otherwise. */ uint32_t eis_device_get_height(struct eis_device *device); /** * @ingroup libeis-seat * * Create a new device on the seat. This device is not immediately active, use * eis_device_add() to notify the client of it's availability. * * The returned device is refcounted, use eis_device_unref() to drop the * reference. * * Before calling eis_device_add(), use the following functions to set up the * device: * - eis_device_configure_type() * - eis_device_configure_name() * - eis_device_configure_capability() * - eis_device_new_region() * - eis_device_new_keymap() * * The device type of the device defaults to @ref EIS_DEVICE_TYPE_VIRTUAL. */ struct eis_device * eis_seat_new_device(struct eis_seat *seat); /** * @ingroup libeis-device * * Set the device type for this device. It is recommended that that the device * type is the first call to configure the device as the device type * influences which other properties on the device can be set and/or will * trigger warnings if invoked with wrong arguments. */ void eis_device_configure_type(struct eis_device *device, enum eis_device_type type); /** * @ingroup libeis-device */ enum eis_device_type eis_device_get_type(struct eis_device *device); /** * @ingroup libeis-device */ void eis_device_configure_name(struct eis_device *device, const char *name); /** * @ingroup libeis-device */ void eis_device_configure_capability(struct eis_device *device, enum eis_device_capability cap); /** * @ingroup libeis-device * * Configure the size in mm of a device of type @ref EIS_DEVICE_TYPE_PHYSICAL. * * Device with relative-only capabilities does not require a size. A device * with capability @ref EIS_DEVICE_CAP_POINTER_ABSOLUTE or @ref * EIS_DEVICE_CAP_TOUCH must have a size. * * This function has no effect if called on a device of type other than @ref * EIS_DEVICE_TYPE_PHYSICAL. * * This function has no effect if called after ei_device_add() */ void eis_device_configure_size(struct eis_device *device, uint32_t width, uint32_t height); /** * @ingroup libeis-device * * Create a new region on the device of type @ref EIS_DEVICE_TYPE_VIRTUAL with * an initial refcount of 1. Use eis_region_add() to properly add the region * to the device. * * A region **must** have a size to be valid, see eis_region_set_size(). * * For a device of type @ref EIS_DEVICE_TYPE_PHYSICAL this function returns * NULL. */ struct eis_region * eis_device_new_region(struct eis_device *device); /** * @ingroup libeis-region * * This call has no effect if called after eis_region_add() */ void eis_region_set_size(struct eis_region *region, uint32_t w, uint32_t h); /** * @ingroup libeis-region * * This call has no effect if called after eis_region_add() */ void eis_region_set_offset(struct eis_region *region, uint32_t x, uint32_t y); /** * @ingroup libeis-region * * Set the physical scale for this region. If unset, the scale defaults to * 1.0. * * A @a scale value of less or equal to 0.0 will be silently ignored. * * This call has no effect if called after eis_region_add() * * See ei_region_get_physical_scale() for details. */ 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 * * Add the given region to its device. Once added, the region will be sent to * the client when the caller calls eis_device_add() later. * * Adding the same region twice will be silently ignored. */ void eis_region_add(struct eis_region *region); /** * @ingroup libeis-device * * Obtain a region from the device. This function only returns regions that * have been added to the device with eis_region_add(). The number of regions * is constant for a device once eis_device_add() has been called and the * indices of any region remains the same for the lifetime of * the device. * * Regions are shared between all capabilities. Where two capabilities need * different region, the EIS implementation must create multiple devices with * individual capabilities and regions. * * This function returns the given region or NULL if the index is larger than * the number of regions available. * * This does not increase the refcount of the region. Use eis_region_ref() to * keep a reference beyond the immediate scope. */ 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 */ struct eis_region * eis_region_ref(struct eis_region *region); /** * @ingroup libeis-region */ struct eis_region * eis_region_unref(struct eis_region *region); /** * @ingroup libeis-region */ void * eis_region_get_user_data(struct eis_region *region); /** * @ingroup libeis-region */ void eis_region_set_user_data(struct eis_region *region, void *user_data); /** * @ingroup libeis-region */ uint32_t eis_region_get_x(struct eis_region *region); /** * @ingroup libeis-region */ uint32_t eis_region_get_y(struct eis_region *region); /** * @ingroup libeis-region */ uint32_t eis_region_get_width(struct eis_region *region); /** * @ingroup libeis-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 * * Add this device to its seat and notify the client of the device's * availability. * * The device is paused, use eis_device_resume() to enable events from * the client. */ void eis_device_add(struct eis_device *device); /** * @ingroup libeis-device * * Remove the device. * This does not release any resources associated with this device, use * eis_device_unref() for any references held by the caller. */ void eis_device_remove(struct eis_device *device); /** * @ingroup libeis-device * * Notify the client that the device is paused and that no events * from the client will be processed. * * The library filters events sent by the client **after** the pause * notification has been processed by the client but this does not affect * events already in transit. In other words, the server may still receive * 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. * * @param device A connected device */ void eis_device_pause(struct eis_device *device); /** * @ingroup libeis-device * * Notify the client that the capabilities are resumed and that events * from the device will be processed. * * @param device A connected device */ void eis_device_resume(struct eis_device *device); /** * @ingroup libeis-device * * Create a new keymap of the given @a type. This keymap does not immediately * apply to the device, use eis_keymap_add() to apply this keymap. A keymap * may only be applied once and to a single device. * * The returned keymap has a refcount of at least 1, use eis_keymap_unref() * to release resources associated with this keymap. * * @param device The device with a @ref EIS_DEVICE_CAP_KEYBOARD capability * @param type The type of the keymap. * @param fd A memmap-able file descriptor of size @a size pointing to the * keymap used by this device. @a fd can be closed by the caller after this * function completes. The file descriptor needs to be mmap:ed with MAP_PRIVATE. * @param size The size of the data at @a fd in bytes * * @return A keymap object or `NULL` on failure. */ struct eis_keymap * eis_device_new_keymap(struct eis_device *device, enum eis_keymap_type type, int fd, size_t size); /** * @ingroup libeis-keymap * * Set the keymap on the device. * * The keymap is constant for the lifetime of the device and assigned to * this device individually. Where the keymap has to change, remove the * device and create a new one. * * If a keymap is `NULL`, the device does not have an individual keymap * assigned. Note that this may mean the client needs to guess at the * keymap layout. * * This function has no effect if called after eis_device_add() */ void eis_keymap_add(struct eis_keymap *keymap); /** * @ingroup libeis-keymap * @return the size of the keymap in bytes */ size_t eis_keymap_get_size(struct eis_keymap *keymap); /** * @ingroup libeis-keymap * * Returns the type for this keymap. The type specifies how to interpret the * data at the file descriptor returned by eis_keymap_get_fd(). */ enum eis_keymap_type eis_keymap_get_type(struct eis_keymap *keymap); /** * @ingroup libeis-keymap * * Return a memmap-able file descriptor pointing to the keymap used by the * device. The keymap is constant for the lifetime of the device and * assigned to this device individually. */ int eis_keymap_get_fd(struct eis_keymap *keymap); /** * @ingroup libeis-keymap */ struct eis_keymap * eis_keymap_ref(struct eis_keymap *keymap); /** * @ingroup libeis-keymap */ struct eis_keymap * eis_keymap_unref(struct eis_keymap *keymap); /** * @ingroup libeis-keymap */ void * eis_keymap_get_user_data(struct eis_keymap *eis_keymap); /** * @ingroup libeis-keymap */ void eis_keymap_set_user_data(struct eis_keymap *eis_keymap, void *user_data); /** * @ingroup libeis-keymap * * Return the device this keymap belongs to. */ struct eis_device * eis_keymap_get_device(struct eis_keymap *keymap); /** * @ingroup libeis-device * * Return the keymap assigned to this device. The return value of this * function is the keymap (if any) after the call to * eis_keymap_add(). */ struct eis_keymap * eis_device_keyboard_get_keymap(struct eis_device *device); /** * @ingroup libeis-device * * Notify the client of the current XKB modifier state. */ void eis_device_keyboard_send_xkb_modifiers(struct eis_device *device, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group); /** * @ingroup libeis-receiver * * see @ref ei_device_start_emulating */ void eis_device_start_emulating(struct eis_device *device, uint32_t sequence); /** * @ingroup libeis-receiver * * see @ref ei_device_stop_emulating */ void eis_device_stop_emulating(struct eis_device *device); /** * @ingroup libeis-receiver * ** see @ref ei_device_frame */ void eis_device_frame(struct eis_device *device, uint64_t time); /** * @ingroup libeis-receiver * ** see @ref ei_device_pointer_motion */ void eis_device_pointer_motion(struct eis_device *device, double x, double y); /** * @ingroup libeis-receiver * ** see @ref ei_device_pointer_motion_absolute */ void eis_device_pointer_motion_absolute(struct eis_device *device, double x, double y); /** * @ingroup libeis-receiver * ** see @ref ei_device_button_button */ void eis_device_button_button(struct eis_device *device, uint32_t button, bool is_press); /** * @ingroup libeis-receiver * ** see @ref ei_device_scroll_delta */ void eis_device_scroll_delta(struct eis_device *device, double x, double y); /** * @ingroup libeis-receiver * ** see @ref ei_device_scroll_discrete */ void eis_device_scroll_discrete(struct eis_device *device, int32_t x, int32_t y); /** * @ingroup libeis-receiver * ** see @ref ei_device_scroll_stop */ void eis_device_scroll_stop(struct eis_device *device, bool stop_x, bool stop_y); /** * @ingroup libeis-receiver * ** see @ref ei_device_scroll_cancel */ void eis_device_scroll_cancel(struct eis_device *device, bool cancel_x, bool cancel_y); /** * @ingroup libeis-receiver * ** see @ref ei_device_keyboard_key */ void eis_device_keyboard_key(struct eis_device *device, uint32_t keycode, bool is_press); /** * @ingroup libeis-receiver * ** see @ref ei_device_touch_new */ struct eis_touch * eis_device_touch_new(struct eis_device *device); /** * @ingroup libeis-receiver * ** see @ref ei_touch_down */ void eis_touch_down(struct eis_touch *touch, double x, double y); /** * @ingroup libeis-receiver * ** see @ref ei_touch_motion */ void eis_touch_motion(struct eis_touch *touch, double x, double y); /** * @ingroup libeis-receiver * ** see @ref ei_touch_up */ void eis_touch_up(struct eis_touch *touch); /** * @ingroup libeis-receiver * ** see @ref ei_touch_ref */ struct eis_touch * eis_touch_ref(struct eis_touch *touch); /** * @ingroup libeis-receiver * ** see @ref ei_touch_unref */ struct eis_touch * eis_touch_unref(struct eis_touch *touch); /** * @ingroup libeis-receiver * ** see @ref ei_touch_set_user_data */ void eis_touch_set_user_data(struct eis_touch *touch, void *user_data); /** * @ingroup libeis-receiver * ** see @ref ei_touch_get_user_data */ void * eis_touch_get_user_data(struct eis_touch *touch); /** * @ingroup libeis-receiver * ** see @ref ei_touch_get_device */ struct eis_device * eis_touch_get_device(struct eis_touch *touch); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_DEVICE_START_EMULATING, return the * sequence number set by the ei client implementation. * * See ei_device_start_emulating() for details. */ uint32_t eis_event_emulating_get_sequence(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_POINTER_MOTION return the relative x * movement in logical pixels or mm, depending on the device type. */ double eis_event_pointer_get_dx(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_POINTER_MOTION return the relative y * movement in logical pixels or mm, depending on the device type. */ double eis_event_pointer_get_dy(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_POINTER_MOTION_ABSOLUTE return the x * position in logical pixels or mm, depending on the device type. */ double eis_event_pointer_get_absolute_x(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_POINTER_MOTION_ABSOLUTE return the y * position in logical pixels or mm, depending on the device type. */ double eis_event_pointer_get_absolute_y(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_BUTTON_BUTTON return the button * code as defined in linux/input-event-codes.h */ uint32_t eis_event_button_get_button(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_BUTTON_BUTTON return true if the * event is a button press, false for a release. */ bool eis_event_button_get_is_press(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_SCROLL_DELTA return the x scroll * distance in logical pixels or mm, depending on the device type. */ double eis_event_scroll_get_dx(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_SCROLL_DELTA return the y scroll * distance in logical pixels or mm, depending on the device type. */ double eis_event_scroll_get_dy(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_SCROLL_STOP return whether the * x axis has stopped scrolling. * * For an event of type @ref EIS_EVENT_SCROLL_CANCEL return whether the * x axis has cancelled scrolling. */ bool eis_event_scroll_get_stop_x(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_SCROLL_STOP return whether the * y axis has stopped scrolling. * * For an event of type @ref EIS_EVENT_SCROLL_CANCEL return whether the * y axis has cancelled scrolling. */ bool eis_event_scroll_get_stop_y(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_SCROLL_DISCRETE return the x * scroll distance in fractions or multiples of 120. */ int32_t eis_event_scroll_get_discrete_dx(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_SCROLL_DISCRETE return the y * scroll distance in fractions or multiples of 120. */ int32_t eis_event_scroll_get_discrete_dy(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_KEYBOARD_KEY return the key code (as * defined in include/linux/input-event-codes.h). */ uint32_t eis_event_keyboard_get_key(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_KEYBOARD_KEY return true if the * event is a key down, false for a release. */ bool eis_event_keyboard_get_key_is_press(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_TOUCH_DOWN, @ref * EIS_EVENT_TOUCH_MOTION, or @ref EIS_EVENT_TOUCH_UP, return the tracking * ID of the touch. * * The tracking ID is a unique identifier for a touch and is valid from * touch down through to touch up but may be re-used in the future. * The tracking ID is randomly assigned to a touch, a client * must not expect any specific value. */ uint32_t eis_event_touch_get_id(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_TOUCH_DOWN, or @ref * EIS_EVENT_TOUCH_MOTION, return the x coordinate of the touch * in logical pixels or mm, depending on the device type. */ double eis_event_touch_get_x(struct eis_event *event); /** * @ingroup libeis-sender * * For an event of type @ref EIS_EVENT_TOUCH_DOWN, or @ref * EIS_EVENT_TOUCH_MOTION, return the y coordinate of the touch * in logical pixels or mm, depending on the device type. */ double eis_event_touch_get_y(struct eis_event *event); /** * @returns a timestamp for the current time to pass into * eis_device_frame(). * * 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 *eis); /** * @} */ #ifdef __cplusplus } #endif libei-1.2.1/src/liboeffis.c000066400000000000000000000465401456005336000155070ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2022 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" #if HAVE_LIBSYSTEMD #include #elif HAVE_LIBELOGIND #include #elif HAVE_BASU #include #endif #include "util-io.h" #include "util-macros.h" #include "util-object.h" #include "util-sources.h" #include "util-strings.h" #include "util-time.h" #include "util-version.h" #include "liboeffis.h" _Static_assert(sizeof(enum oeffis_event_type) == sizeof(int), "Invalid enum size"); _Static_assert(sizeof(enum oeffis_device) == sizeof(uint32_t), "Invalid enum size"); /* oeffis is simple enough that we don't really need debugging or a log * handler. If you want this on, just define it */ /* #define log_debug(...) fprintf(stderr, "DEBUG " __VA_ARGS__) */ #define log_debug(...) /* */ static void portal_init(struct oeffis *oeffis, const char *busname); static void portal_start(struct oeffis *oeffis); enum oeffis_state { OEFFIS_STATE_NEW, OEFFIS_STATE_CREATE_SESSION, OEFFIS_STATE_SESSION_CREATED, OEFFIS_STATE_STARTED, OEFFIS_STATE_CONNECTED_TO_EIS, OEFFIS_STATE_DISCONNECTED, /* used for closed as well since internally it's the same thing */ }; struct oeffis { struct object object; void *user_data; struct sink *sink; enum oeffis_state state; uint32_t devices; /* We can have a maximum of 3 events (connected, optional closed, * disconnected) so we can have the event queue be a fixed * null-terminated array, have a pointer to the current element and * shift that on with every event */ enum oeffis_event_type event_queue[4]; /* Points to the next client-visible event in the event queue. only * advanced by the client */ enum oeffis_event_type *next_event; int eis_fd; /* NULL until OEFFIS_STATE_DISCONNECTED */ char *error_message; /* internal epollfd tickler */ struct source *epoll_tickler_source; int pipefd[2]; /* sd-bus pieces */ struct source *bus_source; sd_bus *bus; sd_bus_slot *slot_request_response; /* Re-used for Request.Response */ sd_bus_slot *slot_session_closed; char *busname; char *session_path; char *sender_name; }; static void oeffis_destroy(struct oeffis *oeffis) { free(oeffis->error_message); sink_unref(oeffis->sink); xclose(oeffis->eis_fd); xclose(oeffis->pipefd[0]); xclose(oeffis->pipefd[1]); free(oeffis->sender_name); free(oeffis->session_path); free(oeffis->busname); sd_bus_close(oeffis->bus); sd_bus_unref(oeffis->bus); sd_bus_slot_unref(oeffis->slot_request_response); sd_bus_slot_unref(oeffis->slot_session_closed); } static OBJECT_IMPLEMENT_CREATE(oeffis); _public_ OBJECT_IMPLEMENT_REF(oeffis); _public_ OBJECT_IMPLEMENT_UNREF_CLEANUP(oeffis); _public_ OBJECT_IMPLEMENT_SETTER(oeffis, user_data, void *); _public_ OBJECT_IMPLEMENT_GETTER(oeffis, user_data, void *); _public_ OBJECT_IMPLEMENT_GETTER(oeffis, error_message, const char *); DEFINE_UNREF_CLEANUP_FUNC(source); static void tickle(struct oeffis *oeffis) { xwrite(oeffis->pipefd[1], "kitzel", 6); } static void _printf_(2, 3) oeffis_disconnect(struct oeffis *oeffis, const char *fmt, ...) { if (oeffis->state == OEFFIS_STATE_DISCONNECTED) return; va_list args; va_start(args, fmt); oeffis->state = OEFFIS_STATE_DISCONNECTED; oeffis->error_message = xvaprintf(fmt, args); va_end(args); *oeffis->next_event = OEFFIS_EVENT_DISCONNECTED; oeffis->eis_fd = xclose(oeffis->eis_fd); tickle(oeffis); /* FIXME: need to so more here? */ } static void tickled(struct source *source, void *data) { /* Nothing to do here, just drain the data */ char buf[64]; xread(source_get_fd(source), buf, sizeof(buf)); } _public_ struct oeffis * oeffis_new(void *user_data) { _unref_(oeffis) *oeffis = oeffis_create(NULL); oeffis->state = OEFFIS_STATE_NEW; oeffis->user_data = user_data; oeffis->next_event = oeffis->event_queue; oeffis->eis_fd = -1; oeffis->pipefd[0] = -1; oeffis->pipefd[1] = -1; oeffis->sink = sink_new(); if (!oeffis->sink) return NULL; /* set up a pipe we can write to to force the epoll to wake up even when * nothing else happens */ int rc = xpipe2(oeffis->pipefd, O_CLOEXEC|O_NONBLOCK); if (rc < 0) return NULL; _unref_(source) *s = source_new(oeffis->pipefd[0], tickled, NULL); sink_add_source(oeffis->sink, s); return steal(&oeffis); } _public_ int oeffis_get_fd(struct oeffis *oeffis) { return sink_get_fd(oeffis->sink); } _public_ int oeffis_get_eis_fd(struct oeffis *oeffis) { if (oeffis->state != OEFFIS_STATE_CONNECTED_TO_EIS) { errno = ENODEV; return -1; } return xdup(oeffis->eis_fd); } _public_ enum oeffis_event_type oeffis_get_event(struct oeffis *oeffis) { enum oeffis_event_type e = *oeffis->next_event; if (e != OEFFIS_EVENT_NONE) oeffis->next_event++; assert(oeffis->next_event < oeffis->event_queue + ARRAY_LENGTH(oeffis->event_queue)); return e; } _public_ void oeffis_create_session(struct oeffis *oeffis, uint32_t devices) { oeffis_create_session_on_bus(oeffis, "org.freedesktop.portal.Desktop", devices); } _public_ void oeffis_create_session_on_bus(struct oeffis *oeffis, const char *busname, uint32_t devices) { if (oeffis->state != OEFFIS_STATE_NEW) return; oeffis->devices = devices; oeffis->state = OEFFIS_STATE_CREATE_SESSION; portal_init(oeffis, busname); } _public_ void oeffis_dispatch(struct oeffis *oeffis) { sink_dispatch(oeffis->sink); } static int oeffis_set_eis_fd(struct oeffis *oeffis, int eisfd) { if (oeffis->state != OEFFIS_STATE_STARTED) return -EALREADY; oeffis->state = OEFFIS_STATE_CONNECTED_TO_EIS; oeffis->eis_fd = eisfd; *oeffis->next_event = OEFFIS_EVENT_CONNECTED_TO_EIS; tickle(oeffis); return 0; } static void oeffis_close(struct oeffis *oeffis) { switch (oeffis->state) { case OEFFIS_STATE_NEW: oeffis_disconnect(oeffis, "Bug: Received Session.Close in state NEW."); break; case OEFFIS_STATE_CREATE_SESSION: case OEFFIS_STATE_SESSION_CREATED: case OEFFIS_STATE_CONNECTED_TO_EIS: case OEFFIS_STATE_STARTED: *oeffis->next_event = OEFFIS_EVENT_CLOSED; tickle(oeffis); oeffis->state = OEFFIS_STATE_DISCONNECTED; break; case OEFFIS_STATE_DISCONNECTED: break; } } /********************************************** DBus implementation **************************************************/ static char * sender_name(sd_bus *bus) { _cleanup_free_ char *sender = NULL; const char *name = NULL; if (sd_bus_get_unique_name(bus, &name) != 0) return NULL; sender = xstrdup(name + 1); /* drop initial : */ for (unsigned i = 0; sender[i]; i++) { if (sender[i] == '.') sender[i] = '_'; } return steal(&sender); } static char * xdp_token(void) { /* next for easier debugging, rand() so we don't ever conflict in * real life situations */ static uint32_t next = 0; return xaprintf("oeffis_%u_%d", next++, rand()); } static char * xdp_request_path(char *sender_name, char *token) { return xaprintf("/org/freedesktop/portal/desktop/request/%s/%s", sender_name, token); } static char * xdp_session_path(char *sender_name, char *token) { return xaprintf("/org/freedesktop/portal/desktop/session/%s/%s", sender_name, token); } 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, "/org/freedesktop/portal/desktop", "org.freedesktop.portal.RemoteDesktop", "ConnectToEIS", &error, &response, "oa{sv}", oeffis->session_path, 0); } if (rc < 0) { 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", eisfd); rc = oeffis_set_eis_fd(oeffis, eisfd); if (rc < 0) oeffis_disconnect(oeffis, "Failed to set the fd: %s", strerror(-rc)); } static int session_closed_received(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct oeffis *oeffis = userdata; oeffis_close(oeffis); return 0; } static void dbus_dispatch(struct source *source, void *data) { struct oeffis *oeffis = data; sd_bus *bus = oeffis->bus; int rc; do { rc = sd_bus_process(bus, NULL); } while (rc > 0); if (rc < 0) oeffis_disconnect(oeffis, "dbus processing failed with %s", strerror(-rc)); } static int portal_setup_request(struct oeffis *oeffis, sd_bus_message_handler_t response_handler, char **token_return, sd_bus_slot **slot_return) { sd_bus *bus = oeffis->bus; _unref_(sd_bus_slot) *slot = NULL; _cleanup_free_ char *token = xdp_token(); _cleanup_free_ char *handle = xdp_request_path(oeffis->sender_name, token); int rc = 0; with_signals_blocked(SIGALRM) { rc = sd_bus_match_signal(bus, &slot, oeffis->busname, handle, "org.freedesktop.portal.Request", "Response", response_handler, oeffis); } if (rc < 0) { oeffis_disconnect(oeffis, "Failed to subscribe to Request.Response signal: %s", strerror(-rc)); return rc; } *token_return = steal(&token); *slot_return = steal(&slot); return 0; } static int portal_start_response_received(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct oeffis *oeffis = userdata; /* We'll only get this signal once */ oeffis->slot_request_response = sd_bus_slot_unref(oeffis->slot_request_response); unsigned int response; int rc = sd_bus_message_read(m, "u", &response); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to read response from signal: %s", strerror(-rc)); return 0; } log_debug("Portal Start reponse is %u", response); if (response != 0) { oeffis_disconnect(oeffis, "Portal denied Start"); return 0; } oeffis->state = OEFFIS_STATE_STARTED; /* Response includes the the device bitmask but we don't care about this here */ /* Don't need a separate state here, ConnectToEIS is synchronous */ portal_connect_to_eis(oeffis); return 0; } static void portal_start(struct oeffis *oeffis) { _cleanup_free_ char *token = NULL; _unref_(sd_bus_slot) *request_slot = NULL; int rc = portal_setup_request(oeffis, portal_start_response_received, &token, &request_slot); if (rc < 0) return; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _unref_(sd_bus_message) *response = NULL; sd_bus *bus = oeffis->bus; with_signals_blocked(SIGALRM) { rc = sd_bus_call_method(bus, oeffis->busname, "/org/freedesktop/portal/desktop", "org.freedesktop.portal.RemoteDesktop", "Start", &error, &response, "osa{sv}", oeffis->session_path, "", /* parent window */ 1, "handle_token", /* string key */ "s", token /* variant string */ ); } if (rc < 0) { oeffis_disconnect(oeffis, "Failed to call method: %s", strerror(-rc)); return; } const char *path = NULL; rc = sd_bus_message_read(response, "o", &path); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to parse Start reply: %s", strerror(-rc)); return; } oeffis->slot_request_response = sd_bus_slot_ref(request_slot); return; } static int portal_select_devices_response_received(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct oeffis *oeffis = userdata; /* We'll only get this signal once */ oeffis->slot_request_response = sd_bus_slot_unref(oeffis->slot_request_response); unsigned int response; int rc = sd_bus_message_read(m, "u", &response); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to read response from signal: %s", strerror(-rc)); return 0; } log_debug("Portal SelectDevices reponse is %u", response); if (response != 0) { oeffis_disconnect(oeffis, "Portal denied SelectDevices"); return 0; } /* Response includes the the device bitmask but we don't care about this here */ portal_start(oeffis); return 0; } static void portal_select_devices(struct oeffis *oeffis) { sd_bus *bus = oeffis->bus; _cleanup_free_ char *token = NULL; _unref_(sd_bus_slot) *request_slot = NULL; int rc = portal_setup_request(oeffis, portal_select_devices_response_received, &token, &request_slot); if (rc < 0) return; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _unref_(sd_bus_message) *response = NULL; with_signals_blocked(SIGALRM) { rc = sd_bus_call_method(bus, oeffis->busname, "/org/freedesktop/portal/desktop", "org.freedesktop.portal.RemoteDesktop", "SelectDevices", &error, &response, "oa{sv}", oeffis->session_path, oeffis->devices == OEFFIS_DEVICE_ALL_DEVICES ? 1 : 2, "handle_token", /* string key */ "s", token, /* variant string */ "types", /* string key */ "u", oeffis->devices ); } if (rc < 0) { oeffis_disconnect(oeffis, "Failed to call method: %s", strerror(-rc)); return; } const char *path = NULL; rc = sd_bus_message_read(response, "o", &path); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to parse Start reply: %s", strerror(-rc)); return; } oeffis->slot_request_response = sd_bus_slot_ref(request_slot); } static int portal_create_session_response_received(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct oeffis *oeffis = userdata; /* We'll only get this signal once */ oeffis->slot_request_response = sd_bus_slot_unref(oeffis->slot_request_response); unsigned int response; int rc = sd_bus_message_read(m, "u", &response); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to read response from signal: %s", strerror(-rc)); return 0; } log_debug("Portal CreateSession reponse is %u", response); const char *session_handle = NULL; if (response == 0) { const char *key; rc = sd_bus_message_read(m, "a{sv}", 1, &key, "s", &session_handle); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to read session handle from signal: %s", strerror(-rc)); return 0; } if (!streq(key, "session_handle")) { oeffis_disconnect(oeffis, "Invalid or unhandled option: %s", key); return 0; } } if (response != 0) { oeffis_disconnect(oeffis, "Portal denied CreateSession"); return 0; } oeffis->session_path = xstrdup(session_handle); oeffis->state = OEFFIS_STATE_SESSION_CREATED; portal_select_devices(oeffis); return 0; } static void portal_init(struct oeffis *oeffis, const char *busname) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _unref_(sd_bus) *bus = NULL; _unref_(sd_bus_message) *response = NULL; const char *path = NULL; int rc = sd_bus_open_user(&bus); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to init dbus: %s", strerror(-rc)); return; } oeffis->sender_name = sender_name(bus); if (!oeffis->sender_name) { oeffis_disconnect(oeffis, "Failed to parse sender name"); return; } oeffis->bus = sd_bus_ref(bus); oeffis->busname = xstrdup(busname); uint32_t version; rc = sd_bus_get_property_trivial(bus, busname, "/org/freedesktop/portal/desktop", "org.freedesktop.portal.RemoteDesktop", "version", &error, 'u', &version); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to get RemoteDesktop.version: %s", strerror(sd_bus_error_get_errno(&error))); return; } else if (version < VERSION_V(2)) { oeffis_disconnect(oeffis, "RemoteDesktop.version is %u, we need 2", version); return; } log_debug("RemoteDesktop.version is %u", version); _cleanup_free_ char *token = NULL; _unref_(sd_bus_slot) *request_slot = NULL; rc = portal_setup_request(oeffis, portal_create_session_response_received, &token, &request_slot); if (rc < 0) return; _unref_(sd_bus_slot) *session_slot = NULL; _cleanup_free_ char *session_token = xdp_token(); _cleanup_free_ char *session_handle = xdp_session_path(oeffis->sender_name, session_token); rc = sd_bus_match_signal(bus, &session_slot, busname, session_handle, "org.freedesktop.portal.Session", "Closed", session_closed_received, oeffis); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to subscribe to Session.Closed signal: %s", strerror(-rc)); return; } with_signals_blocked(SIGALRM) { rc = sd_bus_call_method(bus, busname, "/org/freedesktop/portal/desktop", "org.freedesktop.portal.RemoteDesktop", "CreateSession", &error, &response, "a{sv}", 2, "handle_token", /* string key */ "s", token, /* variant string */ "session_handle_token", /* string key */ "s", session_token /* variant string */ ); } if (rc < 0) { oeffis_disconnect(oeffis, "Failed to call method: %s", strerror(-rc)); return; } rc = sd_bus_message_read(response, "o", &path); if (rc < 0) { oeffis_disconnect(oeffis, "Failed to parse CreateSession reply: %s", strerror(-rc)); return; } 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 */ rc = sink_add_source(oeffis->sink, s); if (rc == 0) { oeffis->bus_source = source_ref(s); oeffis->slot_request_response = sd_bus_slot_ref(request_slot); oeffis->slot_session_closed = sd_bus_slot_ref(session_slot); } return; } #ifdef _enable_tests_ #include "util-munit.h" MUNIT_TEST(test_init_unref) { struct oeffis *oeffis = oeffis_new(NULL); munit_assert_int(oeffis->state, ==, OEFFIS_STATE_NEW); munit_assert_not_null(oeffis->sink); munit_assert_int(oeffis->eis_fd, ==, -1); struct oeffis *refd = oeffis_ref(oeffis); munit_assert_ptr_equal(oeffis, refd); munit_assert_int(oeffis->object.refcount, ==, 2); struct oeffis *unrefd = oeffis_unref(oeffis); munit_assert_null(unrefd); unrefd = oeffis_unref(oeffis); munit_assert_null(unrefd); return MUNIT_OK; } MUNIT_TEST(test_failed_connect) { struct oeffis *oeffis = oeffis_new(NULL); enum oeffis_event_type event; oeffis_create_session_on_bus(oeffis, "foo.bar.Example", 0); while ((event = oeffis_get_event(oeffis)) == OEFFIS_EVENT_NONE) oeffis_dispatch(oeffis); munit_assert_int(event, ==, OEFFIS_EVENT_DISCONNECTED); munit_assert_int(oeffis->state, ==, OEFFIS_STATE_DISCONNECTED); munit_assert_not_null(oeffis->error_message); oeffis_unref(oeffis); return MUNIT_OK; } #endif libei-1.2.1/src/liboeffis.h000066400000000000000000000215161456005336000155100ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2022 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 #ifdef __cplusplus extern "C" { #endif #include #include #include /** * @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. * * liboeffis will: * - connect to the DBus session bus and the ``org.freedesktop.portal.Desktop`` bus name * - Start a ``org.freedesktop.portal.RemoteDesktop`` session, select the devices and invoke * ``RemoteDesktop.ConnectToEIS()`` * - Close everything in case of error or disconnection * * liboeffis is intentionally kept simple, any more complex needs should be * handled by an application talking to DBus directly. * * @{ */ /** * @struct oeffis * * The main context to interact with liboeffis. A liboeffis context is a single * connection to DBus session bus and must only be used once. * * @note A caller must keep the oeffis context alive after a successful * connection. Destroying the context results in the DBus connection being * closed and that again results in our EIS fd being invalidated by the * portal and/or the EIS implementation. * * An @ref oeffis context is refcounted, see oeffis_unref(). */ struct oeffis; /** * Create a new oeffis context. The context is refcounted and must be * released with oeffis_unref(). */ struct oeffis * oeffis_new(void *user_data); /** * Increase the refcount of this struct by one. Use oeffis_unref() to decrease * the refcount. * * @return the argument passed into the function */ struct oeffis * oeffis_ref(struct oeffis *oeffis); /** * Decrease the refcount of this struct by one. When the refcount reaches * zero, the context disconnects from DBus and all allocated resources * are released. * * @note A caller must keep the oeffis context alive after a successful * connection. Destroying the context results in the DBus connection being * closed and that again results in our EIS fd being invalidated by the * portal and/or the EIS implementation. * * @return always NULL */ struct oeffis * oeffis_unref(struct oeffis *oeffis); /** * Set a custom data pointer for this context. liboeffis will not look at or * modify the pointer. Use oeffis_get_user_data() to retrieve a previously set * user data. */ void oeffis_set_user_data(struct oeffis *oeffis, void *user_data); /** * Return the custom data pointer for this context. liboeffis will not look at or * modify the pointer. Use oeffis_set_user_data() to change the user data. */ void * oeffis_get_user_data(struct oeffis *oeffis); /** * liboeffis 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 * events are available on this fd, call oeffis_dispatch() immediately to * process the events. */ int oeffis_get_fd(struct oeffis *oeffis); /** * Get a `dup()` of the file descriptor. This function should only be called * after an event of type @ref OEFFIS_EVENT_CONNECTED_TO_EIS. Otherwise, * this function returns -1 and errno is set to the `dup()` error. * If this function is called when liboeffis is not connected to EIS, the errno * is set to `ENODEV`. * * Repeated calls to this functions will return additional duplicated file * descriptors. There is no need for a well-written application to call this * function more than once. * * The caller is responsible for closing the returned fd. * * @return The EIS fd or -1 on failure or before the fd was retrieved. */ int oeffis_get_eis_fd(struct oeffis *oeffis); /** * The bitmask of devices to request. This bitmask matches the devices * 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), OEFFIS_DEVICE_TOUCHSCREEN = (1 << 2), }; /** * Connect this oeffis instance to a RemoteDesktop session with the given device * mask selected. * * This initiates the DBus communication, starts a RemoteDesktop session and * selects the devices. On success, the @ref * OEFFIS_EVENT_CONNECTED_TO_EIS event is created and the EIS fd can be * retrieved with oeffis_get_eis_fd(). * * Any failure in the above process or any other DBus communication error * once connected, including caller bugs, result in the oeffis context being * disconnected and an @ref OEFFIS_EVENT_DISCONNECTED event. Once * disconnected, the context should be released with oeffis_unref(). * An @ref OEFFIS_EVENT_DISCONNECTED indicates a communication error and * oeffis_get_error_message() is set with an appropriate error message. * * If the RemoteDesktop session is closed by the * compositor, an @ref OEFFIS_EVENT_CLOSED event is created and the context * should be released with oeffis_unref(). Unlike a disconnection, an @ref * OEFFIS_EVENT_CLOSED event signals intentional closure by the portal. For * example, this may happen as a result of user interaction to terminate the * RemoteDesktop session. * * @note A caller must keep the oeffis context alive after a successful * connection. Destroying the context results in the DBus connection being * closed and that again results in our EIS fd being invalidated by the * portal and/or the EIS implementation. * * @warning Due to the asynchronous nature of DBus and libei, it is not * guaranteed that an event of type @ref OEFFIS_EVENT_DISCONNECTED or @ref * OEFFIS_EVENT_CLOSED is received before the EIS fd becomes invalid. * * @param oeffis A new oeffis context * @param devices A bitmask of @ref oeffis_device */ void oeffis_create_session(struct oeffis *oeffis, uint32_t devices); /** * See oeffis_create_session() but this function allows to specify the busname to connect to. * This function should only be used for testing. */ void oeffis_create_session_on_bus(struct oeffis *oeffis, const char *busname, uint32_t devices); enum oeffis_event_type { OEFFIS_EVENT_NONE = 0, /**< No event currently available */ OEFFIS_EVENT_CONNECTED_TO_EIS, /**< The RemoteDesktop session was created and an eis fd is available */ OEFFIS_EVENT_CLOSED, /**< The session was closed by the compositor or portal */ OEFFIS_EVENT_DISCONNECTED, /**< We were disconnected from the Bus due to an error */ }; /** * Process pending events. This function must be called immediately after * the file descriptor returned by oeffis_get_fd() signals data is * available. * * After oeffis_dispatch() completes, zero or more events may be available * by oeffis_get_event(). */ void oeffis_dispatch(struct oeffis *oeffis); /** * Return the next available event, if any. If no event is currently * available, @ref OEFFIS_EVENT_NONE is returned. * * Calling oeffis_dispatch() does not guarantee events are available to the * caller. A single call oeffis_dispatch() may cause more than one event to * be available. */ enum oeffis_event_type oeffis_get_event(struct oeffis *oeffis); /** * If the session was @ref OEFFIS_EVENT_DISCONNECTED, return the error message * that caused the disconnection. The returned string is owned by the oeffis context. */ const char * oeffis_get_error_message(struct oeffis *oeffis); /** * @} */ #ifdef __cplusplus } #endif libei-1.2.1/src/meson.build000066400000000000000000000131771456005336000155430ustar00rootroot00000000000000src_libutil = files( 'util-bits.c', 'util-io.c', 'util-list.c', 'util-logger.c', 'util-memfile.c', 'util-sources.c', 'util-strings.c', ) 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) proto_c_template = files('ei-proto.c.tmpl') proto_h_template = files('ei-proto.h.tmpl') 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, ) 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-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', 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, ) 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] lib_liboeffis = library('oeffis', src_liboeffis, include_directories: [inc_builddir], dependencies: deps_liboeffis, gnu_symbol_visibility: 'hidden', version: soname, 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) pkgconfig.generate(lib_liboeffis, filebase: 'liboeffis-@0@'.format(libei_api_version), name: 'libOeffis', description: 'RemoteDesktop portal DBus helper library', subdirs: libei_api_dir, version: meson.project_version(), libraries: lib_liboeffis, ) endif libei-1.2.1/src/util-bits.c000066400000000000000000000103751456005336000154560ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2022 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 "util-bits.h" #if _enable_tests_ #include "util-munit.h" MUNIT_TEST(test_bits_flag_32) { uint32_t mask = 0; munit_assert_true(flag_fits(mask, 0)); munit_assert_true(flag_fits(mask, 31)); munit_assert_false(flag_fits(mask, 32)); munit_assert_false(flag_fits(mask, -1)); flag_set(mask, 0); munit_assert_true(flag_is_set(mask, 0)); munit_assert_false(flag_is_set(mask, 31)); munit_assert_false(flag_is_set(mask, 32)); flag_set(mask, 31); munit_assert_true(flag_is_set(mask, 0)); munit_assert_true(flag_is_set(mask, 31)); munit_assert_false(flag_is_set(mask, 32)); flag_set(mask, 32); /* silently ignored */ munit_assert_true(flag_is_set(mask, 0)); munit_assert_true(flag_is_set(mask, 31)); munit_assert_false(flag_is_set(mask, 32)); munit_assert_int(mask, ==, 0x80000001); flag_clear(mask, 0); munit_assert_false(flag_is_set(mask, 0)); munit_assert_true(flag_is_set(mask, 31)); munit_assert_false(flag_is_set(mask, 32)); flag_clear(mask, 31); munit_assert_false(flag_is_set(mask, 0)); munit_assert_false(flag_is_set(mask, 31)); munit_assert_false(flag_is_set(mask, 32)); flag_clear(mask, 32); munit_assert_false(flag_is_set(mask, 0)); munit_assert_false(flag_is_set(mask, 31)); munit_assert_false(flag_is_set(mask, 32)); return MUNIT_OK; } MUNIT_TEST(test_bits_flag_8) { uint8_t mask = 0; munit_assert_true(flag_fits(mask, 0)); munit_assert_true(flag_fits(mask, 7)); munit_assert_false(flag_fits(mask, 8)); munit_assert_false(flag_fits(mask, -1)); flag_set(mask, 0); munit_assert_true(flag_is_set(mask, 0)); munit_assert_false(flag_is_set(mask, 7)); munit_assert_false(flag_is_set(mask, 8)); flag_set(mask, 7); munit_assert_true(flag_is_set(mask, 0)); munit_assert_true(flag_is_set(mask, 7)); munit_assert_false(flag_is_set(mask, 8)); flag_set(mask, 8); /* silently ignored */ munit_assert_true(flag_is_set(mask, 0)); munit_assert_true(flag_is_set(mask, 7)); munit_assert_false(flag_is_set(mask, 8)); munit_assert_int(mask, ==, 0x81); flag_clear(mask, 0); munit_assert_false(flag_is_set(mask, 0)); munit_assert_true(flag_is_set(mask, 7)); munit_assert_false(flag_is_set(mask, 8)); flag_clear(mask, 7); munit_assert_false(flag_is_set(mask, 0)); munit_assert_false(flag_is_set(mask, 7)); munit_assert_false(flag_is_set(mask, 8)); flag_clear(mask, 8); munit_assert_false(flag_is_set(mask, 0)); munit_assert_false(flag_is_set(mask, 7)); munit_assert_false(flag_is_set(mask, 8)); return MUNIT_OK; } MUNIT_TEST(test_bits_mask) { munit_assert_true(mask_any(5, 3)); munit_assert_true(mask_any(5, 1)); munit_assert_false(mask_any(5, 2)); munit_assert_true(mask_all(5, 5)); munit_assert_true(mask_all(5, 1)); munit_assert_true(mask_all(5, 4)); munit_assert_false(mask_all(5, 6)); munit_assert_false(mask_all(5, 3)); munit_assert_true(mask_all(13, 5)); munit_assert_true(mask_all(13, 12)); munit_assert_true(mask_none(21, 10)); munit_assert_false(mask_none(21, 5)); uint8_t mask = 0; mask_add(mask, 5); munit_assert_int(mask, ==, 5); mask_add(mask, 2); munit_assert_int(mask, ==, 7); mask_remove(mask, 2); munit_assert_int(mask, ==, 5); return MUNIT_OK; } #endif libei-1.2.1/src/util-bits.h000066400000000000000000000071211456005336000154560ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2008-2011 Kristian Høgsberg * Copyright © 2011 Intel Corporation * Copyright © 2013-2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "config.h" #include #include #include #define bit(x_) (1UL << (x_)) #define NBITS(b) (b * 8) #define LONG_BITS (sizeof(long) * 8) #define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS) #define NCHARS(x) ((size_t)(((x) + 7) / 8)) #define flag_fits(mask_, b_) !!((unsigned long)(b_) < (unsigned long)NBITS(sizeof(mask_))) #define flag_is_set(mask_, b_) (flag_fits(mask_, b_) && !!((mask_) & bit(b_))) #define flag_set(mask_, b_) do { if (flag_fits(mask_, b_)) { (mask_) |= bit(b_); } } while(0) #define flag_clear(mask_, b_) do { if (flag_fits(mask_, b_)) { (mask_) &= ~bit(b_); } } while(0) /** Add all bits in m_ to the existing mask */ #define mask_add(mask_, m_) (mask_) |= (m_) /** Remove all bits in m_ from the existing mask */ #define mask_remove(mask_, m_) (mask_) &= ~(m_) /** True if any of the bits in m_ are set in mask */ #define mask_any(mask_, m_) (((mask_) & (m_)) != 0) /** True if all of the bits in m_ are set in mask */ #define mask_all(mask_, m_) (((mask_) & (m_)) == (m_)) /** True if none of the bits in m_ are set in mask */ #define mask_none(mask_, m_) (((mask_) & (m_)) == 0) /* This bitfield helper implementation is taken from from libevdev-util.h, * except that it has been modified to work with arrays of unsigned chars */ static inline bool bit_is_set(const unsigned char *array, int bit) { return !!(array[bit / 8] & (1 << (bit % 8))); } static inline void set_bit(unsigned char *array, int bit) { array[bit / 8] |= (1 << (bit % 8)); } static inline void clear_bit(unsigned char *array, int bit) { array[bit / 8] &= ~(1 << (bit % 8)); } static inline bool long_bit_is_set(const unsigned long *array, int bit) { return !!(array[bit / LONG_BITS] & (1ULL << (bit % LONG_BITS))); } static inline void long_set_bit(unsigned long *array, int bit) { array[bit / LONG_BITS] |= (1ULL << (bit % LONG_BITS)); } static inline void long_clear_bit(unsigned long *array, int bit) { array[bit / LONG_BITS] &= ~(1ULL << (bit % LONG_BITS)); } static inline void long_set_bit_state(unsigned long *array, int bit, int state) { if (state) long_set_bit(array, bit); else long_clear_bit(array, bit); } static inline bool long_any_bit_set(unsigned long *array, size_t size) { unsigned long i; assert(size > 0); for (i = 0; i < size; i++) if (array[i] != 0) return true; return false; } libei-1.2.1/src/util-color.h000066400000000000000000000105221456005336000156320ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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. */ /* ANSI color codes for terminal colors */ #pragma once #include "config.h" #include #include #include #include #include #define COLOR_SET \ X(RESET, "\x1B[0m") \ X(BLACK, "\x1B[0;30m") \ X(RED, "\x1B[0;31m") \ X(GREEN, "\x1B[0;32m") \ X(YELLOW, "\x1B[0;33m") \ X(BLUE, "\x1B[0;34m") \ X(MAGENTA, "\x1B[0;35m") \ X(CYAN, "\x1B[0;36m") \ X(WHITE, "\x1B[0;37m") \ X(BRIGHT_RED, "\x1B[0;31;1m") \ X(BRIGHT_GREEN, "\x1B[0;32;1m") \ X(BRIGHT_YELLOW, "\x1B[0;33;1m") \ X(BRIGHT_BLUE, "\x1B[0;34;1m") \ X(BRIGHT_MAGENTA, "\x1B[0;35;1m") \ X(BRIGHT_CYAN, "\x1B[0;36;1m") \ X(BRIGHT_WHITE, "\x1B[0;37;1m") \ X(HIGHLIGHT, "\x1B[0;1;39m") \ /** * Expands into * enum Color { * RESET, * RED, * ... * } */ enum AnsiColor { #define X(name, code) name, COLOR_SET #undef X }; /** * Expands into an array usable via: colorcode[RED], etc. */ static const char * const ansi_colorcode[] __attribute__((unused)) = { #define X(name, code) code, COLOR_SET #undef X }; /* ANSI escape sequence for true RBB colors */ #define ANSI_FG_RGB(r, g, b) "\x1B[38;2;" #r ";" #g ";" #b "m" #define ANSI_BG_RGB(r, g, b) "\x1B[48;2;" #r ";" #g ";" #b "m" #define RGB(r_, g_, b_) ((r_ << 16) | (g_ << 8) | b_) #define RGB_BG(r_, g_, b_) (((r_ << 16) | (g_ << 8) | b_) << 32) static inline uint64_t rgb(uint8_t r, uint8_t g, uint8_t b) { return (r << 16) | (g << 8) | b; } static inline uint64_t rgb_bg(uint8_t r, uint8_t g, uint8_t b) { return (((uint64_t)r << 16) | ((uint64_t)g << 8) | (uint64_t)b) << 32; } static inline bool __attribute__((format(printf, 3, 0))) cvdprintf(int fd, uint64_t rgb, const char *format, va_list args) { char buf[1024]; int rc = vsnprintf(buf, sizeof(buf), format, args); if (rc < 0 || (size_t)rc >= sizeof(buf)) return false; if (!isatty(fd)) { dprintf(fd, "%s", buf); } else { uint32_t fg = rgb & 0xffffff; uint32_t bg = (rgb >> 32) & 0xffffff; uint8_t r = (fg & 0xff0000) >> 16, g = (fg & 0x00ff00) >> 8, b = (fg & 0x0000ff); uint8_t bgr = (bg & 0xff0000) >> 16, bgg = (bg & 0x00ff00) >> 8, bgb = (bg & 0x0000ff); const char *color_fg = "", *color_bg = ""; const char *reset = ansi_colorcode[RESET]; char fgstr[64]; char bgstr[64]; if (fg) { snprintf(fgstr, sizeof(fgstr), "\x1B[38;2;%u;%u;%um", r, g, b); color_fg = fgstr; } if (bg) { snprintf(bgstr, sizeof(bgstr), "\x1B[48;2;%u;%u;%um", bgr, bgg, bgb); color_bg = bgstr; } dprintf(fd, "%s%s%s%s", color_bg, color_fg, buf, reset); } return true; } static inline void __attribute__((format(printf, 3, 4))) cdprintf(int fd, uint64_t rgb, const char *format, ...) { va_list args; va_start(args, format); cvdprintf(fd, rgb, format, args); va_end(args); } static inline void __attribute__((format(printf, 2, 3))) cprintf(uint64_t rgb, const char *format, ...) { va_list args; va_start(args, format); cvdprintf(STDOUT_FILENO, rgb, format, args); va_end(args); } static inline void __attribute__((format(printf, 3, 4))) cfprintf(FILE *fp, uint64_t rgb, const char *format, ...) { va_list args; va_start(args, format); cvdprintf(fileno(fp), rgb, format, args); va_end(args); } libei-1.2.1/src/util-io.c000066400000000000000000000514321456005336000151230ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "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) { const size_t MAX_FDS = 32; char control[CMSG_SPACE(MAX_FDS * sizeof(int))]; struct iovec iov = { .iov_base = buf, .iov_len = count, }; struct msghdr msg = { .msg_name = NULL, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1, .msg_control = control, .msg_controllen = sizeof(control), }; int received = xerrno(SYSCALL(recvmsg(fd, &msg, 0))); if (received > 0) { *fds = NULL; _cleanup_free_ int *fd_return = calloc(MAX_FDS + 1, sizeof(int)); size_t idx = 0; for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; hdr = CMSG_NXTHDR(&msg, hdr)) { if (hdr->cmsg_level != SOL_SOCKET || hdr->cmsg_type != SCM_RIGHTS) continue; size_t nfds = (hdr->cmsg_len - CMSG_LEN(0)) / sizeof (int); int *fd = (int *)CMSG_DATA(hdr); for (size_t i = 0; i < nfds; i++) { fd_return[idx++] = *fd; fd++; if (idx >= MAX_FDS) break; } } fd_return[idx] = -1; *fds = steal(&fd_return); } return received; } int xsend_with_fd(int fd, const void *buf, size_t len, int *fds) { size_t nfds = 0; for (nfds = 0; fds != NULL && fds[nfds] != -1; nfds++) { /* noop */ } if (nfds == 0) return xsend(fd, buf, len); char control[CMSG_SPACE(nfds * sizeof(int))]; struct cmsghdr *header = (struct cmsghdr*)control; memset(control, 0, sizeof(control)); struct iovec iov = { .iov_base = (void*)buf, .iov_len = len, }; struct msghdr msg = { .msg_name = NULL, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1, .msg_control = control, .msg_controllen = sizeof(control), }; header->cmsg_len = CMSG_LEN(nfds * sizeof(int)); header->cmsg_level = SOL_SOCKET; header->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(CMSG_FIRSTHDR(&msg)), fds, nfds * sizeof(int)); return xerrno(SYSCALL(sendmsg(fd, &msg, MSG_NOSIGNAL))); } /* consider this struct opaque */ struct iobuf { size_t sz; size_t len; uint8_t *data; int fds[32]; }; struct iobuf * iobuf_new(size_t size) { struct iobuf *buf = malloc(sizeof(*buf)); uint8_t *data = malloc(size); assert(buf); assert(data); *buf = (struct iobuf) { .sz = size, .len = 0, .data = data, }; int *fd; ARRAY_FOR_EACH(buf->fds, fd) { *fd = -1; } return buf; } /** * The count of data bytes in this buffer. */ size_t iobuf_len(struct iobuf *buf) { return buf->len; } /** * Drop the first nbytes from the buffer. */ void iobuf_pop(struct iobuf *buf, size_t nbytes) { assert(nbytes <= buf->len); if (nbytes == buf->len) { buf->len = 0; } else { memmove(buf->data, buf->data + nbytes, buf->len - nbytes); buf->len -= nbytes; } } /** * Pointer to the data bytes. Note that the buffer is considered binary * data. The caller must ensure that any strings stored in the buffer are * null-terminated. * * The returned pointer only valid in the immediate scope, any iobuf * function may invalidate the pointer. */ const uint8_t * iobuf_data(struct iobuf *buf) { return buf->data; } /** * Pointer to the first byte after the end of the data bytes. * * The returned pointer only valid in the immediate scope, any iobuf * function may invalidate the pointer. */ const uint8_t * iobuf_data_end(struct iobuf *buf) { return buf->data + buf->len; } /** * Return the next available file descriptor in this buffer or -1. * The fd is removed from this buffer and belongs to the caller. */ int 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) * sizeof(*buf->fds)); return fd; } static inline void iobuf_resize(struct iobuf *buf, size_t 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. */ uint8_t * iobuf_take_data(struct iobuf *buf) { uint8_t *data = buf->data; buf->data = NULL; buf->len = 0; iobuf_resize(buf, buf->sz); return data; } /** * Append len bytes to the buffer. If the data exceeds the current buffer * size it is resized automatically. */ void iobuf_append(struct iobuf *buf, const void *data, size_t len) { if (len == 0) return; 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 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); } if (buf->len > 0) memmove(buf->data + len, buf->data, buf->len); memcpy(buf->data, data, len); buf->len += len; } int iobuf_append_fd(struct iobuf *buf, int fd) { /* Array must remain terminated by -1 */ for (size_t idx = 0; idx < ARRAY_LENGTH(buf->fds) - 1; idx ++) { if (buf->fds[idx] == -1) { int f = dup(fd); if (f == -1) return -errno; buf->fds[idx] = f; return 0; } } return -ENOMEM; } /** * 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 * data exceeds the current buffer size it is resized automatically. * * @return The number of bytes read or a negative errno on failure. Zero * indicates EOF. */ int iobuf_append_from_fd(struct iobuf *buf, int fd) { char data[1024]; size_t nread = 0; ssize_t rc; do { rc = xread(fd, data, sizeof(data)); if (rc == 0 || rc == -EAGAIN) { break; } else if (rc < 0) { return rc; } iobuf_append(buf, data, rc); nread += rc; } while (rc == sizeof(data)); return nread == 0 ? rc : (int)nread; } /** * 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 * data exceeds the current buffer size it is resized automatically. * * 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. */ int iobuf_recv_from_fd(struct iobuf *buf, int fd) { char data[1024]; size_t nread = 0; ssize_t rc; do { _cleanup_free_ int *fds = NULL; rc = xread_with_fds(fd, data, sizeof(data), &fds); if (rc == 0 || rc == -EAGAIN) { break; } else if (rc < 0) { return rc; } iobuf_append(buf, data, rc); if (fds) { int *fd = fds; for (size_t idx = 0; *fd != -1 && idx < ARRAY_LENGTH(buf->fds) - 1; idx++) { if (buf->fds[idx] == -1) { buf->fds[idx] = *fd; fd++; } } } nread += rc; } while (rc == sizeof(data)); return nread == 0 ? rc : (int)nread; } int iobuf_send(struct iobuf *buf, int fd) { return xsend_with_fd(fd, buf->data, buf->len, buf->fds); } /** * Release the memory associated with this iobuf. Use iobuf_take_data() * prevent the data from being free()d. */ struct iobuf * iobuf_free(struct iobuf *buf) { if (buf) { free(buf->data); buf->sz = 0; buf->len = 0; buf->data = NULL; int fd; while ((fd = iobuf_take_fd(buf)) != -1) xclose(fd); free(buf); } return NULL; } #if _enable_tests_ #include "util-munit.h" #include "util-strings.h" MUNIT_TEST(test_iobuf_new) { /* test allocation and freeing a buffer */ struct iobuf *buf = iobuf_new(10); munit_assert_size(buf->sz, ==, 10); munit_assert_size(buf->len, ==, 0); munit_assert_size(iobuf_len(buf), ==, 0); buf = iobuf_free(buf); munit_assert_null(buf); return MUNIT_OK; } MUNIT_TEST(test_iobuf_cleanup) { /* Test the attribute(cleanup) define. This test needs to run in * valgrind --leak-check=full to be really useful */ _cleanup_iobuf_ struct iobuf *buf = iobuf_new(10); _cleanup_iobuf_ struct iobuf *nullbuf = NULL; assert(buf); assert(nullbuf == NULL); 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 */ _cleanup_iobuf_ struct iobuf *buf = iobuf_new(10); /* append data without a resize */ const char data[] = "foo"; iobuf_append(buf, data, 3); size_t expected_size = 3; munit_assert_size(buf->len, ==, expected_size); munit_assert_size(iobuf_len(buf), ==, expected_size); munit_assert_size(buf->sz, ==, 10); /* we don't have a trailing \0 */ 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'); /* prepend data without resize */ const char prepend_data[] = "bar"; iobuf_prepend(buf, prepend_data, 3); expected_size += 3; munit_assert_size(buf->len, ==, expected_size); munit_assert_size(iobuf_len(buf), ==, expected_size); munit_assert_size(buf->sz, ==, 10); /* we don't have a trailing \0 */ bufdata = iobuf_data(buf); munit_assert_char(bufdata[0], ==, 'b'); munit_assert_char(bufdata[1], ==, 'a'); munit_assert_char(bufdata[2], ==, 'r'); munit_assert_char(bufdata[3], ==, 'f'); munit_assert_char(bufdata[4], ==, 'o'); munit_assert_char(bufdata[5], ==, 'o'); /* Now append enough data to force a buffer resize */ const char data2[] = "data forcing resize"; iobuf_append(buf, data2, sizeof(data2)); /* includes \0 */ expected_size += sizeof(data2); 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((const char *)iobuf_data(buf), "barfoodata forcing resize"); /* and again with prepending */ const char prepend_data2[] = "second resize"; iobuf_prepend(buf, prepend_data2, strlen(prepend_data2)); /* does not include \0 */ expected_size += strlen(prepend_data2); munit_assert_size(iobuf_len(buf), ==, expected_size); munit_assert_size(buf->sz, ==, expected_size); 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; } MUNIT_TEST(test_iobuf_prepend_empty_buffer) { /* Test prepending data */ _cleanup_iobuf_ struct iobuf *buf = iobuf_new(10); const char data[] = "foo"; iobuf_prepend(buf, data, 3); size_t expected_size = 3; munit_assert_size(buf->len, ==, expected_size); munit_assert_size(iobuf_len(buf), ==, expected_size); munit_assert_size(buf->sz, ==, 10); /* we don't have a trailing \0 */ 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'); return MUNIT_OK; } MUNIT_TEST(test_iobuf_pop) { _cleanup_iobuf_ struct iobuf *buf = iobuf_new(10); const char data[] = "foobar"; iobuf_append(buf, data, strlen(data)); munit_assert_size(iobuf_len(buf), ==, 6); iobuf_pop(buf, 3); munit_assert_size(iobuf_len(buf), ==, 3); /* we don't have a trailing \0 */ 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'); return MUNIT_OK; } 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 */ const char data[] = "foobar"; const char nullbyte = '\0'; iobuf_append(buf, data, 3); iobuf_append(buf, &nullbyte, 1); munit_assert_size(buf->len, ==, 4); munit_assert_size(iobuf_len(buf), ==, 4); munit_assert_size(buf->sz, ==, 10); munit_assert_string_equal((const char *)iobuf_data(buf), "foo"); return MUNIT_OK; } MUNIT_TEST(test_iobuf_append_fd) { _cleanup_iobuf_ struct iobuf *buf = iobuf_new(10); int fds[2]; int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, fds); munit_assert_int(rc, ==, 0); int wr = fds[0], rd = fds[1]; /* write some data */ const char data[] = "foobar"; int wlen = xwrite(wr, data, 4); munit_assert_int(wlen, ==, 4); /* read that data */ int rlen = iobuf_append_from_fd(buf, rd); munit_assert_int(rlen, ==, 4); munit_assert_size(iobuf_len(buf), ==, 4); /* so we can do strcmp */ const char nullbyte = '\0'; iobuf_append(buf, &nullbyte, 1); 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); munit_assert_int(blocking_read, ==, -EAGAIN); const char largebuffer[2048] = {0xaa}; /* read data exactly our internal buffer size */ wlen = xwrite(wr, largebuffer, 1024); munit_assert_int(wlen, ==, 1024); int read_1024 = iobuf_append_from_fd(buf, rd); munit_assert_int(read_1024, ==, 1024); /* read data exactly our internal buffer size + 1*/ wlen = xwrite(wr, largebuffer, 1025); munit_assert_int(wlen, ==, 1025); int read_1025 = iobuf_append_from_fd(buf, rd); munit_assert_int(read_1025, ==, 1025); /* close write side, read nothing */ xclose(wr); int read_none = iobuf_append_from_fd(buf, rd); munit_assert_int(read_none, ==, 0); /* close read side, expect error */ xclose(rd); int read_fail = iobuf_append_from_fd(buf, rd); munit_assert_int(read_fail, ==, -EBADF); return MUNIT_OK; } MUNIT_TEST(test_iobuf_append_fd_too_many) { _cleanup_fclose_ FILE *fp = tmpfile(); 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]; int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, fds); munit_assert_int(rc, ==, 0); _cleanup_close_ int left = fds[0]; _cleanup_close_ int right = fds[1]; _cleanup_fclose_ FILE *fp = tmpfile(); /* actual message data to be sent */ char data[] = "some data\n"; /* Send the fd from left to right */ _cleanup_iobuf_ struct iobuf *sender = iobuf_new(20); iobuf_append(sender, data, sizeof(data)); iobuf_append_fd(sender, fileno(fp)); int sendrc = iobuf_send(sender, left); munit_assert_int(sendrc, ==, sizeof(data)); _cleanup_iobuf_ struct iobuf *buf = iobuf_new(64); rc = iobuf_recv_from_fd(buf, right); munit_assert_int(rc, ==, sizeof(data)); _cleanup_close_ int fd = iobuf_take_fd(buf); munit_assert_int(fd, !=, -1); return MUNIT_OK; } MUNIT_TEST(test_pass_fd) { int fds[2]; int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, fds); munit_assert_int(rc, ==, 0); _cleanup_close_ int left = fds[0]; _cleanup_close_ int right = fds[1]; FILE *fps[4]; int sendfds[ARRAY_LENGTH(fps) + 1]; for (size_t idx = 0; idx < ARRAY_LENGTH(fps); idx++) { FILE *fp = tmpfile(); munit_assert_not_null(fp); fps[idx] = fp; sendfds[idx] = fileno(fp); sendfds[idx + 1] = -1; } /* actual message data to be sent */ char data[] = "some data\n"; /* Send the fd from left to right */ int sendrc = xsend_with_fd(left, data, sizeof(data), sendfds); munit_assert_int(sendrc, ==, sizeof(data)); /* 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); FILE *fp = fps[idx]; fwrite(buf, strlen(buf) + 1, 1, fp); fflush(fp); } /* Receive the fd on the right */ _cleanup_free_ int *recvfds = NULL; char recvbuf[sizeof(data)]; int recvrc = xread_with_fds(right, recvbuf, sizeof(recvbuf), &recvfds); munit_assert_int(recvrc, ==, sizeof(data)); munit_assert_string_equal(recvbuf, data); munit_assert_ptr_not_null(recvfds); munit_assert_int(recvfds[0], !=, -1); munit_assert_int(recvfds[1], !=, -1); munit_assert_int(recvfds[2], !=, -1); munit_assert_int(recvfds[3], !=, -1); munit_assert_int(recvfds[4], ==, -1); /* Now check that we can read "foo N" from the passed fd */ for (size_t idx = 0; idx < ARRAY_LENGTH(fps); idx++) { _cleanup_close_ int passed_fd = recvfds[idx]; off_t off = lseek(passed_fd, 0, SEEK_SET); munit_assert_int(off, ==, 0); char readbuf[64]; int readrc = xread(passed_fd, readbuf, sizeof(readbuf)); _cleanup_free_ char *expected = xaprintf("foo %zu\n", idx); munit_assert_int(readrc, ==, strlen(expected) + 1); munit_assert_string_equal(readbuf, expected); /* cleanup */ FILE *fp = fps[idx]; fclose(fp); } return MUNIT_OK; } static inline void sigblock_helper(void) { with_signals_blocked(SIGPIPE, SIGALRM) { break; /* breaking out of loop must clean up too */ } } MUNIT_TEST(test_signal_blocker) { int rc; sigset_t mask; int count = 0; with_signals_blocked(SIGPIPE, SIGALRM) { rc = sigprocmask(SIG_BLOCK, NULL, &mask); munit_assert_int(rc, !=, -1); munit_assert(sigismember(&mask, SIGPIPE)); munit_assert(sigismember(&mask, SIGALRM)); munit_assert(!sigismember(&mask, SIGINT)); /* We didn't touch that one */ ++count; } munit_assert_int(count, ==, 1); /* loop body only entered once */ rc = sigprocmask(SIG_BLOCK, NULL, &mask); munit_assert_int(rc, !=, -1); munit_assert(!sigismember(&mask, SIGPIPE)); munit_assert(!sigismember(&mask, SIGALRM)); sigblock_helper(); rc = sigprocmask(SIG_BLOCK, NULL, &mask); munit_assert_int(rc, !=, -1); munit_assert(!sigismember(&mask, SIGPIPE)); munit_assert(!sigismember(&mask, SIGALRM)); return MUNIT_OK; } #endif libei-1.2.1/src/util-io.h000066400000000000000000000225601456005336000151300ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include #include #include #include #include #include #include "util-strings.h" #include "util-mem.h" /** * Blocks the zero-terminated list of signals * * @return the previous set of signals to pass to signals_release() */ static inline sigset_t signals_block(int signal, ...) { sigset_t old_mask; sigset_t new_mask; va_list sigs; va_start(sigs, signal); int sigcount = 0; sigemptyset(&new_mask); while (signal != 0) { sigaddset(&new_mask, signal); signal = va_arg(sigs, int); assert(++sigcount < 16); /* likely missing zero-terminator */ } sigprocmask(SIG_BLOCK, &new_mask, &old_mask); return old_mask; } /** * Release signals and revert back to the old sigmask * * @param mask: The previous set of signals as returned by signals_block(). * @return Always 0 */ static inline int signals_release(sigset_t mask) { sigprocmask(SIG_SETMASK, &mask, NULL); return 0; } /** * Python-like context manager for blocking signals, e.g. * * void foo(void) { * ... * with_signals_blocked(SIGALARM, SIGINT) { * do_something(); * } * // signals are unblocked again. */ #define with_signals_blocked(...) \ for (_cleanup_(signal_blocked_destroy) struct _sigblock b_ = { .mask = signals_block(__VA_ARGS__, 0), .is_blocked = true }; \ b_.is_blocked; \ b_.is_blocked = false) struct _sigblock { sigset_t mask; bool is_blocked; }; static inline void signal_blocked_destroy(struct _sigblock *s) { signals_release(s->mask); } /** * Wrapper to convert an errno-setting syscall into a * value-or-negative-errno. * * Use: int rc = xerrno(foo(bar)); */ static inline int xerrno(int value) { return value < 0 ? -errno : value; } /** * Wrapper around close() that blocks the SIGALRM signal. It checks for * fd != -1 to satisfy coverity and friends and always returns -1. */ static inline int xclose(int fd) { if (fd != -1) { /* Not SYSCALL(), see libei MR!261#note_2131802 */ close(fd); } return -1; } DEFINE_TRIVIAL_CLEANUP_FUNC(int, xclose); #define _cleanup_close_ _cleanup_(xclosep) DEFINE_TRIVIAL_CLEANUP_FUNC(FILE *, fclose); #define _cleanup_fclose_ _cleanup_(fclosep) /** * Wrapper around read() that blocks the SIGALRM signal. * Returns the number of bytes read or a negative errno on failure. */ static inline int xread(int fd, void *buf, size_t count) { return xerrno(SYSCALL(read(fd, buf, count))); } /** * Wrapper around read() that blocks the SIGALRM signal. * Returns the number of bytes read or a negative errno on failure. * Any fds passed along with the message are stored in the -1-terminated * allocated fds array, to be freed by the caller. Where no fds were * passed, the array is NULL. */ int xread_with_fds(int fd, void *buf, size_t count, int **fds); /** * Wrapper around write() that blocks the SIGALRM signal. * Returns the number of bytes written or a negative errno on failure. */ static inline int xwrite(int fd, const void *buf, size_t count) { return xerrno(SYSCALL(write(fd, buf, count))); } /** * Wrapper around send() that always sets MSG_NOSIGNAL and blocks the * SIGALRM signal. * Returns the number of bytes written or a negative errno on failure. */ static inline int xsend(int fd, const void *buf, size_t len) { return xerrno(SYSCALL(send(fd, buf, len, MSG_NOSIGNAL))); } /** * Wrapper around pipe2() that always blocks the SIGALRM signal. */ static inline int xpipe2(int pipefd[2], int flags) { return SYSCALL(pipe2(pipefd, flags)); } /** * Wrapper around dup() that always blocks the SIGALRM signal. */ static inline int xdup(int fd) { return SYSCALL(dup(fd)); } /** * Wrapper around send() that always sets MSG_NOSIGNAL and allows appending * file descriptors to the message. * * @param fds Array of file descriptors, terminated by -1. */ int xsend_with_fd(int fd, const void *buf, size_t len, int *fds); /** * Connect to the socket in the given path. Returns an fd or a negative * errno on failure. */ static inline int xconnect(const char *path) { struct sockaddr_un addr = { .sun_family = AF_UNIX, .sun_path = {0}, }; if (!xsnprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path)) return -EINVAL; int sockfd = xerrno(SYSCALL(socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0))); if (sockfd < 0) return sockfd; int rc = xerrno(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr))); if (rc < 0) return rc; return sockfd; } /** * Create a new iobuf structure with the given size. */ struct iobuf * iobuf_new(size_t size); /** * The count of data bytes in this buffer. */ size_t iobuf_len(struct iobuf *buf); /** * Pointer to the data bytes. Note that the buffer is considered binary * data. The caller must ensure that any strings stored in the buffer are * null-terminated. * * The returned pointer only valid in the immediate scope, any iobuf * function may invalidate the pointer. */ const uint8_t * iobuf_data(struct iobuf *buf); /** * Pointer to the first byte after the end of the data bytes. * * The returned pointer only valid in the immediate scope, any iobuf * function may invalidate the pointer. */ const uint8_t * iobuf_data_end(struct iobuf *buf); /** * Return the next available file descriptor in this buffer or -1. * The fd is removed from this buffer and belongs to the caller. */ int 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. */ uint8_t * iobuf_take_data(struct iobuf *buf); /** * Drop the first nbytes from the buffer. */ void iobuf_pop(struct iobuf *buf, size_t nbytes); /** * Append len bytes to the buffer. If the data exceeds the current buffer * size it is resized automatically. */ void 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 void *data, size_t len); /** * Append a file descriptor to the buffer. The file descriptor is dup()ed. * * Returns zero on success or a negative errno on failure. */ int 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 * data exceeds the current buffer size it is resized automatically. * * @return The number of bytes read or a negative errno on failure. Zero * indicates EOF. */ int 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 * data exceeds the current buffer size it is resized automatically. * * Any file descriptors passed through the fd are placed * * @return The number of bytes read or a negative errno on failure. Zero * indicates EOF. */ int iobuf_recv_from_fd(struct iobuf *buf, int fd); /** * Send the data in the buffer (with fds, if need be) and return * the number of bytes sent or a negative errno on failure. */ int iobuf_send(struct iobuf *buf, int fd); /** * Release the memory associated with this iobuf. Use iobuf_take_data() * prevent the data from being free()d. */ struct iobuf * iobuf_free(struct iobuf *buf); static inline void _iobuf_cleanup(struct iobuf **buf) { iobuf_free(*buf); } /** * Helper macro to auto-free a iobuf struct when the variable goes out of * scope. Use like this: * _cleanup_iobuf_ struct iobuf *foo = iobuf_new(64); */ #define _cleanup_iobuf_ __attribute__((cleanup(_iobuf_cleanup))) libei-1.2.1/src/util-list.c000066400000000000000000000131541456005336000154660ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2008-2011 Kristian Høgsberg * Copyright © 2011 Intel Corporation * Copyright © 2013-2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include "util-list.h" void list_init(struct list *list) { list->prev = list; list->next = list; } void list_insert(struct list *list, struct list *elm) { assert((list->next != NULL && list->prev != NULL) || !"list->next|prev is NULL, possibly missing list_init()"); assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || !"elm->next|prev is not NULL, list node used twice?"); elm->prev = list; elm->next = list->next; list->next = elm; elm->next->prev = elm; } void list_append(struct list *list, struct list *elm) { assert((list->next != NULL && list->prev != NULL) || !"list->next|prev is NULL, possibly missing list_init()"); assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || !"elm->next|prev is not NULL, list node used twice?"); elm->next = list; elm->prev = list->prev; list->prev = elm; elm->prev->next = elm; } void list_remove(struct list *elm) { assert((elm->next != NULL && elm->prev != NULL) || !"list->next|prev is NULL, possibly missing list_init()"); elm->prev->next = elm->next; elm->next->prev = elm->prev; elm->next = NULL; elm->prev = NULL; } bool list_empty(const struct list *list) { assert((list->next != NULL && list->prev != NULL) || !"list->next|prev is NULL, possibly missing list_init()"); return list->next == list; } #if _enable_tests_ #include "util-munit.h" #include "util-macros.h" MUNIT_TEST(test_list_insert) { struct list_test { int val; struct list node; } tests[] = { { .val = 1 }, { .val = 2 }, { .val = 3 }, { .val = 4 }, }; struct list_test *t; struct list head; list_init(&head); munit_assert(list_empty(&head)); ARRAY_FOR_EACH(tests, t) { list_insert(&head, &t->node); } int val = 4; list_for_each(t, &head, node) { munit_assert_int(t->val, ==, val); val--; } munit_assert_int(val, ==, 0); return MUNIT_OK; } MUNIT_TEST(test_list_append) { struct list_test { int val; struct list node; } tests[] = { { .val = 1 }, { .val = 2 }, { .val = 3 }, { .val = 4 }, }; struct list_test *t; struct list head; list_init(&head); munit_assert(list_empty(&head)); ARRAY_FOR_EACH(tests, t) { list_append(&head, &t->node); } int val = 1; list_for_each(t, &head, node) { munit_assert_int(t->val, ==, val); val++; } munit_assert_int(val, ==, 5); return MUNIT_OK; } MUNIT_TEST(test_list_nth) { struct list_test { int val; struct list node; } tests[] = { { .val = 1 }, { .val = 2 }, { .val = 3 }, { .val = 4 }, }; struct list_test *t; struct list head; list_init(&head); ARRAY_FOR_EACH(tests, t) { list_append(&head, &t->node); } int idx = 0; ARRAY_FOR_EACH(tests, t) { struct list_test *nth = list_nth_entry(struct list_test, &head, node, idx); munit_assert_int(nth->val, ==, tests[idx].val); idx++; } munit_assert(list_nth_entry(struct list_test, &head, node, 10) == NULL); list_init(&head); munit_assert(list_nth_entry(struct list_test, &head, node, 0) == NULL); munit_assert(list_nth_entry(struct list_test, &head, node, 1) == NULL); return MUNIT_OK; } MUNIT_TEST(list_first_last) { struct list_test { int val; struct list node; } tests[] = { { .val = 1 }, { .val = 2 }, { .val = 3 }, { .val = 4 }, }; struct list_test *t; struct list head; list_init(&head); ARRAY_FOR_EACH(tests, t) { list_append(&head, &t->node); } struct list_test *first = list_first_entry(&head, t, node); munit_assert_int(first->val, ==, 1); first = list_first_entry_by_type(&head, struct list_test, node); munit_assert_int(first->val, ==, 1); struct list_test *last = list_last_entry(&head, t, node); munit_assert_int(last->val, ==, 4); last = list_last_entry_by_type(&head, struct list_test, node); munit_assert_int(last->val, ==, 4); return MUNIT_OK; } MUNIT_TEST(list_foreach) { struct list_test { int val; struct list node; } tests[] = { { .val = 1 }, { .val = 2 }, { .val = 3 }, { .val = 4 }, }; struct list_test *t; struct list head; list_init(&head); ARRAY_FOR_EACH(tests, t) { list_append(&head, &t->node); } unsigned int idx = 0; list_for_each(t, &head, node) { munit_assert_int(t->val, ==, tests[idx++].val); } list_for_each_backwards(t, &head, node) { munit_assert_int(t->val, ==, tests[--idx].val); } return MUNIT_OK; } #endif libei-1.2.1/src/util-list.h000066400000000000000000000160111456005336000154660ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2008-2011 Kristian Høgsberg * Copyright © 2011 Intel Corporation * Copyright © 2013-2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "config.h" #include #include /* * This list data structure is a verbatim copy from wayland-util.h from the * Wayland project; except that wl_ prefix has been removed. */ /** * Doubly linked list implementation. This struct is used for both the list * nodes and the list head. Use like this: * * @code * * struct foo { * struct list list_of_bars; // the list head * }; * * struct bar { * struct list link; // links between the bars * }; * * struct foo *f = zalloc(sizeof *f); * struct bar *b = make_some_bar(); * * list_init(&f->list_of_bars); * list_append(&f->list_of_bars, &b->link); * list_remove(&b->link); * @endcode */ struct list { struct list *prev; struct list *next; }; /** * Initialize a list head. This function *must* be called once for each list * head. This function *must not* be called for a node to be added to a * list. */ void list_init(struct list *list); /** * Insert an element at the front of the list */ void list_insert(struct list *list, struct list *elm); /** * Append an element to the back of the list */ void list_append(struct list *list, struct list *elm); /** * Remove an element from list. * * Removing a list element is only possible once, the caller must track * whether the list node has already been removed. * */ void list_remove(struct list *elm); /** * Returns true if the given list head is an empty list. */ bool list_empty(const struct list *list); /** * Return the 'type' parent container struct of 'ptr' of which * 'member' is our 'ptr' field. For example: * * @code * struct foo { // the parent container struct * uint32_t a; * struct bar bar_member; // the member field * }; * * struct foo *f = zalloc(sizeof *f); * struct bar *b = &f->bar_member; * struct foo *f2 = container_of(b, struct foo, bar_member); * * assert(f == f2); * @endcode */ #define container_of(ptr, type, member) \ (__typeof__(type) *)((char *)(ptr) - \ offsetof(__typeof__(type), member)) /** * Given a list 'head', return the first entry of type 'pointer_of_type' that * has a member 'link'. * * The 'pointer_of_type' argument is solely used to determine the type be * returned and not modified otherwise. It is common to use the same pointer * that the return value of list_first_entry() is assigned to, for example: * * @code * struct foo { * struct list list_of_bars; * }; * * struct bar { * struct list link; * } * * struct foo *f = get_a_foo(); * struct bar *b = 0; // initialize to avoid static analysis errors * b = list_first_entry(&f->list_of_bars, b, link); * @endcode */ #define list_first_entry(head, pointer_of_type, member) \ container_of((head)->next, __typeof__(*pointer_of_type), member) /** * Return the last entry of the list. See list_first_entry() for details. */ #define list_last_entry(head, pointer_of_type, member) \ container_of((head)->prev, __typeof__(*pointer_of_type), member) /** * Given a list 'head', return the first entry of type 'container_type' that * has a member 'link'. * * @code * struct foo { * struct list list_of_bars; * }; * * struct bar { * struct list link; * } * * struct foo *f = get_a_foo(); * struct bar *b = list_first_entry(&f->list_of_bars, struct bar, link); * @endcode */ #define list_first_entry_by_type(head, container_type, member) \ container_of((head)->next, container_type, member) /** * Return the last entry of the list. See list_first_entry_by_type() for details. */ #define list_last_entry_by_type(head, container_type, member) \ container_of((head)->prev, container_type, member) /** * Iterate through the list. * * @code * struct foo *f = get_a_foo(); * struct bar *element; * list_for_each(element, &f->list_of_bars, link) { * } * @endcode * * If a list node needs to be removed during iteration, use * list_for_each_safe(). */ #define list_for_each(pos, head, member) \ for (pos = list_first_entry_by_type(head, __typeof__(*pos), member); \ &pos->member != (head); \ pos = list_first_entry_by_type(&pos->member, __typeof__(*pos), member)) /** * Iterate through the list backwards. */ #define list_for_each_backwards(pos, head, member) \ for (pos = list_last_entry_by_type(head, __typeof__(*pos), member); \ &pos->member != (head); \ pos = list_last_entry_by_type(&pos->member, __typeof__(*pos), member)) /** * Iterate through the list. Equivalent to list_for_each() but allows * calling list_remove() on the element. * * @code * struct foo *f = get_a_foo(); * struct bar *element, *tmp; * list_for_each(element, tmp, &f->list_of_bars, link) { * list_remove(&element->link); * } * @endcode */ #define list_for_each_safe(pos, head, member) \ for (__typeof__(pos) _tmp = ({ \ pos = list_first_entry_by_type(head, __typeof__(*pos), member); \ list_first_entry_by_type(&pos->member, __typeof__(*_tmp), member); \ }); \ &pos->member != (head); \ pos = _tmp, \ _tmp = list_first_entry_by_type(&pos->member, __typeof__(*_tmp), member)) /** * Return the nth element of the list, starting at 0. This is the equivalent * to `listname[index]` if the list was an array. If the index is outside the * list size, NULL is returned. * * @code * struct foo *f = get_a_foo(); * struct bar *element = list_nth_entry(struct bar, &f->list_of_bars, link, 2); * @endcode */ #define list_nth_entry(type, head, member, index) \ ({ type *nth_ = NULL, *pos_; \ __typeof__(index) idx_ = 0; \ list_for_each(pos_, head, member) { \ if (idx_ == index) { nth_ = pos_; break; } \ idx_++; \ } \ nth_; \ }) libei-1.2.1/src/util-logger.c000066400000000000000000000065341456005336000157760ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include "util-logger.h" #include "util-object.h" struct logger { struct object object; enum logger_priority priority; logger_log_func_t handler; void *user_data; char *prefix; }; _printf_(7, 0) static void logger_default_log_func(struct logger *logger, const char *prefix, enum logger_priority priority, const char *file, int lineno, const char *func, const char *format, va_list args) { const char *msgtype; switch(priority) { case LOGGER_DEBUG: msgtype = "debug"; break; case LOGGER_INFO: msgtype = "info"; break; case LOGGER_WARN: msgtype = "warn"; break; case LOGGER_ERROR: msgtype = "error"; break; default: msgtype = ""; prefix=""; break; } fprintf(stderr, "%s: %s: ", prefix, msgtype); vfprintf(stderr, format, args); } void log_msg_va(struct logger *logger, enum logger_priority priority, const char *file, int lineno, const char *func, const char *format, va_list args) { if (logger->handler && logger->priority <= priority) logger->handler(logger, logger->prefix, priority, file, lineno, func, format, args); } void log_msg(struct logger *logger, enum logger_priority priority, const char *file, int lineno, const char *func, const char *format, ...) { va_list args; va_start(args, format); log_msg_va(logger, priority, file, lineno, func, format, args); va_end(args); } static void logger_destroy(struct logger* logger) { free(logger->prefix); } static OBJECT_IMPLEMENT_CREATE(logger); OBJECT_IMPLEMENT_UNREF_CLEANUP(logger); OBJECT_IMPLEMENT_SETTER(logger, priority, enum logger_priority); OBJECT_IMPLEMENT_GETTER(logger, priority, enum logger_priority); OBJECT_IMPLEMENT_SETTER(logger, user_data, void *); OBJECT_IMPLEMENT_GETTER(logger, user_data, void *); OBJECT_IMPLEMENT_SETTER(logger, handler, logger_log_func_t); struct logger * logger_new(const char *prefix, void *user_data) { struct logger *logger = logger_create(NULL); logger->prefix = prefix ? strdup(prefix) : NULL; logger->user_data = user_data; logger->priority = LOGGER_WARN; logger->handler = logger_default_log_func; return logger; } libei-1.2.1/src/util-logger.h000066400000000000000000000057151456005336000160030ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include "util-macros.h" struct logger; enum logger_priority { LOGGER_DEBUG = 20, LOGGER_INFO = 30, LOGGER_WARN = 40, LOGGER_ERROR = 50, }; typedef void (*logger_log_func_t)(struct logger *logger, const char *prefix, enum logger_priority priority, const char *file, int lineno, const char *func, const char *format, va_list args); void log_msg(struct logger *logger, enum logger_priority priority, const char *file, int lineno, const char *func, const char *format, ...); void log_msg_va(struct logger *logger, enum logger_priority priority, const char *file, int lineno, const char *func, const char *format, va_list args); /* log helpers. The struct T_ needs to have a field called 'logger' */ #define log_debug(T_, ...) \ log_msg((T_)->logger, LOGGER_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_info(T_, ...) \ log_msg((T_)->logger, LOGGER_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_warn(T_, ...) \ log_msg((T_)->logger, LOGGER_WARN, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_error(T_, ...) \ log_msg((T_)->logger, LOGGER_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__) #define log_bug(T_, ...) \ log_msg((T_)->logger, LOGGER_ERROR, __FILE__, __LINE__, __func__, "bug: " __VA_ARGS__) struct logger * logger_new(const char *prefix, void *user_data); struct logger * logger_unref(struct logger *logger); void * logger_get_user_data(struct logger *logger); void logger_set_user_data(struct logger *logger, void *user_data); enum logger_priority logger_get_priority(struct logger *logger); void logger_set_priority(struct logger *logger, enum logger_priority priority); void logger_set_handler(struct logger *logger, logger_log_func_t handler); libei-1.2.1/src/util-macros.h000066400000000000000000000045301456005336000160020ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2008-2011 Kristian Høgsberg * Copyright © 2011 Intel Corporation * Copyright © 2013-2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "config.h" #include #define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) #define ARRAY_FOR_EACH(_arr, _elem) \ for (size_t _i = 0; _i < ARRAY_LENGTH(_arr) && (_elem = &_arr[_i]); _i++) /** * Use to stringify a switch case value: * switch (foo) { * CASE_RETURN_STRING(ENUMVALUE); * CASE_RETURN_STRING(OTHERVALUE); * } */ #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)) #define ANSI_UP "\x1B[%dA" #define ANSI_DOWN "\x1B[%dB" #define ANSI_RIGHT "\x1B[%dC" #define ANSI_LEFT "\x1B[%dD" #define _public_ __attribute__((visibility("default"))) #define _printf_(_a, _b) __attribute__((format (printf, _a, _b))) #define _fallthrough_ __attribute__((fallthrough)) #define _unused_ __attribute__((unused)) #define _packed_ __attribute__((packed)) #define run_only_once \ static int _once_per_##__func__ = 0; \ for (; _once_per_##__func__ == 0; _once_per_##__func__ = 1) libei-1.2.1/src/util-mem.h000066400000000000000000000063631456005336000153020ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include /** * Use: _cleanup_(somefunction) struct foo *bar; */ #define _cleanup_(_x) __attribute__((cleanup(_x))) /** * Use: _cleanup_unref_(foo) struct foo *bar; * * This requires foo_unrefp() to be present, use DEFINE_UNREF_CLEANUP_FUNC. */ #define _unref_(_type) __attribute__((cleanup(_type##_unrefp))) struct _type static inline void _free_ptr_(void *p) { free(*(void**)p); } /** * Use: _cleanup_free_ char *data; */ #define _cleanup_free_ _cleanup_(_free_ptr_) /** * Use: * DEFINE_TRIVIAL_CLEANUP_FUNC(struct foo *, foo_unref) * _cleanup_(foo_unrefp) struct foo *bar; */ #define DEFINE_TRIVIAL_CLEANUP_FUNC(_type, _func) \ static inline void _func##p(_type *_p) { \ if (*_p) \ _func(*_p); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ /** * Define a cleanup function for the struct type foo with a matching * foo_unref(). Use: * DEFINE_UNREF_CLEANUP_FUNC(foo) * _unref_(foo) struct foo *bar; */ #define DEFINE_UNREF_CLEANUP_FUNC(_type) \ static inline void _type##_unrefp(struct _type **_p) { \ if (*_p) \ _type##_unref(*_p); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ static inline void* _steal(void *ptr) { void **original = (void**)ptr; void *swapped = *original; *original = NULL; return swapped; } /** * Resets the pointer content and resets the data to NULL. * This circumvents _cleanup_ handling for that pointer. * Use: * _cleanup_free_ char *data = malloc(); * return steal(&data); * */ #define steal(ptr_) \ (typeof(*ptr_))_steal(ptr_) /** * Never-failing calloc with a size limit check. */ static inline void * xalloc(size_t size) { void *p; /* 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"); p = calloc(1, size); if (!p) abort(); return p; } static inline void * xrealloc(void *ptr, int size) { void *tmp = realloc(ptr, size); assert(tmp); return tmp; } libei-1.2.1/src/util-memfile.c000066400000000000000000000045601456005336000161320ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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" #if HAVE_MEMFD_CREATE #include #include #include #include #include "util-memfile.h" #include "util-mem.h" #include "util-io.h" #include "util-object.h" struct memfile { struct object object; int fd; size_t size; }; static void memfile_destroy(struct memfile *memfile) { if (memfile->fd != -1) close(memfile->fd); } static OBJECT_IMPLEMENT_CREATE(memfile); OBJECT_IMPLEMENT_UNREF_CLEANUP(memfile); OBJECT_IMPLEMENT_REF(memfile); OBJECT_IMPLEMENT_GETTER(memfile, fd, int); OBJECT_IMPLEMENT_GETTER(memfile, size, size_t); struct memfile * memfile_new(const char *data, size_t sz) { _unref_(memfile) *memfile = memfile_create(NULL); _cleanup_close_ int fd = memfd_create("memfile", MFD_CLOEXEC|MFD_ALLOW_SEALING); if (fd < 0) return NULL; fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK); int rc; with_signals_blocked(SIGALRM) { rc = SYSCALL(posix_fallocate(fd, 0, sz)); } if (rc < 0) return NULL; memfile->fd = fd; memfile->size = sz; fd = -1; void *map = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE, memfile->fd, 0); if (map == MAP_FAILED) return NULL; memcpy(map, data, sz); munmap(map, sz); return steal(&memfile); } #endif libei-1.2.1/src/util-memfile.h000066400000000000000000000030031456005336000161260ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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" struct memfile; #if HAVE_MEMFD_CREATE struct memfile * memfile_new(const char *data, size_t sz); #endif struct memfile * memfile_ref(struct memfile *memfile); struct memfile * memfile_unref(struct memfile *memfile); int memfile_get_fd(struct memfile *memfile); size_t memfile_get_size(struct memfile *memfile); libei-1.2.1/src/util-munit.c000066400000000000000000000051751456005336000156530ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include "util-munit.h" #include "util-mem.h" #include "util-strings.h" /* All MUNIT_TEST functions are in the test_functions_section ELF section. * __start and __stop point to the start and end of that section. See the * __attribute__(section) documentation. */ 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 */ foreach_test(t) { count++; } _cleanup_free_ MunitTest *tests = calloc(count, sizeof(*tests)); size_t idx = 0; foreach_test(t) { MunitTest test = { .name = xaprintf("%s", t->name), .test = t->func, }; 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, NULL, 1, MUNIT_SUITE_OPTION_NONE, }; /* Disable coredumps */ const struct rlimit corelimit = { 0, 0 }; setrlimit(RLIMIT_CORE, &corelimit); int rc = munit_suite_main(&suite, setup.userdata, setup.argc, setup.argv); for (idx = 0; idx < count; idx++) free(tests[idx].name); return rc; } libei-1.2.1/src/util-munit.h000066400000000000000000000104711456005336000156530ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* This is a wrapper around munit to make it faster to use for the simple * type of test cases. * * Use with the MUNIT_TEST macro like this: * * MUNIT_TEST(some_test) { * return MUNIT_OK; * } * * int main(int argc, char **argv) { * return munit_tests_run(argc, argv); * } * */ #pragma once #include /** * Put at the top of the file somewhere, declares the start/stop for the test section we need. */ #define DECLARE_TEST_SECTION() \ extern const struct test_function __start_test_functions_section, __stop_test_functions_section /** * Helper to loop through each test. */ #define foreach_test(t_) \ for (const struct test_function *t_ = &__start_test_functions_section; \ t_ < &__stop_test_functions_section; \ t_++) typedef MunitResult (*munit_test_func_t)(const MunitParameter params[], void *user_data); struct test_function { const char *name; /* function name */ const char *file; /* file name */ munit_test_func_t func; /* test function */ } __attribute__((aligned(16))); /** * Put at the top of the file somewhere, declares the start/stop for the setup section we need. */ #define DECLARE_GLOBAL_SETUP_SECTION() \ extern const struct global_setup_function __start_setup_functions_section, __stop_setup_functions_section /** * Helper to loop through each setup function. */ #define foreach_setup(s_) \ for (const struct global_setup_function *s_ = &__start_setup_functions_section; \ s_ < &__stop_setup_functions_section; \ s_++) struct munit_setup { int argc; char **argv; void *userdata; }; typedef void (*munit_setup_func_t)(struct munit_setup *setup); struct global_setup_function { const char *name; /* function name */ munit_setup_func_t func; /* setup function */ } __attribute__((aligned(16))); /** * Defines a struct test_function in a custom ELF section that we can then * loop over in munit_tests_run() to extract the tests. This removes the * need of manually adding the tests to a suite or listing them somewhere. */ #define MUNIT_TEST(_func) \ static MunitResult _func(const MunitParameter params[], void *user_data); \ static const struct test_function _test_##_func \ __attribute__((used)) \ __attribute__((section("test_functions_section"))) = { \ .name = #_func, \ .func = _func, \ .file = __FILE__, \ }; \ static MunitResult _func(const MunitParameter params[], void *user_data) /** * Defines a struct global_setup_function in a custom ELF section that we can * then find in munit_tests_run() to do setup based on argv and/or pass userdata * to the tests. * * Note that there can only be one of those per process. * * The argument to this function is a pointer to a struct munit_setup that has * argc/argv and a (initially NULL) userdata pointer. */ #define MUNIT_GLOBAL_SETUP(_func) \ static void _func(struct munit_setup *setup); \ static const struct global_setup_function _setup_##_func \ __attribute__((used)) \ __attribute__((section("setup_functions_section"))) = { \ .name = #_func, \ .func = _func, \ }; \ static void _func(struct munit_setup *setup) \ int munit_tests_run(int argc, char **argv); libei-1.2.1/src/util-object.h000066400000000000000000000152651456005336000157730ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /** * This is an abstraction layer for ref-counted objects with an optional * parent. It cuts down on boilerplate by providing a bunch of macros * that generate the various required functions. * Limitation: the object must be the first item in the parent struct. */ #pragma once #include "config.h" #include #include #include struct object; /** * Function called when the last reference is unref'd. Clean up **internal** * state of the object, the object itself is freed by the generated * functions. */ typedef void (*object_destroy_func_t)(struct object *object); /* internal implementation, do not use directly */ struct object { struct object *parent; /* may be NULL */ uint32_t refcount; object_destroy_func_t destroy; }; /* internal implementation, do not call directly */ static inline void object_init(struct object *object, struct object *parent, object_destroy_func_t destroy) { object->refcount = 1; object->destroy = destroy; object->parent = parent; } /* internal implementation, do not call directly */ static inline void object_destroy(struct object *object) { if (object->destroy) object->destroy(object); free(object); } /* internal implementation, do not call directly */ static inline void * object_ref(struct object *object) { assert(object->refcount >= 1); ++object->refcount; return object; } /* internal implementation, do not call directly */ static inline void * object_unref(struct object *object) { assert(object->refcount >= 1); if (--object->refcount == 0) object_destroy(object); return NULL; } /** * For a type of "foo", declare * struct foo *foo_ref(struct foo *f); */ #define OBJECT_DECLARE_REF(type_) \ struct type_ * type_##_ref(struct type_ *obj) /** * For a type of "foo", declare * struct foo *foo_unref(struct foo *f); */ #define OBJECT_DECLARE_UNREF(type_) \ struct type_ * type_##_unref(struct type_ *obj) /** * For a type of "foo", generate * struct foo *foo_ref(struct foo *f); */ #define OBJECT_IMPLEMENT_REF(type_) \ struct type_ * type_##_ref(struct type_ *obj) { \ object_ref(&obj->object); \ return obj; \ } /** * For a type of "foo", generate * struct foo *foo_unref(struct foo *f); * The function always returns NULL, when the last reference is removed the * object is freed. */ #define OBJECT_IMPLEMENT_UNREF(type_) \ struct type_ * type_##_unref(struct type_ *obj) { \ if (!obj) return NULL; \ return object_unref(&obj->object); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ /** * Same as OBJECT_IMPLEMENT_UNREF() but also * also defines a unrefp function, use with * __attribute__((cleanup(foo_unrefp)) */ #define OBJECT_IMPLEMENT_UNREF_CLEANUP(type_) \ OBJECT_IMPLEMENT_UNREF(type_); \ __attribute__((used)) static inline void type_##_unrefp(struct type_ **p_) { \ if (*p_) type_##_unref(*p_); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ /** * For a type for "foo", generate * void foo_init_object(struct foo *f) * which sets up the *object* part of the foo struct. Use this where * allocation through foo_create isn't suitable. */ #define OBJECT_IMPLEMENT_INIT(type_) \ void type_##_init_object(struct type_ *t, struct object *parent) { \ assert((intptr_t)t == (intptr_t)&t->object && "field 'object' must be first one in the struct"); \ object_init(&t->object, parent, (object_destroy_func_t)type_##_destroy); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ /** * For a type for "foo", generate * struct foo *foo_create(struct object *parent) * which returns an callocated and initialized foo struct. */ #define OBJECT_IMPLEMENT_CREATE(type_) \ struct type_ * type_##_create(struct object *parent) { \ struct type_ *t = calloc(1, sizeof *t); \ assert((intptr_t)t == (intptr_t)&t->object && "field 'object' must be first one in the struct"); \ assert(t != NULL); \ object_init(&t->object, parent, (object_destroy_func_t)type_##_destroy); \ return t; \ } \ struct __useless_struct_to_allow_trailing_semicolon__ /** * For a type "foo" with parent type "bar", generate * struct bar* foo_parent(struct foo *foo) */ #define OBJECT_IMPLEMENT_PARENT(type_, parent_type_) \ struct parent_type_ * type_##_parent(struct type_ *o) { \ return container_of(o->object.parent, struct parent_type_, object); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ /** * Declares a simple `bartype foo_get_bar(foo)` function. */ #define OBJECT_DECLARE_GETTER(type_, field_, rtype_) \ rtype_ type_##_get_##field_(struct type_ *obj) /** * Declares a simple `void foo_set_bar(foo, bar)` function. */ #define OBJECT_DECLARE_SETTER(type_, field_, rtype_) \ void type_##_set_##field_(struct type_ *obj, rtype_ val_) /** * Generate a simple getter function for the given type, field and return * type. */ #define OBJECT_IMPLEMENT_GETTER(type_, field_, rtype_) \ rtype_ type_##_get_##field_(struct type_ *obj) { \ return obj->field_; \ } \ struct __useless_struct_to_allow_trailing_semicolon__ /** * Generate a simple getter function for the given type, field and return * type as a pointer to the field. */ #define OBJECT_IMPLEMENT_GETTER_AS_REF(type_, field_, rtype_) \ rtype_ type_##_get_##field_(struct type_ *obj) { \ return &obj->field_; \ } \ struct __useless_struct_to_allow_trailing_semicolon__ /** * Generate a simple setter function for the given type, field and return * type. */ #define OBJECT_IMPLEMENT_SETTER(type_, field_, rtype_) \ void type_##_set_##field_(struct type_ *obj, rtype_ val_) { \ obj->field_ = (val_); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ libei-1.2.1/src/util-sources.c000066400000000000000000000221341456005336000161740ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include "util-object.h" #include "util-io.h" #include "util-list.h" #include "util-sources.h" struct sink { struct object object; int epollfd; struct list sources; struct list sources_removed; }; enum source_close_behavior { SOURCE_CLOSE_FD_ON_REMOVE = 1, /* default */ SOURCE_CLOSE_FD_ON_DESTROY, SOURCE_CLOSE_FD_NEVER, }; struct source { struct object object; struct sink *sink; struct list link; /* sink.sources or sink.sources_removed */ source_dispatch_t dispatch; void *user_data; enum source_close_behavior close_behavior; int fd; bool is_active; }; OBJECT_IMPLEMENT_REF(source); OBJECT_IMPLEMENT_UNREF_CLEANUP(source); OBJECT_IMPLEMENT_GETTER(source, fd, int); OBJECT_IMPLEMENT_GETTER(source, user_data, void*); OBJECT_IMPLEMENT_SETTER(source, user_data, void*); /** * Remove the source, closing the fd. The source is tagged as removed and * will be removed whenever sink_dispatch() finishes (or is called next). */ void source_remove(struct source *source) { if (!source || !source->is_active) return; epoll_ctl(source->sink->epollfd, EPOLL_CTL_DEL, source->fd, NULL); if (source->close_behavior == SOURCE_CLOSE_FD_ON_REMOVE) source->fd = xclose(source->fd); source->is_active = false; source_unref(source); /* Note: sources list was the owner of the source, new owner is the removed list */ list_remove(&source->link); list_append(&source->sink->sources_removed, &source->link); source->sink = NULL; } /* Ignore, use source_unref() */ static void source_destroy(struct source *source) { /* We expect source_remove() to be called before we ever get here */ assert(!source->is_active); if (source->close_behavior == SOURCE_CLOSE_FD_ON_DESTROY) source->fd = xclose(source->fd); } static OBJECT_IMPLEMENT_CREATE(source); struct source * source_new(int sourcefd, source_dispatch_t dispatch, void *user_data) { struct source *source = source_create(NULL); source->dispatch = dispatch; source->user_data = user_data; source->fd = sourcefd; source->close_behavior = SOURCE_CLOSE_FD_ON_REMOVE; source->is_active = false; list_init(&source->link); return source; } void source_never_close_fd(struct source *s) { s->close_behavior = SOURCE_CLOSE_FD_NEVER; } static void sink_destroy(struct sink *sink) { struct source *s; list_for_each_safe(s, &sink->sources, link) { source_remove(s); } list_for_each_safe(s, &sink->sources_removed, link) { source_unref(s); } xclose(sink->epollfd); } OBJECT_IMPLEMENT_UNREF_CLEANUP(sink); static OBJECT_IMPLEMENT_CREATE(sink); int sink_get_fd(struct sink *sink) { assert(sink); return sink->epollfd; } struct sink * sink_new(void) { int fd = epoll_create1(EPOLL_CLOEXEC); if (fd < 0) return NULL; struct sink *sink = sink_create(NULL); sink->epollfd = fd; list_init(&sink->sources); list_init(&sink->sources_removed); return sink; } int sink_dispatch(struct sink *sink) { struct epoll_event ep[32]; int count = epoll_wait(sink->epollfd, ep, sizeof(ep)/sizeof(ep[0]), 0); if (count < 0) return -errno; for (int i = 0; i < count; ++i) { struct source *source = ep[i].data.ptr; if (source->fd == -1) continue; source->dispatch(source, source->user_data); } struct source *s; list_for_each_safe(s, &sink->sources_removed, link) { list_remove(&s->link); list_init(&s->link); source_unref(s); } return 0; } int sink_add_source(struct sink *sink, struct source *source) { struct epoll_event e = { .events = EPOLLIN, .data.ptr = source_ref(source), }; int rc = xerrno(epoll_ctl(sink->epollfd, EPOLL_CTL_ADD, source_get_fd(source), &e)); if (rc < 0) { source_unref(source); return rc; } source->is_active = true; source->sink = sink; source_ref(source); list_append(&sink->sources, &source->link); return 0; } int source_enable_write(struct source *source, bool enable) { assert (source->is_active); struct epoll_event e = { .events = EPOLLIN | (enable ? EPOLLOUT : 0), .data.ptr = source, /* sink_add_source ref'd, so we don't need to here */ }; int rc = xerrno(epoll_ctl(source->sink->epollfd, EPOLL_CTL_MOD, source_get_fd(source), &e)); if (rc < 0) { source_unref(source); return rc; } return 0; } #if _enable_tests_ #include #include #include "util-munit.h" #include "util-macros.h" MUNIT_TEST(test_sink) { struct sink *sink = sink_new(); sink_dispatch(sink); sink_dispatch(sink); int fd = sink_get_fd(sink); munit_assert_int(fd, !=, -1); sink_unref(sink); return MUNIT_OK; } struct buffer { size_t size; size_t len; char *buffer; }; static void read_buffer(struct source *source, void *user_data) { struct buffer *buffer = user_data; size_t sz = max(buffer->size, 1024); buffer->size = sz; buffer->buffer = xrealloc(buffer->buffer, sz); int nread = read(source_get_fd(source), buffer->buffer, sz); munit_assert_int(nread, >=, 0); buffer->len = nread; } MUNIT_TEST(test_source) { _unref_(sink) *sink = sink_new(); int fd[2]; int rc = pipe2(fd, O_CLOEXEC|O_NONBLOCK); munit_assert_int(rc, !=, -1); struct buffer buffer = {0}; struct source *s = source_new(fd[0], read_buffer, &buffer); munit_assert_int(source_get_fd(s), ==, fd[0]); sink_add_source(sink, s); /* Nothing to read yet, dispatch is a noop */ sink_dispatch(sink); munit_assert_int(buffer.len, ==, 0); const char token[] = "foobar"; int wrc = write(fd[1], token, sizeof(token)); munit_assert_int(wrc, ==, sizeof(token)); /* haven't called dispatch yet */ munit_assert_int(buffer.len, ==, 0); sink_dispatch(sink); munit_assert_int(buffer.len, ==, sizeof(token)); munit_assert_string_equal(buffer.buffer, token); /* multiple removals shouldn't matter */ source_remove(s); source_remove(s); sink_dispatch(sink); source_remove(s); sink_dispatch(sink); /* source pipe is already closed */ signal(SIGPIPE, SIG_IGN); const char token2[] = "bazbat"; wrc = write(fd[1], token2, sizeof(token2)); munit_assert_int(wrc, ==, -1); munit_assert_int(errno, ==, EPIPE); sink_dispatch(sink); source_unref(s); sink_dispatch(sink); free(buffer.buffer); return MUNIT_OK; } static void drain_data(struct source *source, void *user_data) { char buf[1024] = {0}; read(source_get_fd(source), buf, sizeof(buf)); } MUNIT_TEST(test_source_readd) { _unref_(sink) *sink = sink_new(); int fd[2]; int rc = pipe2(fd, O_CLOEXEC|O_NONBLOCK); munit_assert_int(rc, !=, -1); _unref_(source) *s = source_new(fd[0], drain_data, NULL); sink_add_source(sink, s); sink_dispatch(sink); /* remove and re-add without calling dispatch */ source_remove(s); sink_add_source(sink, s); source_remove(s); return MUNIT_OK; } static void count_calls(struct source *source, void *user_data) { unsigned int *arg = user_data; *arg = *arg + 1; } MUNIT_TEST(test_source_write) { _unref_(sink) *sink = sink_new(); int fd[2]; int rc = pipe2(fd, O_CLOEXEC|O_NONBLOCK); munit_assert_int(rc, !=, -1); int read_fd = fd[0]; int write_fd = fd[1]; int dispatch_called = 0; _unref_(source) *s = source_new(write_fd, count_calls, &dispatch_called); sink_add_source(sink, s); sink_dispatch(sink); sink_dispatch(sink); sink_dispatch(sink); munit_assert_uint(dispatch_called, ==, 0); source_enable_write(s, true); sink_dispatch(sink); munit_assert_uint(dispatch_called, ==, 1); sink_dispatch(sink); munit_assert_uint(dispatch_called, ==, 2); /* Fill up the buffer */ do { char buf[4096] = {0}; rc = write(write_fd, buf, sizeof(buf)); } while (rc != -1); munit_assert_int(errno, ==, EAGAIN); /* Buffer is full, expect our dispatch to NOT be called */ sink_dispatch(sink); munit_assert_uint(dispatch_called, ==, 2); sink_dispatch(sink); munit_assert_uint(dispatch_called, ==, 2); do { char buf[406]; rc = read(read_fd, buf, sizeof(buf)); } while (rc != -1); munit_assert_int(errno, ==, EAGAIN); sink_dispatch(sink); munit_assert_uint(dispatch_called, ==, 3); source_enable_write(s, false); sink_dispatch(sink); munit_assert_uint(dispatch_called, ==, 3); return MUNIT_OK; } #endif libei-1.2.1/src/util-sources.h000066400000000000000000000070121456005336000161770ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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. */ /** * Source/sink objects. */ #pragma once #include "config.h" #include struct source; struct sink; /** * Callback invoked when the source has data available. userdata is the data * provided to source_add(). * * If source_enable_write() was called, this dispatch function is also called * when writes are possible (and/or data is available to read at the same time). */ typedef void (*source_dispatch_t)(struct source *source, void *user_data); /** * Remove source from its sink without destroying it, a source may be * re-added to a sink later. */ void source_remove(struct source *source); struct source * source_ref(struct source *source); /** * Unref source. When the last reference is dropped, resources * are released. * * Note that due to implementation details, it is not possible to get the * refcount to zero by calling source_unref() in the caller, you *must* * remove a source with source_remove() to be able to release it fully. */ struct source * source_unref(struct source *source); int source_get_fd(struct source *source); void * source_get_user_data(struct source *source); void source_set_user_data(struct source *source, void *user_data); /** * Create a new source for the given file descriptor with the given dispatch * callback. The source's default behavior is that the fd is closed on the * call to source_remove(). * * This source does not generate events until added to a sink with * sink_add_source(). * * The returned source has a refcount of 1, use source_unref() to release th * memory. */ struct source * source_new(int fd, source_dispatch_t dispatch, void *user_data); void source_never_close_fd(struct source *s); /** * Enable or disable write notifications on this source. By default we assume * our sources only read from the fd and thus their dispatch is only called * when there's data available to read. * * If write is enabled, the dispatch is also called with data available to write. */ int source_enable_write(struct source *source, bool enable); struct sink * sink_new(void); struct sink * sink_unref(struct sink *sink); int sink_dispatch(struct sink *sink); /** * Add the source to the given sink. Use source_remove() to remove the * source. */ int sink_add_source(struct sink *sink, struct source *source); /** * The epollfd to monitor for this sink. */ int sink_get_fd(struct sink *sink); libei-1.2.1/src/util-strings.c000066400000000000000000000356641456005336000162160ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2008 Kristian Høgsberg * Copyright © 2013-2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include "util-strings.h" /** * Return the next word in a string pointed to by state before the first * separator character. Call repeatedly to tokenize a whole string. * * @param state Current state * @param len String length of the word returned * @param separators List of separator characters * * @return The first word in *state, NOT null-terminated */ static const char * next_word(const char **state, size_t *len, const char *separators) { const char *next = *state; size_t l; if (!*next) return NULL; next += strspn(next, separators); if (!*next) { *state = next; return NULL; } l = strcspn(next, separators); *state = next + l; *len = l; return next; } /** * Return a null-terminated string array with the tokens in the input * string, e.g. "one two\tthree" with a separator list of " \t" will return * an array [ "one", "two", "three", NULL ]. * * Use strv_free() to free the array. * * @param in Input string * @param separators List of separator characters * * @return A null-terminated string array or NULL on errors */ char ** strv_from_string(const char *in, const char *separators) { const char *s, *word; char **strv = NULL; int nelems = 0, idx; size_t l; assert(in != NULL); s = in; while (next_word(&s, &l, separators) != NULL) nelems++; if (nelems == 0) return NULL; nelems++; /* NULL-terminated */ strv = xalloc(nelems * sizeof *strv); idx = 0; s = in; while ((word = next_word(&s, &l, separators)) != NULL) { char *copy = strndup(word, l); if (!copy) { strv_free(strv); return NULL; } strv[idx++] = copy; } return strv; } /** * Return a newly allocated string with all elements joined by the * joiner, same as Python's string.join() basically. * A strv of ["one", "two", "three", NULL] with a joiner of ", " results * in "one, two, three". * * An empty strv ([NULL]) returns NULL, same for passing NULL as either * argument. * * @param strv Input string arrray * @param joiner Joiner between the elements in the final string * * @return A null-terminated string joining all elements */ char * strv_join(char **strv, const char *joiner) { char **s; char *str; size_t slen = 0; size_t count = 0; if (!strv || !joiner) return NULL; if (strv[0] == NULL) return NULL; for (s = strv, count = 0; *s; s++, count++) { slen += strlen(*s); } assert(slen < 1000); assert(strlen(joiner) < 1000); assert(count > 0); assert(count < 100); slen += (count - 1) * strlen(joiner); str = xalloc(slen + 1); /* trailing \0 */ for (s = strv; *s; s++) { strcat(str, *s); --count; if (count > 0) strcat(str, joiner); } return str; } char * strreplace(const char *string, const char *separator, const char *replacement) { assert(string != NULL); assert(string[0] != '\0'); /* Enough to replace every character in the string with the * replacement. This will blow up on extremely long strings with long * replacements, but meh. It saves us having to write resizing code. */ size_t slen = strlen(string); size_t splen = strlen(separator); const char *current = string; const char *next; next = strstr(current, separator); if (!next) /* No separator found */ return xstrdup(string); size_t rlen = strlen(replacement); size_t max = slen * max(rlen, 1); char *r = calloc(max + 1, 1); /* the result, one extra for terminating \0 */ char *destptr = r; /* our strncpy calls truncate the second argument, we know... */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-truncation" while (next) { size_t len = next - current; /* silently truncate because we really don't care about this * case */ if (destptr + len > r + max) break; /* Copy the source string over, then append the separator */ strncpy(destptr, current, len); destptr += len; if (destptr + rlen > r + max) break; strncpy(destptr, replacement, rlen); destptr += rlen; current = next + splen; if (current > string + max) break; next = strstr(current, separator); } size_t len = strlen(current); /* silently truncate because we really don't care about this * case */ if (destptr + len <= r + max) { strncpy(destptr, current, len); destptr += len; } #pragma GCC diagnostic pop void *tmp = realloc(r, (destptr - r) + 1); assert(tmp); return tmp; } char ** strv_from_mem(const uint8_t *buffer, size_t sz, size_t stride) { assert(stride > 0); assert(stride <= 16); char **strv = xalloc(((sz / stride) + 2) * sizeof *strv); #define hex(buffer_, sz, line_, offset_) \ (line_ + offset_ < sz) ? buffer_[line_ + offset_] : 0u for (size_t i = 0; i < sz; i += stride) { char *str = xaprintf( "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", hex(buffer, sz, i, 0), hex(buffer, sz, i, 1), hex(buffer, sz, i, 2), hex(buffer, sz, i, 3), hex(buffer, sz, i, 4), hex(buffer, sz, i, 5), hex(buffer, sz, i, 6), hex(buffer, sz, i, 7), hex(buffer, sz, i, 8), hex(buffer, sz, i, 9), hex(buffer, sz, i, 10), hex(buffer, sz, i, 11), hex(buffer, sz, i, 12), hex(buffer, sz, i, 13), hex(buffer, sz, i, 14), hex(buffer, sz, i, 15)); /* Chop the string off at the last value or our stride, whichever applies */ if (i + stride >= sz) str[(sz - i) * 3 - 1] = '\0'; else if (stride < 16) str[stride * 3 - 1] = '\0'; strv[i / stride] = str; } return strv; } #if _enable_tests_ #include "util-munit.h" MUNIT_TEST(test_strsplit) { struct strsplit_test { const char *string; const char *delim; const char *results[10]; } tests[] = { { "one two three", " ", { "one", "two", "three", NULL } }, { "one", " ", { "one", NULL } }, { "one two ", " ", { "one", "two", NULL } }, { "one two", " ", { "one", "two", NULL } }, { " one two", " ", { "one", "two", NULL } }, { "one", "\t \r", { "one", NULL } }, { "one two three", " t", { "one", "wo", "hree", NULL } }, { " one two three", "te", { " on", " ", "wo ", "hr", NULL } }, { "one", "ne", { "o", NULL } }, { "onene", "ne", { "o", NULL } }, { NULL, NULL, { NULL }} }; struct strsplit_test *t = tests; while (t->string) { char **strv; int idx = 0; strv = strv_from_string(t->string, t->delim); while (t->results[idx]) { munit_assert_string_equal(t->results[idx], strv[idx]); idx++; } munit_assert_ptr_equal(strv[idx], NULL); strv_free(strv); t++; } /* Special cases */ munit_assert_ptr_equal(strv_from_string("", " "), NULL); munit_assert_ptr_equal(strv_from_string(" ", " "), NULL); munit_assert_ptr_equal(strv_from_string(" ", " "), NULL); munit_assert_ptr_equal(strv_from_string("oneoneone", "one"), NULL); return MUNIT_OK; } MUNIT_TEST(test_kvsplit_double) { struct kvsplit_dbl_test { const char *string; const char *psep; const char *kvsep; ssize_t nresults; struct { double a; double b; } results[32]; } tests[] = { { "1:2;3:4;5:6", ";", ":", 3, { {1, 2}, {3, 4}, {5, 6}}}, { "1.0x2.3 -3.2x4.5 8.090909x-6.00", " ", "x", 3, { {1.0, 2.3}, {-3.2, 4.5}, {8.090909, -6}}}, { "1:2", "x", ":", 1, {{1, 2}}}, { "1:2", ":", "x", -1, {}}, { "1:2", NULL, "x", -1, {}}, { "1:2", "", "x", -1, {}}, { "1:2", "x", NULL, -1, {}}, { "1:2", "x", "", -1, {}}, { "a:b", "x", ":", -1, {}}, { "", " ", "x", -1, {}}, { "1.2.3.4.5", ".", "", -1, {}}, { NULL } }; struct kvsplit_dbl_test *t = tests; while (t->string) { struct key_value_double *result = NULL; ssize_t npairs; npairs = kv_double_from_string(t->string, t->psep, t->kvsep, &result); munit_assert_int(npairs, ==, t->nresults); for (ssize_t i = 0; i < npairs; i++) { munit_assert_double(t->results[i].a, ==, result[i].key); munit_assert_double(t->results[i].b, ==, result[i].value); } free(result); t++; } return MUNIT_OK; } MUNIT_TEST(test_strjoin) { struct strjoin_test { char *strv[10]; const char *joiner; const char *result; } tests[] = { { { "one", "two", "three", NULL }, " ", "one two three" }, { { "one", NULL }, "x", "one" }, { { "one", "two", NULL }, "x", "onextwo" }, { { "one", "two", NULL }, ",", "one,two" }, { { "one", "two", NULL }, ", ", "one, two" }, { { "one", "two", NULL }, "one", "oneonetwo" }, { { "one", "two", NULL }, NULL, NULL }, { { "", "", "", NULL }, " ", " " }, { { "a", "b", "c", NULL }, "", "abc" }, { { "", "b", "c", NULL }, "x", "xbxc" }, { { "", "", "", NULL }, "", "" }, { { NULL }, NULL, NULL } }; struct strjoin_test *t = tests; struct strjoin_test nulltest = { {NULL}, "x", NULL }; while (t->strv[0]) { char *str; str = strv_join(t->strv, t->joiner); if (t->result == NULL) munit_assert(str == NULL); else munit_assert_string_equal(str, t->result); free(str); t++; } munit_assert(strv_join(nulltest.strv, "x") == NULL); return MUNIT_OK; } MUNIT_TEST(test_strstrip) { struct strstrip_test { const char *string; const char *expected; const char *what; } tests[] = { { "foo", "foo", "1234" }, { "\"bar\"", "bar", "\"" }, { "'bar'", "bar", "'" }, { "\"bar\"", "\"bar\"", "'" }, { "'bar'", "'bar'", "\"" }, { "\"bar\"", "bar", "\"" }, { "\"\"", "", "\"" }, { "\"foo\"bar\"", "foo\"bar", "\"" }, { "\"'foo\"bar\"", "foo\"bar", "\"'" }, { "abcfooabcbarbca", "fooabcbar", "abc" }, { "xxxxfoo", "foo", "x" }, { "fooyyyy", "foo", "y" }, { "xxxxfooyyyy", "foo", "xy" }, { "x xfooy y", " xfooy ", "xy" }, { " foo\n", "foo", " \n" }, { "", "", "abc" }, { "", "", "" }, { NULL , NULL, NULL } }; struct strstrip_test *t = tests; while (t->string) { char *str; str = strstrip(t->string, t->what); munit_assert_string_equal(str, t->expected); free(str); t++; } return MUNIT_OK; } MUNIT_TEST(test_strstartswith) { struct strstartswith_test { const char *string; const char *suffix; bool expected; } tests[] = { { "foobar", "foo", true }, { "foobar", "bar", false }, { "foobar", "foobar", true }, { "foo", "foobar", false }, { "foo", "", false }, { "", "", false }, { "foo", "", false }, { NULL, NULL, false }, }; for (struct strstartswith_test *t = tests; t->string; t++) { munit_assert_int(strstartswith(t->string, t->suffix), ==, t->expected); } return MUNIT_OK; } MUNIT_TEST(test_strendswith) { struct strendswith_test { const char *string; const char *suffix; bool expected; } tests[] = { { "foobar", "bar", true }, { "foobar", "foo", false }, { "foobar", "foobar", true }, { "foo", "foobar", false }, { "foobar", "", false }, { "", "", false }, { "", "foo", false }, { NULL, NULL, false }, }; for (struct strendswith_test *t = tests; t->string; t++) { munit_assert_int(strendswith(t->string, t->suffix), ==, t->expected); } return MUNIT_OK; } MUNIT_TEST(test_strreplace) { struct strtest { const char *string; const char *separator; const char *replacement; const char *expected; } tests[] = { { "teststring", "-", ".", "teststring" }, { "test-string", "-", ".", "test.string" }, { "test.string.", ".", "xyz", "testxyzstringxyz" }, { "ftestfstringf", "f", "", "teststring" }, { "xxx", "x", "y", "yyy" }, { "xyz", "x", "y", "yyz" }, { "xyz", "xy", "y", "yz" }, { .string = NULL }, }; for (struct strtest *t = tests; t->string; t++) { _cleanup_free_ char *s = strreplace(t->string, t->separator, t->replacement); munit_assert_string_equal(t->expected, s); } return MUNIT_OK; } MUNIT_TEST(test_cmdline_as_str) { _cleanup_free_ char *from_function = cmdline_as_str(); char cmdline[PATH_MAX]; xsnprintf(cmdline, sizeof(cmdline), "/proc/%i/cmdline", getpid()); int fd = open(cmdline, O_RDONLY); munit_assert_int(fd, >=, 0); int len = read(fd, cmdline, sizeof(cmdline) - 1); munit_assert_int(len, >=, 0); cmdline[len] = '\0'; munit_assert_string_equal(cmdline, from_function); return MUNIT_OK; } MUNIT_TEST(test_strlen0) { munit_assert_int(strlen0(NULL), ==, 0); munit_assert_int(strlen0(""), ==, 1); munit_assert_int(strlen0("foo"), ==, 4); return MUNIT_OK; } MUNIT_TEST(test_strv_from_mem) { uint8_t buf[36]; for (size_t i = 0; i < sizeof(buf); i++) buf[i] = i; { _cleanup_(strv_freep) char **strv = strv_from_mem(buf, 16, 16); munit_assert(strv != NULL); munit_assert(strv[0] != NULL); munit_assert_null(strv[1]); /* we expect one line */ munit_assert_string_equal(strv[0], "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"); } { _cleanup_(strv_freep) char **strv = strv_from_mem(buf, 8, 16); munit_assert(strv != NULL); munit_assert(strv[0] != NULL); munit_assert_null(strv[1]); /* we expect one line */ munit_assert_string_equal(strv[0], "00 01 02 03 04 05 06 07"); } { _cleanup_(strv_freep) char **strv = strv_from_mem(buf, 8, 4); munit_assert(strv != NULL); munit_assert(strv[0] != NULL); munit_assert(strv[1] != NULL); munit_assert_null(strv[2]); /* we expect two lines */ munit_assert_string_equal(strv[0], "00 01 02 03"); munit_assert_string_equal(strv[1], "04 05 06 07"); } { _cleanup_(strv_freep) char **strv = strv_from_mem(buf, sizeof(buf), 5); munit_assert(strv != NULL); munit_assert_string_equal(strv[0], "00 01 02 03 04"); munit_assert_string_equal(strv[1], "05 06 07 08 09"); munit_assert_string_equal(strv[2], "0a 0b 0c 0d 0e"); munit_assert_string_equal(strv[3], "0f 10 11 12 13"); munit_assert_string_equal(strv[4], "14 15 16 17 18"); munit_assert_string_equal(strv[5], "19 1a 1b 1c 1d"); munit_assert_string_equal(strv[6], "1e 1f 20 21 22"); munit_assert_string_equal(strv[7], "23"); munit_assert_null(strv[8]); } { uint8_t buffer[14]; memset(buffer, -1, sizeof(buffer)); _cleanup_(strv_freep) char **strv = strv_from_mem(buffer, sizeof(buffer), 8); munit_assert(strv != NULL); munit_assert_string_equal(strv[0], "ff ff ff ff ff ff ff ff"); munit_assert_string_equal(strv[1], "ff ff ff ff ff ff"); munit_assert_null(strv[2]); } return MUNIT_OK; } #endif libei-1.2.1/src/util-strings.h000066400000000000000000000215741456005336000162160ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2008 Kristian Høgsberg * Copyright © 2013-2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_XLOCALE_H #include #endif #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include #include #include #endif #include "util-macros.h" #include "util-mem.h" #define streq(s1, s2) (strcmp((s1), (s2)) == 0) #define strneq(s1, s2, n) (strncmp((s1), (s2), (n)) == 0) /** * Returns the length of the string including the trailing zero. */ static inline size_t strlen0(const char *str) { return str ? strlen(str) + 1 : 0; } /** * strdup guaranteed to succeed. If the input string is NULL, the output * string is NULL. If the input string is a string pointer, we strdup or * abort on failure. */ static inline char* xstrdup(const char *str) { char *s; if (!str) return NULL; s = strdup(str); if (!s) abort(); return s; } static inline bool _printf_(3, 0) xvsnprintf(char *buf, size_t sz, const char *format, va_list ap) { int rc = vsnprintf(buf, sz, format, ap); return rc >= 0 && (size_t)rc < sz; } static inline bool _printf_(3, 4) xsnprintf(char *buf, size_t sz, const char *format, ...) { va_list ap; int rc; va_start(ap, format); rc = xvsnprintf(buf, sz, format, ap); va_end(ap); return rc; } static inline char * _printf_(1, 0) xvaprintf(const char *fmt, va_list args) { char *str; int len; len = vasprintf(&str, fmt, args); if (len == -1) return NULL; return str; } /** * A version of asprintf that returns the allocated string or NULL on error. */ static inline char * _printf_(1, 2) xaprintf(const char *fmt, ...) { va_list args; char *str; va_start(args, fmt); str = xvaprintf(fmt, args); va_end(args); return str; } static inline bool xatoi_base(const char *str, int *val, int base) { char *endptr; long v; assert(base == 10 || base == 16 || base == 8); errno = 0; v = strtol(str, &endptr, base); if (errno > 0) return false; if (str == endptr) return false; if (*str != '\0' && *endptr != '\0') return false; if (v > INT_MAX || v < INT_MIN) return false; *val = v; return true; } static inline bool xatoi(const char *str, int *val) { return xatoi_base(str, val, 10); } static inline bool xatou_base(const char *str, unsigned int *val, int base) { char *endptr; unsigned long v; assert(base == 10 || base == 16 || base == 8); errno = 0; v = strtoul(str, &endptr, base); if (errno > 0) return false; if (str == endptr) return false; if (*str != '\0' && *endptr != '\0') return false; if ((long)v < 0) return false; *val = v; return true; } static inline bool xatou(const char *str, unsigned int *val) { return xatou_base(str, val, 10); } static inline bool xatod(const char *str, double *val) { char *endptr; double v; #ifdef HAVE_LOCALE_H locale_t c_locale; #endif size_t slen = strlen(str); /* We don't have a use-case where we want to accept hex for a double * or any of the other values strtod can parse */ for (size_t i = 0; i < slen; i++) { char c = str[i]; if (isdigit(c)) continue; switch(c) { case '+': case '-': case '.': break; default: return false; } } #ifdef HAVE_LOCALE_H /* Create a "C" locale to force strtod to use '.' as separator */ c_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0); if (c_locale == (locale_t)0) return false; errno = 0; v = strtod_l(str, &endptr, c_locale); freelocale(c_locale); #else /* No locale support in provided libc, assume it already uses '.' */ errno = 0; v = strtod(str, &endptr); #endif if (errno > 0) return false; if (str == endptr) return false; if (*str != '\0' && *endptr != '\0') return false; if (v != 0.0 && !isnormal(v)) return false; *val = v; return true; } char **strv_from_string(const char *string, const char *separator); char *strv_join(char **strv, const char *separator); static inline void strv_free(char **strv) { char **s = strv; if (!strv) return; while (*s != NULL) { free(*s); *s = (char*)0x1; /* detect use-after-free */ s++; } free (strv); } DEFINE_TRIVIAL_CLEANUP_FUNC(char **, strv_free); char * strreplace(const char *string, const char *separator, const char *replacement); /** * Creates a list of strings representing the buffer similar to hexdump, i.e. * a buffer of [0, 1, 2, 3, ...] with a stride of 4 is split into * the strv of ["00 01 02 03", "04 05 06 07", ...]. */ char **strv_from_mem(const uint8_t *buffer, size_t sz, size_t stride); struct key_value_str{ char *key; char *value; }; struct key_value_double { double key; double value; }; static inline ssize_t kv_double_from_string(const char *string, const char *pair_separator, const char *kv_separator, struct key_value_double **result_out) { char **pairs; char **pair; struct key_value_double *result = NULL; ssize_t npairs = 0; unsigned int idx = 0; if (!pair_separator || pair_separator[0] == '\0' || !kv_separator || kv_separator[0] == '\0') return -1; pairs = strv_from_string(string, pair_separator); if (!pairs) return -1; for (pair = pairs; *pair; pair++) npairs++; if (npairs == 0) goto error; result = xalloc(npairs * sizeof *result); for (pair = pairs; *pair; pair++) { char **kv = strv_from_string(*pair, kv_separator); double k, v; if (!kv || !kv[0] || !kv[1] || kv[2] || !xatod(kv[0], &k) || !xatod(kv[1], &v)) { strv_free(kv); goto error; } result[idx].key = k; result[idx].value = v; idx++; strv_free(kv); } strv_free(pairs); *result_out = result; return npairs; error: strv_free(pairs); free(result); return -1; } /** * Strip any of the characters in what from the beginning and end of the * input string. * * @return a newly allocated string with none of "what" at the beginning or * end of string */ static inline char * strstrip(const char *input, const char *what) { char *str, *last; str = xstrdup(&input[strspn(input, what)]); last = str; for (char *c = str; *c != '\0'; c++) { if (!strchr(what, *c)) last = c + 1; } *last = '\0'; return str; } /** * Return true if str ends in suffix, false otherwise. If the suffix is the * empty string, strendswith() always returns false. */ static inline bool strendswith(const char *str, const char *suffix) { size_t slen = strlen(str); size_t suffixlen = strlen(suffix); size_t offset; if (slen == 0 || suffixlen == 0 || suffixlen > slen) return false; offset = slen - suffixlen; return strneq(&str[offset], suffix, suffixlen); } static inline bool strstartswith(const char *str, const char *prefix) { size_t prefixlen = strlen(prefix); return prefixlen > 0 ? strneq(str, prefix, strlen(prefix)) : false; } /** * Return the content of /proc/$pid/cmdline as newly allocated string. */ static inline char * cmdline_as_str(void) { #ifdef KERN_PROC_ARGS int mib[] = { CTL_KERN, #if defined(__NetBSD__) || defined(__OpenBSD__) KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV, #else KERN_PROC, KERN_PROC_ARGS, getpid(), #endif }; size_t len; if (sysctl(mib, ARRAY_LENGTH(mib), NULL, &len, NULL, 0)) return NULL; char *const procargs = malloc(len); if (sysctl(mib, ARRAY_LENGTH(mib), procargs, &len, NULL, 0)) return NULL; return procargs; #else int fd = open("/proc/self/cmdline", O_RDONLY); if (fd > 0) { char buffer[1024] = {0}; int len = read(fd, buffer, sizeof(buffer) - 1); close(fd); return len > 0 ? xstrdup(buffer) : NULL; } #endif return NULL; } libei-1.2.1/src/util-structs.h000066400000000000000000000025011456005336000162210ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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. */ /* Just a collection of useful structs */ #pragma once #include "config.h" #include struct dimensions { uint32_t width; uint32_t height; }; libei-1.2.1/src/util-time.h000066400000000000000000000037561456005336000154650ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include /* Merely for code readability, e.g. timeout = us(100); */ static inline uint64_t us(uint64_t us) { return us; } static inline uint64_t us2ms(uint64_t us) { return us / 1000; } static inline uint64_t ns2us(uint64_t ns) { return us(ns / 1000); } static inline uint64_t ms2us(uint64_t ms) { return us(ms * 1000); } static inline uint64_t s2us(uint64_t s) { return ms2us(s * 1000); } static inline int now(uint64_t *now_out) { struct timespec ts = { 0, 0 }; assert(now_out != NULL); if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { *now_out = s2us(ts.tv_sec) + ns2us(ts.tv_nsec); return 0; } else { return -errno; } } static inline void msleep(int32_t millis) { usleep(ms2us(millis)); } libei-1.2.1/src/util-tristate.h000066400000000000000000000124211456005336000163530ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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. */ /** * A type-safe tristate implementation. A tristate value has three options, * usually a logical 'on' and 'off' plus the 'unset' value. * * Usage: * * DEFINE_TRISTATE(yes, no, unset); * DEFINE_TRISTATE(on, off, neither); * * tristate t = tristate_unset; * if (something) * t = tristate_yes; * else if (something_else) * t = tristate_no; * * if (tristate_is_yes(t)) * printf("yep"); * * switch (tristate_value(t)) { * case tristate_val_yes: * case tristate_val_no: * case tristate_val_unset: * } * * Basic type safety is provided - mixing tristates types causes an * abort(). For example: * * DEFINE_TRISTATE(yes, no, unset); * DEFINE_TRISTATE(on, off, neither); * * tristate t1 = tristate_unset; * tristate t2 = tristate_off; * tristate t3 = tristate_neither; * * t2 and t3 have the same "type". t1 is a different "type". * * tristate_is_neither(t1) // this will abort * tristate_is_yes(t2) // this will abort */ #pragma once #include typedef struct { unsigned _val; } tristate; /* Implementation detail: * Tristate value is type_mask | val * where val are the 2 LSB with * 11 ... logical true state * 10 ... logical false state * 00 ... unset state * All other bits are the type mask. This type mask is used to check that * two different tristate definitions cannot be intermixed. */ static const unsigned _TRISTATE_TYPE_MASK = ~0x3; /* implementation detail, ignore */ __attribute__((used)) static inline void _tristate_check_type(const tristate *t1, unsigned type) { assert((t1->_val & _TRISTATE_TYPE_MASK) == type || !"Invalid tristate type comparison"); } /** * For the three given arguments on, off and none, define: * - tristate_on, tristate_off and tristate_none as constant values to * assign. For example: tristate t = tristate_on; * - tristate_is_on(), tristate_is_off(), tristate_is_none() as functions to check * a tristate. This function will abort if different tristate types are * mixed. For example: * tristate t = tristate_on; * if (tristate_is_none(t)) { .... } * - tristate_onoff_value() to retrieve the value from a tristate to be used * in e.g. a switch statement. The values are tristate_val_on, * tristate_val_off, trisate_val_none. For example: * switch(tristate_onoff_value(t)) { * case tristate_val_on: break; * case tristate_val_off: break; * case tristate_val_none: break; * } */ #define DEFINE_TRISTATE(_on, _off, _none) \ static const unsigned _TRISTATE_TYPE_##_on##_off = (__LINE__ << 2); \ static const unsigned tristate_val_##_on = _TRISTATE_TYPE_##_on##_off | 3; \ static const unsigned tristate_val_##_off = _TRISTATE_TYPE_##_on##_off | 2; \ static const unsigned tristate_val_##_none = _TRISTATE_TYPE_##_on##_off | 0; \ static const tristate tristate_##_on = { ._val = tristate_val_##_on }; \ static const tristate tristate_##_off = { ._val = tristate_val_##_off }; \ static const tristate tristate_##_none = { ._val = tristate_val_##_none }; \ static inline bool tristate_is_##_on(tristate t) { \ _tristate_check_type(&t, _TRISTATE_TYPE_##_on##_off); \ return t._val == tristate_##_on._val; \ } \ __attribute__((used)) static inline bool tristate_is_##_off(tristate t) { \ _tristate_check_type(&t, _TRISTATE_TYPE_##_on##_off); \ return t._val == tristate_##_off._val; \ } \ __attribute__((used)) static inline bool tristate_is_##_none(tristate t) { \ _tristate_check_type(&t, _TRISTATE_TYPE_##_on##_off); \ return t._val == tristate_##_none._val; \ } \ __attribute__((used)) static inline signed char tristate_##_on##_off##_value(tristate t) { \ _tristate_check_type(&t, _TRISTATE_TYPE_##_on##_off); \ return t._val; \ } \ __attribute__((used)) static inline const char *tristate_##_on##_off##_name(tristate t) { \ if (tristate_is_##_on(t)) return #_on; \ if (tristate_is_##_off(t)) return #_off; \ if (tristate_is_##_none(t)) return #_none; \ assert(!"Invalid tristate value"); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ libei-1.2.1/src/util-version.h000066400000000000000000000031631456005336000162040ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2022 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" /** * Convenience macro to make version numbers grepable. * Simply use as * ```c * int version = VERSION_V(2); * const char *strversion = VERSION_V("1.2.3"); * struct { int major, minor; } majmin = VERSION_V({2, 0}); * ``` * And now all places you set a version number can be easily found with grep. * * The macro doesn't care about the actual value, so it'll take strings, etc. */ #define VERSION_V(...) __VA_ARGS__ libei-1.2.1/subprojects/000077500000000000000000000000001456005336000151445ustar00rootroot00000000000000libei-1.2.1/subprojects/munit.wrap000066400000000000000000000001161456005336000171710ustar00rootroot00000000000000[wrap-git] directory=munit url=https://github.com/nemequ/munit/ revision=head libei-1.2.1/test/000077500000000000000000000000001456005336000135605ustar00rootroot00000000000000libei-1.2.1/test/buildtest.c000066400000000000000000000002561456005336000157260ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #if INCLUDE_LIBEI #include #endif #if INCLUDE_LIBEIS #include #endif int main(int argc, char **argv) { return 0; } libei-1.2.1/test/buildtest.cc000066400000000000000000000001751456005336000160710ustar00rootroot00000000000000#include #include /* This is a build-test only */ using namespace std; int main(void) { return 0; } libei-1.2.1/test/conftest.py000066400000000000000000000003651456005336000157630ustar00rootroot00000000000000import os try: import xdist # noqa: F401 # Otherwise we get unknown hook 'pytest_xdist_auto_num_workers' def pytest_xdist_auto_num_workers(config): return os.getenv("FDO_CI_CONCURRENT", None) except ImportError: pass libei-1.2.1/test/eierpecken.c000066400000000000000000001175171456005336000160520ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include #include #include #include "eierpecken.h" #include "util-bits.h" #include "util-color.h" #include "util-io.h" #include "util-mem.h" #include "util-munit.h" #include "util-tristate.h" #include "util-logger.h" #include "util-object.h" #include "util-strings.h" #include "util-time.h" static bool enable_sigalarm; DEFINE_TRISTATE(yes, no, unset); struct peck { struct object object; struct ei *ei; struct eis *eis; uint32_t eis_behavior; uint32_t ei_behavior; struct logger *logger; int ei_socket_fd; /* The default seat/devices */ struct eis_seat *eis_seat; struct eis_device *eis_pointer; struct eis_device *eis_keyboard; struct eis_device *eis_abs; struct eis_device *eis_button; struct eis_device *eis_scroll; struct eis_device *eis_touch; struct ei_seat *ei_seat; struct ei_device *ei_pointer; struct ei_device *ei_keyboard; struct ei_device *ei_abs; struct ei_device *ei_button; struct ei_device *ei_scroll; struct ei_device *ei_touch; uint64_t now; struct eis_client *eis_client; uint32_t indent; struct sigaction sigact; bool ei_fatal_bugs; bool eis_fatal_bugs; uint64_t ei_time_offset; uint64_t eis_time_offset; }; static const uint32_t INDENTATION = 4; static void peck_indent(struct peck *peck) { peck->indent += INDENTATION; } static void peck_dedent(struct peck *peck) { assert(peck->indent >= INDENTATION); peck->indent -= INDENTATION; } static OBJECT_IMPLEMENT_GETTER(peck, indent, uint32_t); static uint64_t peck_ei_now_func(struct ei *ei) { static uint64_t offset = 0; struct peck *peck = ei_get_user_data(ei); if (peck->ei_time_offset != offset) { offset = peck->ei_time_offset; log_debug(peck, "Time is now offset by %" PRIu64"us\n", offset); } uint64_t ts = 0; now(&ts); return ts + peck->ei_time_offset; } static uint64_t peck_eis_now_func(struct eis *eis) { static uint64_t offset = 0; struct peck *peck = eis_get_user_data(eis); if (peck->eis_time_offset != offset) { offset = peck->eis_time_offset; log_debug(peck, "Time is now offset by %" PRIu64"us\n", offset); } uint64_t ts = 0; now(&ts); return ts + peck->ei_time_offset; } static void peck_destroy(struct peck *peck) { log_debug(peck, "destroying peck context\n"); /* we don't want valgrind to complain about us not handling *all* * events in a test */ peck_drain_ei(peck->ei); peck_drain_eis(peck->eis); log_debug(peck, "final event processing done\n"); eis_client_unref(peck->eis_client); eis_device_unref(peck->eis_pointer); eis_device_unref(peck->eis_abs); eis_device_unref(peck->eis_keyboard); eis_device_unref(peck->eis_touch); eis_device_unref(peck->eis_button); eis_device_unref(peck->eis_scroll); eis_seat_unref(peck->eis_seat); ei_device_unref(peck->ei_pointer); ei_device_unref(peck->ei_abs); ei_device_unref(peck->ei_keyboard); ei_device_unref(peck->ei_touch); ei_device_unref(peck->ei_button); ei_device_unref(peck->ei_scroll); ei_seat_unref(peck->ei_seat); ei_unref(peck->ei); eis_unref(peck->eis); logger_unref(peck->logger); if (enable_sigalarm) { struct itimerval timer = {0}; setitimer(ITIMER_REAL, &timer, 0); sigaction(SIGALRM, &peck->sigact, NULL); } } void peck_ei_add_time_offset(struct peck *peck, uint64_t us) { log_debug(peck, "Adding ei time offset of %" PRIu64 "us\n", us); peck->ei_time_offset += us; } void peck_eis_add_time_offset(struct peck *peck, uint64_t us) { log_debug(peck, "Adding EIS time offset of %" PRIu64 "us\n", us); peck->eis_time_offset += us; } static OBJECT_IMPLEMENT_CREATE(peck); OBJECT_IMPLEMENT_UNREF(peck); OBJECT_IMPLEMENT_GETTER(peck, ei, struct ei*); OBJECT_IMPLEMENT_GETTER(peck, eis, struct eis*); void peck_drop_ei(struct peck *peck) { peck->ei_pointer = ei_device_unref(peck->ei_pointer); peck->ei_keyboard = ei_device_unref(peck->ei_keyboard); peck->ei_abs = ei_device_unref(peck->ei_abs); peck->ei_touch = ei_device_unref(peck->ei_touch); peck->ei_seat = ei_seat_unref(peck->ei_seat); peck->ei = NULL; } struct eis_client * peck_eis_get_default_client(struct peck *peck) { munit_assert_ptr_not_null(peck->eis_client); return peck->eis_client; }; struct eis_seat * peck_eis_get_default_seat(struct peck *peck) { munit_assert_ptr_not_null(peck->eis_seat); return peck->eis_seat; }; struct eis_device * peck_eis_get_default_pointer(struct peck *peck) { munit_assert_ptr_not_null(peck->eis_pointer); return peck->eis_pointer; }; struct eis_device * peck_eis_get_default_pointer_absolute(struct peck *peck) { munit_assert_ptr_not_null(peck->eis_abs); return peck->eis_abs; }; struct eis_device * peck_eis_get_default_button(struct peck *peck) { munit_assert_ptr_not_null(peck->eis_button); return peck->eis_button; }; struct eis_device * peck_eis_get_default_scroll(struct peck *peck) { munit_assert_ptr_not_null(peck->eis_scroll); return peck->eis_scroll; }; struct eis_device * peck_eis_get_default_keyboard(struct peck *peck) { munit_assert_ptr_not_null(peck->eis_keyboard); return peck->eis_keyboard; }; struct eis_device * peck_eis_get_default_touch(struct peck *peck) { munit_assert_ptr_not_null(peck->eis_touch); return peck->eis_touch; }; struct ei_seat * peck_ei_get_default_seat(struct peck *peck) { munit_assert_ptr_not_null(peck->ei_seat); return peck->ei_seat; } struct ei_device * peck_ei_get_default_pointer(struct peck *peck) { munit_assert_ptr_not_null(peck->ei_pointer); return peck->ei_pointer; }; struct ei_device * peck_ei_get_default_pointer_absolute(struct peck *peck) { munit_assert_ptr_not_null(peck->ei_abs); return peck->ei_abs; }; struct ei_device * peck_ei_get_default_button(struct peck *peck) { munit_assert_ptr_not_null(peck->ei_button); return peck->ei_button; }; struct ei_device * peck_ei_get_default_scroll(struct peck *peck) { munit_assert_ptr_not_null(peck->ei_scroll); return peck->ei_scroll; }; struct ei_device * peck_ei_get_default_keyboard(struct peck *peck) { munit_assert_ptr_not_null(peck->ei_keyboard); return peck->ei_keyboard; }; struct ei_device * peck_ei_get_default_touch(struct peck *peck) { munit_assert_ptr_not_null(peck->ei_touch); return peck->ei_touch; }; /* Ensures that device frames in tests always have an ascending and fixed * interval. Every time this is called it adds 10ms to the time offset. */ uint64_t peck_ei_now(struct peck *peck) { peck_ei_add_time_offset(peck, ms2us(10)); return ei_now(peck->ei); } uint64_t peck_eis_now(struct peck *peck) { peck_eis_add_time_offset(peck, ms2us(10)); return eis_now(peck->eis); } void peck_ei_disable_fatal_bug(struct peck *peck) { peck->ei_fatal_bugs = false; } void peck_ei_enable_fatal_bug(struct peck *peck) { peck->ei_fatal_bugs = true; } void peck_eis_disable_fatal_bug(struct peck *peck) { peck->eis_fatal_bugs = false; } void peck_eis_enable_fatal_bug(struct peck *peck) { peck->eis_fatal_bugs = true; } static void peck_ei_log_handler(struct ei *ei, enum ei_log_priority priority, const char *message, struct ei_log_context *ctx) { bool use_color = true; run_only_once { use_color = isatty(STDERR_FILENO); } const char *prefix = NULL; switch (priority) { case EI_LOG_PRIORITY_ERROR: prefix = "ERR "; break; case EI_LOG_PRIORITY_WARNING: prefix = "WRN "; break; case EI_LOG_PRIORITY_INFO: prefix = "INF "; break; case EI_LOG_PRIORITY_DEBUG: prefix = " "; break; } struct peck *peck = ei_get_user_data(ei); fprintf(stderr, "| | ei | | %s |%*s🥚 %s%s%s\n", prefix, (int)peck_get_indent(peck), " ", use_color ? ANSI_FG_RGB(150, 125, 100) : "", message, use_color ? ansi_colorcode[RESET] : ""); if (peck->ei_fatal_bugs) { assert(!strstr(message, "🪳")); assert(!strstr(message, "🪲")); } } static void peck_eis_log_handler(struct eis *eis, enum eis_log_priority priority, const char *message, struct eis_log_context *ctx) { bool use_color = true; run_only_once { use_color = isatty(STDERR_FILENO); } const char *prefix = NULL; switch (priority) { case EIS_LOG_PRIORITY_ERROR: prefix = "ERR "; break; case EIS_LOG_PRIORITY_WARNING: prefix = "WRN "; break; case EIS_LOG_PRIORITY_INFO: prefix = "INF "; break; case EIS_LOG_PRIORITY_DEBUG: prefix = " "; break; } struct peck *peck = eis_get_user_data(eis); fprintf(stderr, "| | | EIS | %s |%*s🍨 %s%s%s\n", prefix, (int)peck_get_indent(peck), " ", use_color ? ANSI_FG_RGB(150, 200, 125) : "", message, use_color ? ansi_colorcode[RESET] : ""); if (peck->eis_fatal_bugs) { assert(!strstr(message, "🪳")); assert(!strstr(message, "🪲")); } } _printf_(7, 0) static void peck_log_handler(struct logger *logger, const char *prefix, enum logger_priority priority, const char *file, int lineno, const char *func, const char *format, va_list args) { _cleanup_free_ char *msgtype; bool use_color = true; run_only_once { use_color = isatty(STDERR_FILENO); } switch(priority) { case LOGGER_DEBUG: msgtype = xaprintf("%4d", lineno); break; case LOGGER_INFO: msgtype = xstrdup("INF"); break; case LOGGER_WARN: msgtype = xstrdup("WRN"); break; case LOGGER_ERROR: msgtype = xstrdup("ERR"); break; default: msgtype = xstrdup(""); break; } struct peck *peck = logger_get_user_data(logger); fprintf(stderr, "| peck | | | %s |%*s%s", msgtype, (int)peck_get_indent(peck), " ", use_color ? ANSI_FG_RGB(125, 150, 255) : ""); vfprintf(stderr, format, args); if (use_color) fprintf(stderr, "%s", ansi_colorcode[RESET]); } static void handle_sigalrm(int signo) { /* Nothing to do but this may cause a few EINTR */ } static struct peck * new_context(enum peck_ei_mode ei_mode) { struct peck *peck = peck_create(NULL); assert(ei_mode == PECK_EI_RECEIVER || ei_mode == PECK_EI_SENDER); int rc, fd; struct eis *eis = eis_new(peck); eis_log_set_handler(eis, peck_eis_log_handler); eis_log_set_priority(eis, EIS_LOG_PRIORITY_DEBUG); eis_clock_set_now_func(eis, peck_eis_now_func); rc = eis_setup_backend_fd(eis); munit_assert_int(rc, ==, 0); fd = eis_backend_fd_add_client(eis); munit_assert_int(fd, >, 0); peck->eis = eis; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_FRAME); struct ei *ei = ei_mode == PECK_EI_RECEIVER ? ei_new_receiver(peck) : ei_new_sender(peck); ei_set_user_data(ei, peck); ei_log_set_handler(ei, peck_ei_log_handler); ei_log_set_priority(ei, EI_LOG_PRIORITY_DEBUG); ei_clock_set_now_func(ei, peck_ei_now_func); ei_configure_name(ei, "eierpecken test context"); rc = ei_setup_backend_fd(ei, fd); munit_assert_int(rc, ==, 0); peck->ei_socket_fd = -1; peck->ei = ei; peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSTART); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_FRAME); peck_ei_enable_fatal_bug(peck); peck_eis_enable_fatal_bug(peck); peck->logger = logger_new("peck", peck); logger_set_handler(peck->logger, peck_log_handler); logger_set_priority(peck->logger, LOGGER_DEBUG); if (enable_sigalarm) { /* We set up SIGALRM to hammer us because libei is used by Xwayland which * uses that for scheduling and we need to be able to handle that */ struct sigaction act; sigaddset(&act.sa_mask, SIGALRM); act.sa_flags = 0; act.sa_handler = handle_sigalrm; rc = xerrno(sigaction(SIGALRM, &act, &peck->sigact)); if (rc >= 0) { struct itimerval timer = { .it_interval = { .tv_sec = 0, .tv_usec = 5, }, .it_value = { .tv_sec = 0, .tv_usec = 5, } }; rc = xerrno(setitimer(ITIMER_REAL, &timer, 0)); if (rc < 0) { log_error(peck, "Failed to enable timer: %s\n", strerror(-rc)); munit_assert_int(rc, >=, 0); } } else { log_error(peck, "Failed to enable SIGALRM: %s\n", strerror(-rc)); munit_assert_int(rc, >=, 0); } } return peck; } struct peck * peck_new_context(enum peck_ei_mode ei_mode) { return new_context(ei_mode); } struct peck * peck_new(void) { return peck_new_context(PECK_EI_SENDER); } void peck_ei_connect(struct peck *peck) { assert(peck->ei_socket_fd != -1); struct ei *ei = peck->ei; int rc = ei_setup_backend_fd(ei, peck->ei_socket_fd); munit_assert_int(rc, ==, 0); peck->ei_socket_fd = -1; } void peck_enable_eis_behavior(struct peck *peck, enum peck_eis_behavior behavior) { switch (behavior) { case PECK_EIS_BEHAVIOR_NONE: peck->eis_behavior = 0; break; case PECK_EIS_BEHAVIOR_DEFAULT_SEAT: case PECK_EIS_BEHAVIOR_NO_DEFAULT_SEAT: flag_set(peck->eis_behavior, behavior); break; case PECK_EIS_BEHAVIOR_ACCEPT_ALL: peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_CLOSE_DEVICE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_START_EMULATING); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_STOP_EMULATING); break; case PECK_EIS_BEHAVIOR_ADD_DEVICES: peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH); break; case PECK_EIS_BEHAVIOR_REJECT_CLIENT: flag_clear(peck->eis_behavior, behavior - 1); flag_set(peck->eis_behavior, behavior); break; case PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT: case PECK_EIS_BEHAVIOR_HANDLE_CLOSE_DEVICE: case PECK_EIS_BEHAVIOR_HANDLE_FRAME: case PECK_EIS_BEHAVIOR_HANDLE_START_EMULATING: case PECK_EIS_BEHAVIOR_HANDLE_STOP_EMULATING: case PECK_EIS_BEHAVIOR_ADD_POINTER: case PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE: case PECK_EIS_BEHAVIOR_ADD_KEYBOARD: case PECK_EIS_BEHAVIOR_ADD_TOUCH: flag_set(peck->eis_behavior, behavior); break; case PECK_EIS_BEHAVIOR_ACCEPT_CLIENT: flag_clear(peck->eis_behavior, behavior + 1); flag_set(peck->eis_behavior, behavior); break; case PECK_EIS_BEHAVIOR_RESUME_DEVICE: flag_clear(peck->eis_behavior, PECK_EIS_BEHAVIOR_SUSPEND_DEVICE); flag_set(peck->eis_behavior, behavior); break; case PECK_EIS_BEHAVIOR_SUSPEND_DEVICE: flag_clear(peck->eis_behavior, PECK_EIS_BEHAVIOR_RESUME_DEVICE); flag_set(peck->eis_behavior, behavior); break; } } void peck_enable_ei_behavior(struct peck *peck, enum peck_ei_behavior behavior) { switch (behavior) { case PECK_EI_BEHAVIOR_NONE: peck->ei_behavior = 0; break; case PECK_EI_BEHAVIOR_HANDLE_CONNECT: case PECK_EI_BEHAVIOR_AUTOSEAT: flag_set(peck->ei_behavior, behavior); break; case PECK_EI_BEHAVIOR_AUTODEVICES: peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_RESUMED); break; case PECK_EI_BEHAVIOR_HANDLE_ADDED: peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER_ABSOLUTE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_TOUCH); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_BUTTON); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_SCROLL); break; case PECK_EI_BEHAVIOR_AUTOSTART: case PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER: case PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER_ABSOLUTE: case PECK_EI_BEHAVIOR_HANDLE_ADDED_KEYBOARD: case PECK_EI_BEHAVIOR_HANDLE_ADDED_TOUCH: case PECK_EI_BEHAVIOR_HANDLE_ADDED_BUTTON: case PECK_EI_BEHAVIOR_HANDLE_ADDED_SCROLL: case PECK_EI_BEHAVIOR_HANDLE_FRAME: flag_set(peck->ei_behavior, behavior); break; case PECK_EI_BEHAVIOR_HANDLE_RESUMED: case PECK_EI_BEHAVIOR_HANDLE_PAUSED: flag_set(peck->ei_behavior, behavior); break; } } static inline void peck_create_eis_seat(struct peck *peck, struct eis_client *client) { _unref_(eis_seat) *seat = eis_client_new_seat(client, "peck default seat"); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER_ABSOLUTE); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_KEYBOARD); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TOUCH); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_BUTTON); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_SCROLL); log_debug(peck, "EIS adding seat: '%s'\n", eis_seat_get_name(seat)); eis_seat_add(seat); peck->eis_seat = eis_seat_ref(seat); } static inline void peck_handle_eis_connect(struct peck *peck, struct eis_event *e) { struct eis_client *client = eis_event_get_client(e); if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT)) { log_debug(peck, "EIS accepting client: '%s'\n", eis_client_get_name(client)); peck->eis_client = eis_client_ref(client); eis_client_connect(client); if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_DEFAULT_SEAT)) peck_create_eis_seat(peck, client); } else { log_debug(peck, "EIS disconnecting client: '%s'\n", eis_client_get_name(client)); eis_client_disconnect(client); } } static inline struct eis_device * peck_eis_create_pointer(struct peck *peck, struct eis_seat *seat, const char *name) { struct eis_device *device = eis_seat_new_device(seat); eis_device_configure_name(device, name); eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER); eis_device_configure_capability(device, EIS_DEVICE_CAP_BUTTON); eis_device_configure_capability(device, EIS_DEVICE_CAP_SCROLL); eis_device_add(device); return device; } static inline struct eis_device * peck_eis_create_pointer_absolute(struct peck *peck, struct eis_seat *seat, const char *name) { struct eis_device *device = eis_seat_new_device(seat); _unref_(eis_region) *region = eis_device_new_region(device); eis_region_set_offset(region, 0, 0); eis_region_set_size(region, 1920, 1080); eis_device_configure_name(device, name); eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE); eis_device_configure_capability(device, EIS_DEVICE_CAP_BUTTON); eis_device_configure_capability(device, EIS_DEVICE_CAP_SCROLL); eis_region_add(region); eis_device_add(device); return device; } static inline struct eis_device * peck_eis_create_keyboard(struct peck *peck, struct eis_seat *seat, const char *name) { struct eis_device *device = eis_seat_new_device(seat); eis_device_configure_name(device, name); eis_device_configure_capability(device, EIS_DEVICE_CAP_KEYBOARD); eis_device_add(device); return device; } static inline struct eis_device * peck_eis_create_touch(struct peck *peck, struct eis_seat *seat, const char *name) { struct eis_device *device = eis_seat_new_device(seat); _unref_(eis_region) *region = eis_device_new_region(device); eis_region_set_offset(region, 0, 0); eis_region_set_size(region, 1920, 1080); eis_device_configure_name(device, name); eis_device_configure_capability(device, EIS_DEVICE_CAP_TOUCH); eis_region_add(region); eis_device_add(device); return device; } static inline void peck_handle_eis_seat_bind(struct peck *peck, struct eis_event *e) { struct eis_seat *seat = eis_event_get_seat(e); log_debug(peck, "EIS binding seat: '%s'\n", eis_seat_get_name(seat)); if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER)) { if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_ADD_POINTER) && !peck->eis_pointer) { log_debug(peck, "EIS creating default pointer\n"); _unref_(eis_device) *ptr = peck_eis_create_pointer(peck, seat, "default pointer"); if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_RESUME_DEVICE)) eis_device_resume(ptr); peck->eis_pointer = eis_device_ref(ptr); } } else { if (peck->eis_pointer) { log_debug(peck, "EIS removing default pointer\n"); _unref_(eis_device) *ptr = steal(&peck->eis_pointer); eis_device_remove(ptr); } } if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) { if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE) && !peck->eis_abs) { log_debug(peck, "EIS creating default abs pointer\n"); _unref_(eis_device) *abs = peck_eis_create_pointer_absolute(peck, seat, "default abs"); if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_RESUME_DEVICE)) eis_device_resume(abs); peck->eis_abs = eis_device_ref(abs); } } else { if (peck->eis_abs) { log_debug(peck, "EIS removing default abs\n"); _unref_(eis_device) *abs = steal(&peck->eis_abs); eis_device_remove(abs); } } if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD)) { if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_ADD_KEYBOARD) && !peck->eis_keyboard) { log_debug(peck, "EIS creating default keyboard\n"); _unref_(eis_device) *kbd = peck_eis_create_keyboard(peck, seat, "default keyboard"); if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_RESUME_DEVICE)) eis_device_resume(kbd); peck->eis_keyboard = eis_device_ref(kbd); } } else { if (peck->eis_keyboard) { log_debug(peck, "EIS removing default keyboard\n"); _unref_(eis_device) *kbd = steal(&peck->eis_keyboard); eis_device_remove(kbd); } } if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH)) { if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_ADD_TOUCH) && !peck->eis_touch) { log_debug(peck, "EIS creating default touch\n"); _unref_(eis_device) *touch = peck_eis_create_touch(peck, seat, "default touch"); if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_RESUME_DEVICE)) eis_device_resume(touch); peck->eis_touch = eis_device_ref(touch); } } else { if (peck->eis_touch) { log_debug(peck, "EIS removing default touch\n"); _unref_(eis_device) *touch = steal(&peck->eis_touch); eis_device_remove(touch); } } /* Removing all caps means removing the seat */ if (!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER_ABSOLUTE) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_BUTTON) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_SCROLL) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH)) eis_seat_remove(seat); } static inline void peck_eis_device_remove(struct peck *peck, struct eis_device *device) { eis_device_remove(device); if (device == peck->eis_pointer) peck->eis_pointer = eis_device_unref(device); if (device == peck->eis_abs) peck->eis_abs = eis_device_unref(device); if (device == peck->eis_keyboard) peck->eis_keyboard = eis_device_unref(device); if (device == peck->eis_touch) peck->eis_touch = eis_device_unref(device); if (device == peck->eis_button) peck->eis_button = eis_device_unref(device); if (device == peck->eis_scroll) peck->eis_scroll = eis_device_unref(device); } bool _peck_dispatch_eis(struct peck *peck, int lineno) { struct eis *eis = peck->eis; bool had_event = false; bool need_frame = false; static uint64_t last_timestamp; log_debug(peck, "EIS Dispatch, line %d\n", lineno); peck_indent(peck); while (eis) { eis_dispatch(eis); tristate process_event = tristate_unset; _unref_(eis_event) *e = eis_peek_event(eis); if (!e) break; switch (eis_event_get_type(e)) { case EIS_EVENT_CLIENT_CONNECT: case EIS_EVENT_CLIENT_DISCONNECT: if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT) || flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_REJECT_CLIENT)) process_event = tristate_yes; break; case EIS_EVENT_SEAT_BIND: if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT)) process_event = tristate_yes; else process_event = tristate_no; break; case EIS_EVENT_DEVICE_CLOSED: if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_CLOSE_DEVICE)) process_event = tristate_yes; else process_event = tristate_no; break; case EIS_EVENT_FRAME: /* Ensure we only expect frames when we expect them */ munit_assert_true(need_frame); need_frame = false; if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_FRAME)) process_event = tristate_yes; break; case EIS_EVENT_DEVICE_START_EMULATING: if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_START_EMULATING)) process_event = tristate_yes; break; case EIS_EVENT_DEVICE_STOP_EMULATING: if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_STOP_EMULATING)) process_event = tristate_yes; break; case EIS_EVENT_POINTER_MOTION: case EIS_EVENT_POINTER_MOTION_ABSOLUTE: case EIS_EVENT_BUTTON_BUTTON: case EIS_EVENT_SCROLL_DELTA: case EIS_EVENT_SCROLL_STOP: case EIS_EVENT_SCROLL_CANCEL: case EIS_EVENT_SCROLL_DISCRETE: case EIS_EVENT_KEYBOARD_KEY: case EIS_EVENT_TOUCH_DOWN: case EIS_EVENT_TOUCH_UP: case EIS_EVENT_TOUCH_MOTION: need_frame = true; break; } log_debug(peck, "EIS received %s.... %s\n", peck_eis_event_name(e), tristate_is_yes(process_event) ? "handling ourselves" : "punting to caller"); if (!tristate_is_yes(process_event)) break; had_event = true; /* manual unref, _cleanup_ will take care of the real event */ eis_event_unref(e); e = eis_get_event(eis); switch (eis_event_get_type(e)) { case EIS_EVENT_CLIENT_CONNECT: peck_handle_eis_connect(peck, e); break; case EIS_EVENT_CLIENT_DISCONNECT: log_debug(peck, "EIS disconnecting client: %s\n", eis_client_get_name(eis_event_get_client(e))); eis_client_disconnect(eis_event_get_client(e)); break; case EIS_EVENT_SEAT_BIND: peck_handle_eis_seat_bind(peck, e); break; case EIS_EVENT_DEVICE_CLOSED: peck_eis_device_remove(peck, eis_event_get_device(e)); break; case EIS_EVENT_FRAME: { uint64_t timestamp = eis_event_get_time(e); uint64_t ts_now = 0; munit_assert_int(now(&ts_now), ==, 0); munit_assert_int64(last_timestamp, <, timestamp); munit_assert_int64(last_timestamp, <=, ts_now); last_timestamp = timestamp; break; } default: break; } } peck_dedent(peck); return had_event; } static inline tristate peck_check_ei_added(struct peck *peck, struct ei_event *e) { struct ei_device *device = ei_event_get_device(e); if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER)) return tristate_yes; if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) && flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER_ABSOLUTE)) return tristate_yes; if (ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD) && flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_ADDED_KEYBOARD)) return tristate_yes; if (ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH) & flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_ADDED_TOUCH)) return tristate_yes; if (ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON) & flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_ADDED_BUTTON)) return tristate_yes; if (ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL) & flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_ADDED_SCROLL)) return tristate_yes; return tristate_unset; } bool _peck_dispatch_ei(struct peck *peck, int lineno) { struct ei *ei = peck->ei; bool had_event = false; bool need_frame = false; log_debug(peck, "ei dispatch, line %d\n", lineno); peck_indent(peck); while (ei) { ei_dispatch(ei); tristate process_event = tristate_no; _unref_(ei_event) *e = ei_peek_event(ei); if (!e) break; switch (ei_event_get_type(e)) { case EI_EVENT_CONNECT: if (flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_CONNECT)) process_event = tristate_yes; break; case EI_EVENT_DISCONNECT: break; case EI_EVENT_SEAT_ADDED: if (peck->ei_seat == NULL && flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_AUTOSEAT)) process_event = tristate_yes; break; case EI_EVENT_SEAT_REMOVED: break; case EI_EVENT_DEVICE_ADDED: process_event = peck_check_ei_added(peck, e); break; case EI_EVENT_DEVICE_REMOVED: break; case EI_EVENT_DEVICE_RESUMED: if (flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_RESUMED)) process_event = tristate_yes; break; case EI_EVENT_DEVICE_PAUSED: if (flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_PAUSED)) process_event = tristate_yes; break; case EI_EVENT_FRAME: /* Ensure we only expect frames when we expect them */ munit_assert_true(need_frame); need_frame = false; if (flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_FRAME)) process_event = tristate_yes; break; case EI_EVENT_DEVICE_START_EMULATING: case EI_EVENT_DEVICE_STOP_EMULATING: break; case EI_EVENT_POINTER_MOTION: case EI_EVENT_POINTER_MOTION_ABSOLUTE: case EI_EVENT_BUTTON_BUTTON: case EI_EVENT_SCROLL_DELTA: case EI_EVENT_SCROLL_STOP: case EI_EVENT_SCROLL_CANCEL: case EI_EVENT_SCROLL_DISCRETE: case EI_EVENT_KEYBOARD_KEY: case EI_EVENT_KEYBOARD_MODIFIERS: case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_UP: case EI_EVENT_TOUCH_MOTION: need_frame = true; break; } log_debug(peck, "ei event %s.... %s\n", peck_ei_event_name(e), tristate_is_yes(process_event) ? "handling ourselves" : "punting to caller"); if (!tristate_is_yes(process_event)) break; had_event = true; /* manual unref, _cleanup_ will take care of the real event */ ei_event_unref(e); e = ei_get_event(ei); switch (ei_event_get_type(e)) { case EI_EVENT_CONNECT: log_debug(peck, "ei is connected\n"); /* Nothing to do here */ break; case EI_EVENT_SEAT_ADDED: { struct ei_seat *seat = ei_event_get_seat(e); munit_assert_ptr_null(peck->ei_seat); peck->ei_seat = ei_seat_ref(seat); log_debug(peck, "default seat: %s\n", ei_seat_get_name(peck->ei_seat)); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_POINTER_ABSOLUTE, EI_DEVICE_CAP_KEYBOARD, EI_DEVICE_CAP_TOUCH, EI_DEVICE_CAP_BUTTON, EI_DEVICE_CAP_SCROLL, NULL); break; } case EI_EVENT_DEVICE_ADDED: { struct ei_device *device = ei_event_get_device(e); if (!peck->ei_pointer && ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) peck->ei_pointer = ei_device_ref(device); if (!peck->ei_abs && ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) peck->ei_abs = ei_device_ref(device); if (!peck->ei_keyboard && ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) peck->ei_keyboard = ei_device_ref(device); if (!peck->ei_touch && ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)) peck->ei_touch = ei_device_ref(device); if (!peck->ei_button && ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON)) peck->ei_button = ei_device_ref(device); if (!peck->ei_scroll && ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) peck->ei_scroll = ei_device_ref(device); break; } case EI_EVENT_DEVICE_RESUMED: if (flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_AUTOSTART)) { static uint32_t sequence; struct ei_device *device = ei_event_get_device(e); if (ei_is_sender(ei_device_get_context(device))) ei_device_start_emulating(device, ++sequence); } break; case EI_EVENT_DEVICE_PAUSED: /* Nothing to do here */ break; default: break; } } peck_dedent(peck); return had_event; } void _peck_dispatch_until_stable(struct peck *peck, const char *func, int lineno) { int eis = 0, ei = 0; int loop_counter = 0; log_debug(peck, "dispatching until stable (%s:%d) {\n", func, lineno); peck_indent(peck); /* we go two dispatch loops for both ei and EIS before we say it's * stable. With the DEVICE_REGION/KEYMAP/DONE event split we may * get events on the wire that don't result in an actual event * yet, so we can get stuck if we just wait for one. */ do { log_debug(peck, "entering dispatch loop %d {\n", loop_counter++); peck_indent(peck); eis = _peck_dispatch_eis(peck, lineno) ? 0 : eis + 1; ei = _peck_dispatch_ei(peck, lineno) ? 0 : ei + 1; peck_dedent(peck); log_debug(peck, "} done: ei %d|eis %d\n", ei, eis); } while (ei <= 2 || eis <= 2); peck_dedent(peck); log_debug(peck, "} stable (%s:%d)\n", func, lineno); } void peck_drain_eis(struct eis *eis) { if (!eis) return; eis_dispatch(eis); while (true) { _unref_(eis_event) *e = eis_get_event(eis); if (e == NULL) break; } } void peck_drain_ei(struct ei *ei) { if (!ei) return; ei_dispatch(ei); while (true) { _unref_(ei_event) *e = ei_get_event(ei); if (e == NULL) break; } } void peck_assert_no_ei_events(struct ei *ei) { struct peck *peck = ei_get_user_data(ei); ei_dispatch(ei); while (true) { _unref_(ei_event) *e = ei_get_event(ei); if (!e) return; if (peck && flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_FRAME) && ei_event_get_type(e) == EI_EVENT_FRAME) { log_debug(peck, "Skipping over frame event\n"); continue; } munit_errorf("Expected empty event queue, have: %s\n", peck_ei_event_name(e)); } } void peck_assert_no_eis_events(struct eis *eis) { struct peck *peck = eis_get_user_data(eis); eis_dispatch(eis); while (true) { _unref_(eis_event) *e = eis_get_event(eis); if (!e) return; if (peck && flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_FRAME) && eis_event_get_type(e) == EIS_EVENT_FRAME) { log_debug(peck, "Skipping over frame event\n"); continue; } munit_errorf("Expected empty event queue, have: %s\n", peck_eis_event_name(e)); } } struct ei_event * _peck_ei_next_event(struct ei *ei, enum ei_event_type type, int lineno) { struct ei_event *event = ei_get_event(ei); struct peck *peck = ei_get_user_data(ei); if (flag_is_set(peck->ei_behavior, PECK_EI_BEHAVIOR_HANDLE_FRAME)) { while (event && ei_event_get_type(event) == EI_EVENT_FRAME) { ei_event_unref(event); log_debug(peck, "Skipping over frame event\n"); event = ei_get_event(ei); } } if (!event) munit_errorf("Expected ei event type %s, got none, line %d\n", peck_ei_event_type_name(type), lineno); if (!streq(peck_ei_event_name(event), peck_ei_event_type_name(type))) munit_errorf("Expected ei event type %s, got %s, line %d\n", peck_ei_event_type_name(type), peck_ei_event_name(event), lineno); log_debug(peck, "ei passing %s to caller, line %d\n", peck_ei_event_name(event), lineno); return event; } struct eis_event * _peck_eis_next_event(struct eis *eis, enum eis_event_type type, int lineno) { struct eis_event *event = eis_get_event(eis); struct peck *peck = eis_get_user_data(eis); if (flag_is_set(peck->eis_behavior, PECK_EIS_BEHAVIOR_HANDLE_FRAME)) { while (event && eis_event_get_type(event) == EIS_EVENT_FRAME) { eis_event_unref(event); log_debug(peck, "Skipping over frame event\n"); event = eis_get_event(eis); } } if (!event) munit_errorf("Expected EIS event type %s, got none, line %d\n", peck_eis_event_type_name(type), lineno); munit_assert_string_equal(peck_eis_event_name(event), peck_eis_event_type_name(type)); log_debug(peck, "EIS passing %s to caller, line %d\n", peck_eis_event_name(event), lineno); return event; } struct eis_event * _peck_eis_touch_event(struct eis *eis, enum eis_event_type type, double x, double y, int lineno) { assert(type == EIS_EVENT_TOUCH_DOWN || type == EIS_EVENT_TOUCH_MOTION); struct eis_event *event = _peck_eis_next_event(eis, type, lineno); double ex = eis_event_touch_get_x(event); double ey = eis_event_touch_get_y(event); if (fabs(ex - x) > 1e-3 || fabs(ey - y) > 1e-3) { munit_errorf("Touch coordinate mismatch: have (%.2f/%.2f) need (%.2f/%.2f)\n", ex, ey, x, y); } return event; } struct ei_event * _peck_ei_touch_event(struct ei *ei, enum ei_event_type type, double x, double y, int lineno) { assert(type == EI_EVENT_TOUCH_DOWN || type == EI_EVENT_TOUCH_MOTION); struct ei_event *event = _peck_ei_next_event(ei, type, lineno); double ex = ei_event_touch_get_x(event); double ey = ei_event_touch_get_y(event); if (fabs(ex - x) > 1e-3 || fabs(ey - y) > 1e-3) { munit_errorf("Touch coordinate mismatch: have (%.2f/%.2f) need (%.2f/%.2f)\n", ex, ey, x, y); } return event; } const char * peck_ei_event_name(struct ei_event *e) { return peck_ei_event_type_name(ei_event_get_type(e)); } const char * peck_ei_event_type_name(enum ei_event_type type) { #define CASE_STRING(_name) \ case EI_EVENT_##_name: return #_name switch (type) { CASE_STRING(CONNECT); CASE_STRING(DISCONNECT); CASE_STRING(SEAT_ADDED); CASE_STRING(SEAT_REMOVED); CASE_STRING(DEVICE_ADDED); CASE_STRING(DEVICE_REMOVED); CASE_STRING(DEVICE_PAUSED); CASE_STRING(DEVICE_RESUMED); CASE_STRING(KEYBOARD_MODIFIERS); CASE_STRING(FRAME); CASE_STRING(DEVICE_START_EMULATING); CASE_STRING(DEVICE_STOP_EMULATING); CASE_STRING(POINTER_MOTION); CASE_STRING(POINTER_MOTION_ABSOLUTE); CASE_STRING(BUTTON_BUTTON); CASE_STRING(SCROLL_DELTA); CASE_STRING(SCROLL_STOP); CASE_STRING(SCROLL_CANCEL); CASE_STRING(SCROLL_DISCRETE); CASE_STRING(KEYBOARD_KEY); CASE_STRING(TOUCH_DOWN); CASE_STRING(TOUCH_UP); CASE_STRING(TOUCH_MOTION); } #undef CASE_STRING assert(!"Unhandled ei event type"); } const char * peck_eis_event_name(struct eis_event *e) { return peck_eis_event_type_name(eis_event_get_type(e)); } const char * peck_eis_event_type_name(enum eis_event_type type) { #define CASE_STRING(_name) \ case EIS_EVENT_##_name: return #_name switch (type) { CASE_STRING(CLIENT_CONNECT); CASE_STRING(CLIENT_DISCONNECT); CASE_STRING(SEAT_BIND); CASE_STRING(DEVICE_CLOSED); CASE_STRING(DEVICE_START_EMULATING); CASE_STRING(DEVICE_STOP_EMULATING); CASE_STRING(POINTER_MOTION); CASE_STRING(POINTER_MOTION_ABSOLUTE); CASE_STRING(BUTTON_BUTTON); CASE_STRING(SCROLL_DELTA); CASE_STRING(SCROLL_STOP); CASE_STRING(SCROLL_CANCEL); CASE_STRING(SCROLL_DISCRETE); CASE_STRING(KEYBOARD_KEY); CASE_STRING(TOUCH_DOWN); CASE_STRING(TOUCH_UP); CASE_STRING(TOUCH_MOTION); CASE_STRING(FRAME); } #undef CASE_STRING assert(!"Unhandled EIS event type"); } void _peck_mark(struct peck *peck, const char *func, int line) { static uint32_t mark = 0; log_debug(peck, "mark %3d: line %d in %s()\n", mark++, line, func); } void _peck_debug(struct peck *peck, const char *func, int line, const char *format, ...) { va_list args; va_start(args, format); log_msg_va(peck->logger, LOGGER_DEBUG, "unused", line, func, format, args); va_end(args); } MUNIT_GLOBAL_SETUP(init_sigalarm) { int argc = setup->argc; char **argv = setup->argv; for (int i = 0; i < argc; i++) { if (streq(argv[i], "--enable-sigalrm") || streq(argv[i], "--enable-sigalarm")) { enable_sigalarm = true; for (int j = i + 1; j < setup->argc; j++) { setup->argv[i] = setup->argv[j]; } setup->argc--; break; } } } libei-1.2.1/test/eierpecken.h000066400000000000000000000260741456005336000160540ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #pragma once #include "libei.h" #include "libeis.h" #include "util-mem.h" enum peck_ei_mode { PECK_EI_RECEIVER = 20, PECK_EI_SENDER, }; /** * An enum to define basic server behavior in peck_dispatch_eis(). * Where a flag is **not** set for any specific behaviour, that event will * remain on the event queue after peck_dispatch_eis(). For example, a * caller setting @ref PECK_EIS_BEHAVIOR_ACCEPT_CLIENT will see * the device added event as first event in the queue. */ enum peck_eis_behavior { /** * Behavior of EIS is implemented in the test case. */ PECK_EIS_BEHAVIOR_NONE, /** * Accept all client connection requests, create default seats * **and** resume any device immediately after add. */ PECK_EIS_BEHAVIOR_ACCEPT_ALL, /** * Process connect/disconnect requests from the client. */ PECK_EIS_BEHAVIOR_ACCEPT_CLIENT, PECK_EIS_BEHAVIOR_REJECT_CLIENT, /** * Create a "default" seat. This behavior is enabled as part of * PECK_EIS_BEHAVIOR_ACCEPT_ALL. */ PECK_EIS_BEHAVIOR_DEFAULT_SEAT, /** * Do not create a "default" seat. */ PECK_EIS_BEHAVIOR_NO_DEFAULT_SEAT, /** * Handle the bind seat request */ PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT, /** * Handle the device close. This behavior is enabled as part of * PECK_EIS_BEHAVIOR_ACCEPT_ALL. */ PECK_EIS_BEHAVIOR_HANDLE_CLOSE_DEVICE, /** * Handle frame events. This behavior is enabled by default. */ PECK_EIS_BEHAVIOR_HANDLE_FRAME, /** * Handle the start/stop emulation event. This behavior is enabled as part of * PECK_EIS_BEHAVIOR_ACCEPT_ALL. */ PECK_EIS_BEHAVIOR_HANDLE_START_EMULATING, PECK_EIS_BEHAVIOR_HANDLE_STOP_EMULATING, /** * Create default devices */ PECK_EIS_BEHAVIOR_ADD_DEVICES, /**< add all of the below */ PECK_EIS_BEHAVIOR_ADD_POINTER, PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE, PECK_EIS_BEHAVIOR_ADD_KEYBOARD, PECK_EIS_BEHAVIOR_ADD_TOUCH, PECK_EIS_BEHAVIOR_RESUME_DEVICE, PECK_EIS_BEHAVIOR_SUSPEND_DEVICE, }; enum peck_ei_behavior { PECK_EI_BEHAVIOR_NONE, /* enabled by default - handle the Connect event */ PECK_EI_BEHAVIOR_HANDLE_CONNECT, /* enabled by default - handle the first seat added event, setting * the default seat to the first seat and bind to it */ PECK_EI_BEHAVIOR_AUTOSEAT, /* handle Connect/Seat/Added/Resumed events, i.e. anything until the * first real device event */ PECK_EI_BEHAVIOR_AUTODEVICES, /** * Immediately send a StartEmulating request once a device is resumed. */ PECK_EI_BEHAVIOR_AUTOSTART, PECK_EI_BEHAVIOR_HANDLE_ADDED, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER_ABSOLUTE, PECK_EI_BEHAVIOR_HANDLE_ADDED_KEYBOARD, PECK_EI_BEHAVIOR_HANDLE_ADDED_TOUCH, PECK_EI_BEHAVIOR_HANDLE_ADDED_BUTTON, PECK_EI_BEHAVIOR_HANDLE_ADDED_SCROLL, PECK_EI_BEHAVIOR_HANDLE_RESUMED, PECK_EI_BEHAVIOR_HANDLE_PAUSED, /** * Handle frame events. This behavior is enabled by default. */ PECK_EI_BEHAVIOR_HANDLE_FRAME, }; struct peck; struct peck * peck_new(void); struct peck * peck_new_context(enum peck_ei_mode ei_mode); void peck_ei_connect(struct peck *peck); void _peck_mark(struct peck *peck, const char *func, int line); /** Add debug marker to the log output */ #define peck_mark(peck_) _peck_mark(peck_, __func__, __LINE__) void _peck_debug(struct peck *peck, const char *func, int line, const char *format, ...); #define peck_debug(peck_, ...) _peck_debug(peck_, __func__, __LINE__, __VA_ARGS__) void peck_ei_disable_fatal_bug(struct peck *peck); void peck_ei_enable_fatal_bug(struct peck *peck); void peck_eis_disable_fatal_bug(struct peck *peck); void peck_eis_enable_fatal_bug(struct peck *peck); void peck_ei_add_time_offset(struct peck *peck, uint64_t us); void peck_eis_add_time_offset(struct peck *peck, uint64_t us); void peck_enable_eis_behavior(struct peck *peck, enum peck_eis_behavior behavior); void peck_enable_ei_behavior(struct peck *peck, enum peck_ei_behavior behavior); struct ei * peck_get_ei(struct peck *peck); void peck_drop_ei(struct peck *peck); struct eis * peck_get_eis(struct peck *peck); struct eis_client * peck_eis_get_default_client(struct peck *peck); struct eis_seat * peck_eis_get_default_seat(struct peck *peck); struct eis_device * peck_eis_get_default_pointer(struct peck *peck); struct eis_device * peck_eis_get_default_keyboard(struct peck *peck); struct eis_device * peck_eis_get_default_pointer_absolute(struct peck *peck); struct eis_device * peck_eis_get_default_touch(struct peck *peck); struct eis_device * peck_eis_get_default_button(struct peck *peck); struct eis_device * peck_eis_get_default_scroll(struct peck *peck); struct ei_seat * peck_ei_get_default_seat(struct peck *peck); struct ei_device * peck_ei_get_default_pointer(struct peck *peck); struct ei_device * peck_ei_get_default_button(struct peck *peck); struct ei_device * peck_ei_get_default_scroll(struct peck *peck); struct ei_device * peck_ei_get_default_keyboard(struct peck *peck); struct ei_device * peck_ei_get_default_pointer_absolute(struct peck *peck); struct ei_device * peck_ei_get_default_touch(struct peck *peck); uint64_t peck_ei_now(struct peck *peck); uint64_t peck_eis_now(struct peck *peck); /** * Dispatch all events according to the currently defined behavior. * When this function returns false, the connection is in a "stable" state * and futher calls to this function will not change that state. This stable * state may mean either there are no events or events pending to be * processed by the caller. * * @return true if at least one event was processed according to the current * behavior */ #define peck_dispatch_eis(_p) \ _peck_dispatch_eis(_p, __LINE__) bool _peck_dispatch_eis(struct peck *peck, int lineno); /** * Dispatch all events according to the currently defined behavior. * When this function returns false, the connection is in a "stable" state * and futher calls to this function will not change that state. This stable * state may mean either there are no events or events pending to be * processed by the caller. * * @return true if at least one behavior was processes according to the * current behavior */ #define peck_dispatch_ei(_p) \ _peck_dispatch_ei(_p, __LINE__) bool _peck_dispatch_ei(struct peck *peck, int lineno); struct peck * peck_unref(struct peck *peck); /** * Dispatch both EIS and EI until a stable state is reached, i.e. until * either there are no events pending or until either (or both) have events * pending that are not processed by the current behaviors. */ void _peck_dispatch_until_stable(struct peck *peck, const char *func, int lineno); #define peck_dispatch_until_stable(_p) \ _peck_dispatch_until_stable((_p), __func__, __LINE__) /** * Discard all pending events. */ void peck_drain_eis(struct eis *eis); /** * Discard all pending events. */ void peck_drain_ei(struct ei *ei); void peck_assert_no_eis_events(struct eis *eis); void peck_assert_no_ei_events(struct ei *ei); #define peck_ei_next_event(_ei, _type) \ _peck_ei_next_event(_ei, _type, __LINE__) struct ei_event * _peck_ei_next_event(struct ei *ei, enum ei_event_type type, int lineno); #define peck_eis_next_event(_eis, _type) \ _peck_eis_next_event(_eis, _type, __LINE__) struct eis_event * _peck_eis_next_event(struct eis *eis, enum eis_event_type type, int lineno); #define peck_eis_touch_down(_eis, _x, _y) \ _peck_eis_touch_event(_eis, EIS_EVENT_TOUCH_DOWN, _x, _y, __LINE__) #define peck_eis_touch_motion(_eis, _x, _y) \ _peck_eis_touch_event(_eis, EIS_EVENT_TOUCH_MOTION, _x, _y, __LINE__) #define peck_eis_touch_up(_eis) \ _peck_eis_next_event(_eis, EIS_EVENT_TOUCH_UP, __LINE__) struct eis_event * _peck_eis_touch_event(struct eis *eis, enum eis_event_type type, double x, double y, int lineno); #define peck_ei_touch_down(_ei, _x, _y) \ _peck_ei_touch_event(_ei, EI_EVENT_TOUCH_DOWN, _x, _y, __LINE__) #define peck_ei_touch_motion(_ei, _x, _y) \ _peck_ei_touch_event(_ei, EI_EVENT_TOUCH_MOTION, _x, _y, __LINE__) #define peck_ei_touch_up(_ei) \ _peck_ei_next_event(_ei, EI_EVENT_TOUCH_UP, __LINE__) struct ei_event * _peck_ei_touch_event(struct ei *ei, enum ei_event_type type, double x, double y, int lineno); const char * peck_ei_event_name(struct ei_event *e); const char * peck_eis_event_name(struct eis_event *e); const char * peck_ei_event_type_name(enum ei_event_type type); const char * peck_eis_event_type_name(enum eis_event_type type); DEFINE_UNREF_CLEANUP_FUNC(peck); /* Define a bunch of _cleanup_foo_ macros for a struct foo */ DEFINE_UNREF_CLEANUP_FUNC(ei); DEFINE_UNREF_CLEANUP_FUNC(ei_event); DEFINE_UNREF_CLEANUP_FUNC(ei_device); DEFINE_UNREF_CLEANUP_FUNC(ei_touch); DEFINE_UNREF_CLEANUP_FUNC(ei_keymap); DEFINE_UNREF_CLEANUP_FUNC(ei_seat); DEFINE_UNREF_CLEANUP_FUNC(ei_region); DEFINE_UNREF_CLEANUP_FUNC(eis); DEFINE_UNREF_CLEANUP_FUNC(eis_client); DEFINE_UNREF_CLEANUP_FUNC(eis_event); DEFINE_UNREF_CLEANUP_FUNC(eis_device); DEFINE_UNREF_CLEANUP_FUNC(eis_touch); DEFINE_UNREF_CLEANUP_FUNC(eis_keymap); DEFINE_UNREF_CLEANUP_FUNC(eis_seat); DEFINE_UNREF_CLEANUP_FUNC(eis_region); /* Macros intended just for readability to make it more obvious which part of a test handles server vs client */ #define with_server(peck_) for (struct eis *eis = peck_get_eis(peck_); eis; eis = NULL) #define with_client(peck_) for (struct ei *ei = peck_get_ei(peck_); ei; ei = NULL) #define with_emulation(d_, sequence_) for (bool _loop = ({ ei_device_start_emulating(d_, sequence_); true;});\ _loop; \ ({ ei_device_stop_emulating(d_); _loop = false; })) #define with_nonfatal_eis_bug(peck_) for (bool _loop = ({ peck_eis_disable_fatal_bug(peck_); true; }); \ _loop; \ ({ peck_eis_enable_fatal_bug(peck_); _loop = false; })) #define with_nonfatal_ei_bug(peck_) for (bool _loop = ({ peck_ei_disable_fatal_bug(peck_); true; }); \ _loop; \ ({ peck_ei_enable_fatal_bug(peck_); _loop = false; })) #define peck_errno_check(_rc) { \ const int xrc = (_rc); \ if (xrc != 0) \ munit_errorf("errno is not 0: %d - %s\n", -xrc, strerror(-xrc)); \ } libei-1.2.1/test/eiproto.py.tmpl000066400000000000000000000277261456005336000166040ustar00rootroot00000000000000#!/usr/bin/env python3 # # GENERATED FILE, DO NOT EDIT # # SPDX-License-Identifier: MIT # {#- this is a jinja template, warning above is for the generated file Non-obvious variables set by the scanner that are used in this template: - target: because eis is actually eis_client in the code, the target points to either "ei" or "eis_client". The various attributes on target resolve accordingly. - request.fqdn/event.fqdn - the full name of a request/event with the interface name prefixed, "ei_foo_request_bar" or "ei_foo_event_bar" - incoming/outgoing: points to the list of requests or events, depending which one is the outgoing one from the perspective of the file we're generating (ei or eis) #} from typing import Any, Callable, Generator, Tuple from enum import IntEnum try: from enum import StrEnum except ImportError: from strenum import StrEnum import attr import binascii import itertools import logging import struct import structlog import time # type aliases s = str i = int u = int x = int t = int o = int n = int f = float h = int # FIXME this should be a file-like object logger = structlog.get_logger() def hexlify(data): return binascii.hexlify(data, sep=" ", bytes_per_sep=4) @attr.s class MethodCall: name: str = attr.ib() args: dict[str, Any] = attr.ib() objects: dict[str, "Interface"] = attr.ib(default=attr.Factory(dict)) timestamp: float = attr.ib(default=attr.Factory(time.time)) @attr.s class MessageHeader: object_id: int = attr.ib(repr=lambda id: f"{id:#x}") msglen: int = attr.ib() opcode: int = attr.ib() @classmethod def size(cls) -> int: return 16 @classmethod def from_data(cls, data: bytes) -> "MessageHeader": object_id, msglen, opcode = struct.unpack("=QII", data[:cls.size()]) return cls(object_id, msglen, opcode) @property def as_tuple(self) -> Tuple[int, int, int]: return self.object_id, self.msglen, self.opcode @attr.s class Context: objects: dict[str, "Interface"] = attr.ib(default=attr.Factory(dict)) _callbacks: dict[str, dict[int, Callable]] = attr.ib(init=False) _ids: Generator = attr.ib(init=False, default=attr.Factory(itertools.count)) @_callbacks.default # type: ignore def _default_callbacks(self) -> dict[str, dict[int, Callable]]: return { "register": {}, "unregister": {}} def register(self, object: "Interface") -> None: assert object.object_id not in self.objects logger.debug(f"registering object", interface=object.name, object_id=f"{object.object_id:#x}") self.objects[object.object_id] = object for cb in self._callbacks["register"].values(): cb(object) def unregister(self, object: "Interface") -> None: assert object.object_id in self.objects logger.debug(f"unregistering object", interface=object.name, object=object, object_id=f"{object.object_id:#x}") del self.objects[object.object_id] for cb in self._callbacks["unregister"].values(): cb(object) def connect(self, signal: str, callback: Callable) -> int: cbs = self._callbacks[signal] id = next(self._ids) cbs[id] = callback return id def disconnect(self, signal: str, id: int) -> None: del self._callbacks[signal][id] def dispatch(self, data: bytes) -> None: if len(data) < MessageHeader.size(): return header = MessageHeader.from_data(data) object_id, opcode, msglen = header.object_id, header.opcode, header.msglen logger.debug(f"incoming packet ({msglen} bytes)", object_id=f"{object_id:x}", opcode=opcode, bytes=hexlify(data[:msglen])) try: dispatcher = self.objects[object_id] except KeyError: logger.error("Message from unknown object", object_id=f"{object_id:x}") return msglen try: logger.debug(f"incoming packet: dispatching", func=f"{dispatcher.name}.{dispatcher.incoming[opcode]}()", object=dispatcher) except KeyError: logger.error("Invalid opcode for object", object_id=f"{object_id:x}", opcode=opcode) return msglen consumed = dispatcher.dispatch(data, context=self) return consumed @classmethod def create(cls) -> "Context": o = cls() o.register(EiHandshake.create(object_id=0, version=1)) return o class InterfaceName(StrEnum): {% for interface in interfaces %} {{ interface.name.upper() }} = "{{interface.protocol_name}}" {% endfor %} @attr.s(eq=False) class Interface: object_id: int = attr.ib(repr=lambda id: f"{id:#x}") version: int = attr.ib() callbacks: dict[str, Callable] = attr.ib(init=False, default=attr.Factory(dict), repr=False) calllog: list[MethodCall] = attr.ib(init=False, default=attr.Factory(list), repr=False) name: str = attr.ib(default="") incoming: dict[int, str] = attr.ib(default=attr.Factory(list), repr=False) outgoing: dict[int, str] = attr.ib(default=attr.Factory(list), repr=False) def format(self, *args, opcode: int, signature: str) -> bytes: encoding = ["=QII"] arguments = [] for sig, arg in zip(signature, args): if sig in ["u"]: encoding.append("I") elif sig in ["i"]: encoding.append("i") elif sig in ["f"]: encoding.append("f") elif sig in ["n", "o", "t"]: encoding.append("Q") elif sig in ["x"]: encoding.append("q") elif sig in ["s"]: encoding.append("I") arguments.append(len(arg) + 1) slen = ((len(arg) + 3) // 4) * 4 encoding.append(f"{slen}s") arg = arg.encode("utf8") elif sig in ["h"]: raise NotImplementedError("fd passing is not yet supported here") arguments.append(arg) format = "".join(encoding) length = struct.calcsize(format) header = MessageHeader(self.object_id, length, opcode) # logger.debug(f"Packing {encoding}: {arguments}") return struct.pack(format, *header.as_tuple, *arguments) def unpack(self, data, signature: str, names: list[str]) -> Tuple[int, dict[str, Any]]: encoding = ["=QII"] # the header for sig in signature: if sig in ["u"]: encoding.append("I") elif sig in ["i"]: encoding.append("i") elif sig in ["f"]: encoding.append("f") elif sig in ["x"]: encoding.append("q") elif sig in ["n", "o", "t"]: encoding.append("Q") elif sig in ["s"]: length_so_far = struct.calcsize("".join(encoding)) slen, = struct.unpack("I", data[length_so_far:length_so_far + 4]) slen = ((slen + 3) // 4) * 4 encoding.append(f"I{slen}s") elif sig in ["h"]: raise NotImplementedError("fd passing is not yet supported here") format = "".join(encoding) msglen = struct.calcsize(format) try: values = list(struct.unpack(format, data[:msglen])) except struct.error as e: logger.error(f"{e}", bytes=hexlify(data), length=len(data), encoding=format) raise e # logger.debug(f"unpacked {format} to {values}") results = [] values = values[3:] # drop id, length, opcode # we had to insert the string length into the format, filter the # value for that out again. for sig in signature: if sig in ["s"]: values.pop(0) s = values.pop(0) if not s: s = None # zero-length string is None else: s = s.decode("utf8").rstrip("\x00") # strip trailing zeroes results.append(s) else: results.append(values.pop(0)) # First two values are object_id and len|opcode return (msglen, { name: value for name, value in zip(names, results) }) def connect(self, event: str, callback: Callable): self.callbacks[event] = callback @classmethod def lookup(cls, name: str) -> "Interface": return { {% for interface in interfaces %} "{{interface.name}}": {{interface.camel_name}}, {% endfor %} }[name] {% for interface in interfaces %} @attr.s class {{interface.camel_name}}(Interface): {% for enum in interface.enums %} class {{component.capitalize()}}{{enum.camel_name}}(IntEnum): {% for entry in enum.entries %} {{entry.name.upper()}} = {{entry.value}} {% endfor %} {% endfor %} {% for outgoing in interface.outgoing %} def {{outgoing.camel_name}}(self{%- for arg in outgoing.arguments %}, {{arg.name}}: {{arg.signature}}{% endfor -%}) -> bytes: data = self.format({%- for arg in outgoing.arguments %}{{arg.name}}, {% endfor -%}opcode={{outgoing.opcode}}, signature="{{outgoing.signature}}") logger.debug("composing message", oject=self, func="{{interface.name}}.{{outgoing.name}}", args={ {%- for arg in outgoing.arguments %}"{{arg.name}}": {{arg.name}}, {% endfor -%} }, result=hexlify(data)) return data {% endfor %} {% for incoming in interface.incoming %} def on{{incoming.camel_name}}(self, context: Context{%- for arg in incoming.arguments %}, {{arg.name}}: {{arg.signature}}{% endfor -%}): new_objects = { {% for arg in incoming.arguments %} {% if arg.signature == "n" %} {% if arg.interface %} "{{arg.name}}": {{arg.interface.camel_name}}.create({{arg.name}}, {{arg.version_arg.name}}), {% else %} "{{arg.name}}": Interface.lookup(interface_name).create({{arg.name}}, {{arg.version_arg.name}}), {% endif %} {% endif %} {% endfor %} } for o in new_objects.values(): context.register(o) cb = self.callbacks.get("{{incoming.camel_name}}", None) if cb is not None: if new_objects: cb(self{%- for arg in incoming.arguments %}, {{arg.name}}{% endfor -%}, new_objects=new_objects) else: cb(self{%- for arg in incoming.arguments %}, {{arg.name}}{% endfor -%}) m = MethodCall(name="{{incoming.camel_name}}", args={ {% for arg in incoming.arguments %} "{{arg.name}}": {{arg.name}}, {% endfor %} }, objects=new_objects) self.calllog.append(m) {% if incoming.is_destructor %} context.unregister(self) {% endif %} {% endfor %} def dispatch(self, data: bytes, context: Context) -> int: header = MessageHeader.from_data(data) object_id, opcode = header.object_id, header.opcode if False: pass {% for incoming in interface.incoming %} elif opcode == {{incoming.opcode}}: consumed, args = self.unpack(data, signature="{{incoming.signature}}", names=[ {% for arg in incoming.arguments %} "{{arg.name}}", {% endfor %} ]) logger.debug("dispatching", object=self, func="{{incoming.camel_name}}", args=args) self.on{{incoming.camel_name}}(context, **args) {% endfor %} else: raise NotImplementedError(f"Invalid opcode {opcode}") return consumed @classmethod def create(cls, object_id: int, version: int): incoming = { {% for incoming in interface.incoming %} {{incoming.opcode}}: "{{incoming.name}}", {% endfor %} } outgoing = { {% for outgoing in interface.outgoing %} {{outgoing.opcode}}: "{{outgoing.name}}", {% endfor %} } return cls(object_id=object_id, version=version, name="{{interface.name}}", incoming=incoming, outgoing=outgoing) {% endfor %} libei-1.2.1/test/meson.build000066400000000000000000000162351456005336000157310ustar00rootroot00000000000000if get_option('tests').disabled() summary({'Test suite enabled': false}, section: 'Test options') subdir_done() endif subproject('munit', default_options: 'werror=false') munit = dependency('munit', fallback: ['munit', 'munit_dep']) lib_unittest = static_library('unittest', '../src/util-munit.c', dependencies: munit, include_directories: [inc_builddir], ) dep_unittest = declare_dependency( link_with: lib_unittest, dependencies: munit ) test('unit-tests-utils', executable('unit-tests-utils', 'unit-tests.c', src_libutil, include_directories: [inc_src, inc_builddir], c_args: ['-D_enable_tests_'], dependencies: [dep_unittest, dep_math, dep_epoll]), args: ['--log-visible', 'debug']) test('unit-tests-ei', executable('unit-tests-ei', 'unit-tests.c', src_libei, include_directories: [inc_src, inc_builddir], c_args: ['-D_enable_tests_'], dependencies: deps_libei + [dep_unittest]), args: ['--log-visible', 'debug']) test('unit-tests-eis', executable('unit-tests-eis', 'unit-tests.c', src_libeis, include_directories: [inc_src, inc_builddir], c_args: ['-D_enable_tests_'], dependencies: [dep_unittest, dep_libutil]), args: ['--log-visible', 'debug']) if build_oeffis test('unit-tests-oeffis', executable('unit-tests-oeffis', 'unit-tests.c', src_liboeffis, include_directories: [inc_src, inc_builddir], c_args: ['-D_enable_tests_'], dependencies: deps_liboeffis + [dep_unittest]), args: ['--log-visible', 'debug']) endif lib_eierpecken = static_library('eierpecken', 'eierpecken.h', 'eierpecken.c', include_directories: [inc_src, inc_builddir], dependencies: [munit, dep_libutil, dep_libei, dep_libeis], ) eierpecken = executable('eierpecken', 'test-ei-device.c', 'test-ei-seat.c', 'test-ei.c', 'test-eis.c', 'test-main.c', link_with: lib_eierpecken, include_directories: [inc_builddir], dependencies: [dep_unittest, dep_libei, dep_libeis]) test('eierpecken', eierpecken, args: ['--log-visible', 'debug', '--enable-sigalarm'], suite: 'sigalrm') test('eierpecken-no-sigalrm', eierpecken, args: ['--log-visible', 'debug'], suite: 'nosigalrm') valgrind = find_program('valgrind', required : false) if valgrind.found() and meson.version().version_compare('> 0.57') add_test_setup('valgrind', exe_wrapper : [ valgrind, '--leak-check=full', '--gen-suppressions=all', '--error-exitcode=3' ], exclude_suites: ['python', 'sigalrm'], # we don't want to valgrind python and SIGALRM tests timeout_multiplier : 100) else message('valgrind not found, disabling valgrind test suite') endif # build-test only executable('test-build-libei', 'buildtest.c', dependencies : [dep_libei], include_directories : [inc_src], c_args : ['-Werror', '-DINCLUDE_LIBEI=1'], install : false) executable('test-build-libeis', 'buildtest.c', dependencies : [dep_libeis], include_directories : [inc_src], c_args : ['-Werror', '-DINCLUDE_LIBEIS=1'], install : false) if add_languages('cpp', required: false) executable('test-build-cxx', 'buildtest.cc', dependencies: [dep_libei, dep_libeis], include_directories: [inc_src], install: false) endif # Python-based tests pymod = import('python') required_python_modules = ['pytest', 'attr', 'structlog'] python = pymod.find_installation('python3', required: get_option('tests')) if python.found() and python.language_version().version_compare('< 3.11') required_python_modules += ['strenum'] endif if build_oeffis required_python_modules += ['dbusmock'] endif python = pymod.find_installation('python3', modules: required_python_modules, required: get_option('tests')) pytest = find_program('pytest-3', 'pytest', required: get_option('tests')) pytest_args = ['--verbose', '--log-level=DEBUG'] enable_pytest = python.found() and pytest.found() if enable_pytest # pytest xdist is nice because it significantly speeds up our # test process, but it's not required optional_python_modules = ['xdist'] if pymod.find_installation('python3', modules: optional_python_modules, required: false).found() pytest_args += ['-n', 'auto'] configure_file(input: 'conftest.py', output: '@PLAINNAME@', copy: true) endif eiproto_python_template = files('eiproto.py.tmpl') eiproto_python = custom_target('eiproto.py', input: protocol_xml, output: 'eiproto.py', command: [scanner, '--component=ei', '--output=@OUTPUT@', '@INPUT@', eiproto_python_template], build_by_default: true) protocol_test_config = configuration_data() protocol_test_config.set('LIBEI_TEST_SERVER', eis_demo_server.full_path()) configure_file(input: 'test_protocol.py', output: '@PLAINNAME@', configuration: protocol_test_config) test('protocol-test', pytest, args: pytest_args + ['-k', 'TestEiProtocol'], suite: 'python', workdir: meson.project_build_root(), ) if valgrind.found() env = environment() env.set('LIBEI_USE_VALGRIND', '1') test('protocol-test-valgrind', pytest, args: pytest_args + ['-k', 'TestEiProtocol'], suite: 'python', workdir: meson.project_build_root(), env: env ) endif if build_oeffis env = environment() env.set('LD_LIBRARY_PATH', fs.parent(lib_liboeffis.full_path())) test('oeffis-pytest', pytest, args: pytest_args, suite: 'python', workdir: meson.current_source_dir(), env: env, ) endif test_scanner_config = configuration_data() test_scanner_config.set('PROTOFILE', protocol_xml_path) # ei-scanner can't be imported as-is in python, so we copy + rename it configure_file(input: scanner_source, output: 'eiscanner.py', copy: true) configure_file(input: 'test_scanner.py', output: '@PLAINNAME@', configuration: test_scanner_config) test('scanner-pytest', pytest, args: pytest_args + ['-k', 'TestScanner'], suite: 'python', workdir: meson.current_build_dir()) endif summary({'valgrind available': valgrind.found(), 'unit tests enabled': true, 'pytest tests enabled': enable_pytest, }, section: 'Test options') libei-1.2.1/test/templates/000077500000000000000000000000001456005336000155565ustar00rootroot00000000000000libei-1.2.1/test/templates/__init__.py000066400000000000000000000072751456005336000177020ustar00rootroot00000000000000# SPDX-License-Identifier: MIT # # # This file is formatted with Python Black from dbusmock import DBusMockObject from typing import Dict, Any, NamedTuple, Optional from itertools import count from gi.repository import GLib # type: ignore import dbus import dbus.service import logging ASVType = Dict[str, Any] logging.basicConfig(format="%(levelname).1s|%(name)s: %(message)s", level=logging.DEBUG) logger = logging.getLogger("templates") class MockParams: """ Helper class for storing template parameters. The Mock object passed into ``load()`` is shared between all templates. This makes it easy to have per-template parameters by calling: >>> params = MockParams.get(mock, MAIN_IFACE) >>> params.version = 1 and later, inside a DBus method: >>> params = MockParams.get(self, MAIN_IFACE) >>> return params.version """ @classmethod def get(cls, mock, interface_name): params = getattr(mock, "params", {}) try: return params[interface_name] except KeyError: c = cls() params[interface_name] = c mock.params = params return c class Response(NamedTuple): response: int results: ASVType class Request: _token_counter = count() def __init__( self, bus_name: dbus.service.BusName, sender: str, options: Optional[ASVType] ): options = options or {} sender_token = sender.removeprefix(":").replace(".", "_") handle_token = options.get("handle_token", next(self._token_counter)) self.sender = sender self.handle = ( f"/org/freedesktop/portal/desktop/request/{sender_token}/{handle_token}" ) self.mock = DBusMockObject( bus_name=bus_name, path=self.handle, interface="org.freedesktop.portal.Request", props={}, ) self.mock.AddMethod("", "Close", "", "", "self.RemoveObject(self.path)") logger.debug(f"Request created at {self.handle}") def respond(self, response: Response, delay: int = 0): def respond(): logger.debug(f"Request.Response on {self.handle}: {response}") self.mock.EmitSignalDetailed( "", "Response", "ua{sv}", [dbus.UInt32(response.response), response.results], details={"destination": self.sender}, ) if delay > 0: GLib.timeout_add(delay, respond) else: respond() class Session: _token_counter = count() def __init__( self, bus_name: dbus.service.BusName, sender: str, options: Optional[ASVType] ): options = options or {} sender_token = sender.removeprefix(":").replace(".", "_") handle_token = options.get("session_handle_token", next(self._token_counter)) self.sender = sender self.handle = ( f"/org/freedesktop/portal/desktop/session/{sender_token}/{handle_token}" ) self.mock = DBusMockObject( bus_name=bus_name, path=self.handle, interface="org.freedesktop.portal.Session", props={}, ) self.mock.AddMethod("", "Close", "", "", "self.RemoveObject(self.path)") logger.debug(f"Session created at {self.handle}") def close(self, details: ASVType, delay: int = 0): def respond(): logger.debug(f"Session.Closed on {self.handle}: {details}") self.mock.EmitSignalDetailed( "", "Closed", "a{sv}", [details], destination=self.sender ) if delay > 0: GLib.timeout_add(delay, respond) else: respond() libei-1.2.1/test/templates/remotedesktop.py000066400000000000000000000230551456005336000210220ustar00rootroot00000000000000# SPDX-License-Identifier: MIT # # This file is formatted with Python Black from templates import Request, Response, Session, MockParams from typing import Dict from itertools import count import dbus import dbus.service import enum import logging logger = logging.getLogger(f"templates.{__name__}") BUS_NAME = "org.freedesktop.portal.Desktop" MAIN_OBJ = "/org/freedesktop/portal/desktop" SYSTEM_BUS = False MAIN_IFACE = "org.freedesktop.portal.RemoteDesktop" _restore_tokens = count() class RDSession(Session): class State(enum.IntEnum): CREATED = enum.auto() SELECTED = enum.auto() STARTED = enum.auto() CONNECTED = enum.auto() @property def state(self): try: return self._session_state except AttributeError: self._session_state = RDSession.State.CREATED return self._session_state def advance_state(self): if self.state != RDSession.State.CONNECTED: self._session_state += 1 def load(mock, parameters): logger.debug(f"loading {MAIN_IFACE} template") params = MockParams.get(mock, MAIN_IFACE) params.delay = 500 params.version = parameters.get("version", 2) params.response = parameters.get("response", 0) params.devices = parameters.get("devices", 0b111) params.sessions: Dict[str, RDSession] = {} mock.AddProperties( MAIN_IFACE, dbus.Dictionary( { "version": dbus.UInt32(params.version), "AvailableDeviceTypes": dbus.UInt32( parameters.get("device-types", params.devices) ), } ), ) @dbus.service.method( MAIN_IFACE, sender_keyword="sender", in_signature="a{sv}", out_signature="o", ) def CreateSession(self, options, sender): try: logger.debug(f"CreateSession: {options}") params = MockParams.get(self, MAIN_IFACE) request = Request(bus_name=self.bus_name, sender=sender, options=options) session = RDSession(bus_name=self.bus_name, sender=sender, options=options) params.sessions[session.handle] = session response = Response(params.response, {"session_handle": session.handle}) request.respond(response, delay=params.delay) return request.handle except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, sender_keyword="sender", in_signature="oa{sv}", out_signature="o", ) def SelectDevices(self, session_handle, options, sender): try: logger.debug(f"SelectDevices: {session_handle} {options}") params = MockParams.get(self, MAIN_IFACE) request = Request(bus_name=self.bus_name, sender=sender, options=options) try: session = params.sessions[session_handle] if session.state != RDSession.State.CREATED: raise dbus.exceptions.DBusException( f"Session in state {session.state}, expected CREATED", name="org.freedesktop.DBus.Error.AccessDenied", ) else: resp = params.response if resp == 0: session.advance_state() except KeyError: raise dbus.exceptions.DBusException( "Invalid session", name="org.freedesktop.DBus.Error.AccessDenied" ) response = Response(resp, {}) request.respond(response, delay=params.delay) return request.handle except Exception as e: logger.critical(e) if isinstance(e, dbus.exceptions.DBusException): raise e @dbus.service.method( MAIN_IFACE, sender_keyword="sender", in_signature="osa{sv}", out_signature="o", ) def Start(self, session_handle, parent_window, options, sender): try: logger.debug(f"Start: {session_handle} {options}") params = MockParams.get(self, MAIN_IFACE) request = Request(bus_name=self.bus_name, sender=sender, options=options) results = { "devices": dbus.UInt32(params.devices), } try: session = params.sessions[session_handle] if session.state != RDSession.State.SELECTED: raise dbus.exceptions.DBusException( f"Session in state {session.state}, expected SELECTED", name="org.freedesktop.DBus.Error.AccessDenied", ) else: resp = params.response if resp == 0: session.advance_state() except KeyError: raise dbus.exceptions.DBusException( "Invalid session", name="org.freedesktop.DBus.Error.AccessDenied" ) response = Response(resp, results) request.respond(response, delay=params.delay) return request.handle except Exception as e: logger.critical(e) if isinstance(e, dbus.exceptions.DBusException): raise e @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}dd", out_signature="", ) def NotifyPointerMotion(self, session_handle, options, dx, dy): try: logger.debug(f"NotifyPointerMotion: {session_handle} {options} {dx} {dy}") except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}udd", out_signature="", ) def NotifyPointerMotionAbsolute(self, session_handle, options, stream, x, y): try: logger.debug( f"NotifyPointerMotionAbsolute: {session_handle} {options} {stream} {x} {y}" ) except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}iu", out_signature="", ) def NotifyPointerButton(self, session_handle, options, button, state): try: logger.debug( f"NotifyPointerButton: {session_handle} {options} {button} {state}" ) except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}dd", out_signature="", ) def NotifyPointerAxis(self, session_handle, options, dx, dy): try: logger.debug(f"NotifyPointerAxis: {session_handle} {options} {dx} {dx}") except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}ui", out_signature="", ) def NotifyPointerAxisDiscrete(self, session_handle, options, axis, steps): try: logger.debug( f"NotifyPointerAxisDiscrete: {session_handle} {options} {axis} {steps}" ) except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}iu", out_signature="", ) def NotifyKeyboardKeycode(self, session_handle, options, keycode, state): try: logger.debug( f"NotifyKeyboardKeycode: {session_handle} {options} {keycode} {state}" ) except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}iu", out_signature="", ) def NotifyKeyboardKeysym(self, session_handle, options, keysym, state): try: logger.debug( f"NotifyKeyboardKeysym: {session_handle} {options} {keysym} {state}" ) except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}uudd", out_signature="", ) def NotifyTouchDown(self, session_handle, options, stream, slot, x, y): try: logger.debug( f"NotifyTouchDown: {session_handle} {options} {stream} {slot} {x} {y}" ) except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}uudd", out_signature="", ) def NotifyTouchMotion(self, session_handle, options, stream, slot, x, y): try: logger.debug( f"NotifyTouchMotion: {session_handle} {options} {stream} {slot} {x} {y}" ) except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}u", out_signature="", ) def NotifyTouchUp(self, session_handle, options, slot): try: logger.debug(f"NotifyTouchMotion: {session_handle} {options} {slot}") except Exception as e: logger.critical(e) @dbus.service.method( MAIN_IFACE, in_signature="oa{sv}", out_signature="h", ) def ConnectToEIS(self, session_handle, options): try: logger.debug(f"ConnectToEIS: {session_handle} {options}") params = MockParams.get(self, MAIN_IFACE) try: session = params.sessions[session_handle] if session.state != RDSession.State.STARTED: logger.error(f"Session in state {session.state}, expected STARTED") raise dbus.exceptions.DBusException( "Session must be started before ConnectToEIS", name="org.freedesktop.DBus.Error.AccesDenied", ) except KeyError: raise dbus.exceptions.DBusException( "Invalid session", name="org.freedesktop.DBus.Error.AccessDenied" ) import socket sockets = socket.socketpair() # Write some random data down so it'll break anything that actually # expects the socket to be a real EIS socket, plus it makes it # easy to check if we connected to the right EIS socket in our tests. sockets[0].send(b"VANILLA") fd = sockets[1] logger.debug(f"ConnectToEIS with fd {fd.fileno()}") return dbus.types.UnixFd(fd) except Exception as e: logger.critical(e) if isinstance(e, dbus.exceptions.DBusException): raise e libei-1.2.1/test/test-ei-device.c000066400000000000000000002420241456005336000165370ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include #include #include "util-io.h" #include "util-munit.h" #include "util-memfile.h" #include "eierpecken.h" #if HAVE_MEMFD_CREATE DEFINE_TRIVIAL_CLEANUP_FUNC(struct memfile *, memfile_unref); #define _cleanup_memfile_ _cleanup_(memfile_unrefp) #endif MUNIT_TEST(test_ei_device_basics) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); _unref_(eis_device) *device = eis_seat_new_device(seat); /* The default value */ munit_assert_int(eis_device_get_type(device), ==, EIS_DEVICE_TYPE_VIRTUAL); eis_device_configure_name(device, "string is freed"); munit_assert_string_equal(eis_device_get_name(device), "string is freed"); /* overwrite before eis_device_add() is possible */ eis_device_configure_name(device, __func__); munit_assert_string_equal(eis_device_get_name(device), __func__); munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER)); munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)); munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)); eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER); munit_assert_true(eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER)); eis_device_configure_capability(device, EIS_DEVICE_CAP_KEYBOARD); munit_assert_true(eis_device_has_capability(device, EIS_DEVICE_CAP_KEYBOARD)); eis_device_add(device); /* device is read-only now */ eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE); munit_assert_false(eis_device_has_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); eis_device_configure_name(device, "nope"); munit_assert_string_equal(eis_device_get_name(device), __func__); } peck_dispatch_ei(peck); /* device creation and getters/setters test */ with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); struct ei_device *device = ei_event_get_device(event); munit_assert_not_null(device); munit_assert_int(ei_device_get_type(device), ==, EI_DEVICE_TYPE_VIRTUAL); munit_assert_string_equal(ei_device_get_name(device), __func__); munit_assert_true(ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)); munit_assert_true(ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)); munit_assert_false(ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_false(ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)); } return MUNIT_OK; } MUNIT_TEST(test_passive_ei_device_type) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); _unref_(eis_device) *phys = eis_seat_new_device(seat); eis_device_configure_type(phys, EIS_DEVICE_TYPE_PHYSICAL); munit_assert_int(eis_device_get_type(phys), ==, EIS_DEVICE_TYPE_PHYSICAL); eis_device_configure_capability(phys, EIS_DEVICE_CAP_POINTER); eis_device_configure_size(phys, 100, 100); eis_device_add(phys); /* noop after add */ eis_device_configure_type(phys, EIS_DEVICE_TYPE_VIRTUAL); _unref_(eis_device) *virt = eis_seat_new_device(seat); eis_device_configure_type(virt, EIS_DEVICE_TYPE_VIRTUAL); eis_device_configure_capability(virt, EIS_DEVICE_CAP_POINTER); with_nonfatal_eis_bug(peck) eis_device_configure_size(virt, 200, 200); /* Has no effect on a virtual device */ munit_assert_int(eis_device_get_type(virt), ==, EIS_DEVICE_TYPE_VIRTUAL); eis_device_add(virt); /* noop after add */ eis_device_configure_type(virt, EIS_DEVICE_TYPE_PHYSICAL); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event_phys = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); struct ei_device *phys = ei_event_get_device(event_phys); munit_assert_int(ei_device_get_type(phys), ==, EI_DEVICE_TYPE_PHYSICAL); munit_assert_int(ei_device_get_width(phys), ==, 100); munit_assert_int(ei_device_get_height(phys), ==, 100); _unref_(ei_event) *event_virt = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); struct ei_device *virt = ei_event_get_device(event_virt); munit_assert_int(ei_device_get_type(virt), ==, EI_DEVICE_TYPE_VIRTUAL); munit_assert_int(ei_device_get_width(virt), ==, 0); munit_assert_int(ei_device_get_height(virt), ==, 0); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_set_name_multiple_devices) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); _unref_(eis_device) *d1 = eis_seat_new_device(seat); eis_device_configure_name(d1, "first device"); eis_device_configure_capability(d1, EIS_DEVICE_CAP_POINTER); eis_device_add(d1); _unref_(eis_device) *d2 = eis_seat_new_device(seat); /* Unnamed */ eis_device_configure_capability(d2, EIS_DEVICE_CAP_POINTER); eis_device_add(d2); _unref_(eis_device) *d3 = eis_seat_new_device(seat); eis_device_configure_name(d3, "third device"); eis_device_configure_capability(d3, EIS_DEVICE_CAP_POINTER); eis_device_add(d3); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *e1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); struct ei_device *d1 = ei_event_get_device(e1); munit_assert_string_equal(ei_device_get_name(d1), "first device"); _unref_(ei_event) *e2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); struct ei_device *d2 = ei_event_get_device(e2); munit_assert_string_equal(ei_device_get_name(d2), "unnamed device"); _unref_(ei_event) *e3 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); struct ei_device *d3 = ei_event_get_device(e3); munit_assert_string_equal(ei_device_get_name(d3), "third device"); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_never_added) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_dispatch_until_stable(peck); /* Unref after remove */ with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); struct eis_device *device = eis_seat_new_device(seat); eis_device_remove(device); eis_device_unref(device); } peck_dispatch_until_stable(peck); /* device was never added, shouldn't show up */ with_client(peck) { peck_assert_no_ei_events(ei); } /* unref before remove. * * This would be invalid client code since you can't expect to have * a ref after unref, but since we know the device is still here, we * can test for the lib being correct. */ with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); struct eis_device *device = eis_seat_new_device(seat); eis_device_unref(device); eis_device_remove(device); } peck_dispatch_until_stable(peck); with_client(peck) { peck_assert_no_ei_events(ei); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_add_remove) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_dispatch_until_stable(peck); _unref_(ei_device) *device = NULL; _unref_(eis_device) *eis_device = NULL; with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); eis_device = eis_seat_new_device(seat); eis_device_configure_name(eis_device, __func__); eis_device_configure_capability(eis_device, EIS_DEVICE_CAP_POINTER); eis_device_add(eis_device); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); device = ei_device_ref(ei_event_get_device(event)); } with_server(peck) { eis_device_remove(eis_device); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); munit_assert_ptr_equal(ei_event_get_device(event), device); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_close) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_DEVICES); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); ei_device_close(device); device = peck_ei_get_default_touch(peck); ei_device_close(device); device = peck_ei_get_default_keyboard(peck); ei_device_close(device); device = peck_ei_get_default_pointer_absolute(peck); ei_device_close(device); peck_assert_no_ei_events(ei); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *e1 = peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED); struct eis_device *d1 = eis_event_get_device(e1); munit_assert_ptr_equal(d1, peck_eis_get_default_pointer(peck)); _unref_(eis_event) *e2 = peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED); struct eis_device *d2 = eis_event_get_device(e2); munit_assert_ptr_equal(d2, peck_eis_get_default_touch(peck)); _unref_(eis_event) *e3 = peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED); struct eis_device *d3 = eis_event_get_device(e3); munit_assert_ptr_equal(d3, peck_eis_get_default_keyboard(peck)); _unref_(eis_event) *e4 = peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED); struct eis_device *d4 = eis_event_get_device(e4); munit_assert_ptr_equal(d4, peck_eis_get_default_pointer_absolute(peck)); /* release in different order */ eis_device_remove(d2); eis_device_remove(d1); eis_device_remove(d4); eis_device_remove(d3); peck_assert_no_eis_events(eis); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *e1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); struct ei_device *d1 = ei_event_get_device(e1); munit_assert_ptr_equal(d1, peck_ei_get_default_touch(peck)); _unref_(ei_event) *e2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); struct ei_device *d2 = ei_event_get_device(e2); munit_assert_ptr_equal(d2, peck_ei_get_default_pointer(peck)); _unref_(ei_event) *e3 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); struct ei_device *d3 = ei_event_get_device(e3); munit_assert_ptr_equal(d3, peck_ei_get_default_pointer_absolute(peck)); _unref_(ei_event) *e4 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); struct ei_device *d4 = ei_event_get_device(e4); munit_assert_ptr_equal(d4, peck_ei_get_default_keyboard(peck)); peck_assert_no_ei_events(ei); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_button_button) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); ei_device_button_button(device, BTN_LEFT, true); ei_device_frame(device, peck_ei_now(peck)); ei_device_button_button(device, BTN_RIGHT, true); ei_device_frame(device, peck_ei_now(peck)); ei_device_button_button(device, BTN_RIGHT, false); ei_device_frame(device, peck_ei_now(peck)); ei_device_button_button(device, BTN_LEFT, false); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *ld = peck_eis_next_event(eis, EIS_EVENT_BUTTON_BUTTON); munit_assert_int(eis_event_button_get_button(ld), ==, BTN_LEFT); munit_assert_true(eis_event_button_get_is_press(ld)); _unref_(eis_event) *rd = peck_eis_next_event(eis, EIS_EVENT_BUTTON_BUTTON); munit_assert_int(eis_event_button_get_button(rd), ==, BTN_RIGHT); munit_assert_true(eis_event_button_get_is_press(rd)); _unref_(eis_event) *ru = peck_eis_next_event(eis, EIS_EVENT_BUTTON_BUTTON); munit_assert_int(eis_event_button_get_button(ru), ==, BTN_RIGHT); munit_assert_false(eis_event_button_get_is_press(ru)); _unref_(eis_event) *lu = peck_eis_next_event(eis, EIS_EVENT_BUTTON_BUTTON); munit_assert_int(eis_event_button_get_button(lu), ==, BTN_LEFT); munit_assert_false(eis_event_button_get_is_press(lu)); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_keyboard_key) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_keyboard(peck); ei_device_keyboard_key(device, KEY_Q, true); ei_device_frame(device, peck_ei_now(peck)); ei_device_keyboard_key(device, KEY_Q, false); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *press = peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY); munit_assert_int(eis_event_keyboard_get_key(press), ==, KEY_Q); munit_assert_true(eis_event_keyboard_get_key_is_press(press)); _unref_(eis_event) *release = peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY); munit_assert_int(eis_event_keyboard_get_key(release), ==, KEY_Q); munit_assert_false(eis_event_keyboard_get_key_is_press(release)); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_pointer_rel) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); ei_device_pointer_motion(device, 1, 2); ei_device_frame(device, peck_ei_now(peck)); ei_device_pointer_motion(device, 0.3, 1.4); ei_device_frame(device, peck_ei_now(peck)); ei_device_pointer_motion(device, 100, 200); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *first = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION); munit_assert_double_equal(eis_event_pointer_get_dx(first), 1.0, 2 /* precision */); munit_assert_double_equal(eis_event_pointer_get_dy(first), 2.0, 2 /* precision */); _unref_(eis_event) *second = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION); munit_assert_double_equal(eis_event_pointer_get_dx(second), 0.3, 2 /* precision */); munit_assert_double_equal(eis_event_pointer_get_dy(second), 1.4, 2 /* precision */); _unref_(eis_event) *third = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION); munit_assert_double_equal(eis_event_pointer_get_dx(third), 100, 2 /* precision */); munit_assert_double_equal(eis_event_pointer_get_dy(third), 200, 2 /* precision */); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_regions) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); _unref_(eis_device) *device = eis_seat_new_device(seat); eis_device_configure_name(device, __func__); eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE); /* nothing cares about the actual values, so we're just * checking for correct passthrough here */ _unref_(eis_region) *r1 = eis_device_new_region(device); eis_region_set_size(r1, 100, 200); eis_region_set_offset(r1, 300, 400); eis_region_set_mapping_id(r1, "oo oo eye dee"); /* no scale, default to 1.0 */ eis_region_add(r1); _unref_(eis_region) *r2 = eis_device_new_region(device); eis_region_set_size(r2, 500, 600); eis_region_set_offset(r2, 700, 800); eis_region_set_physical_scale(r2, 3.9); eis_region_add(r2); _unref_(eis_region) *r3 = eis_device_new_region(device); eis_region_set_size(r3, 900, 1000); eis_region_set_offset(r3, 1100, 1200); eis_region_set_physical_scale(r3, 0.3); eis_region_add(r3); /* Add the same region twice, should be ignored */ eis_region_add(r3); eis_region_add(r3); eis_region_add(r3); eis_device_add(device); } peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer_absolute(peck); struct ei_region *r, *r2; r = ei_device_get_region(device, 0); munit_assert_int(ei_region_get_width(r), ==, 100); munit_assert_int(ei_region_get_height(r), ==, 200); munit_assert_int(ei_region_get_x(r), ==, 300); munit_assert_int(ei_region_get_y(r), ==, 400); munit_assert_double_equal(ei_region_get_physical_scale(r), 1.0, 2 /* precision */); munit_assert_string_equal(ei_region_get_mapping_id(r), "oo oo eye dee"); r2 = ei_device_get_region_at(device, 300, 400); munit_assert_ptr_equal(r, r2); r2 = ei_device_get_region_at(device, 350, 450); munit_assert_ptr_equal(r, r2); r = ei_device_get_region(device, 1); munit_assert_int(ei_region_get_width(r), ==, 500); munit_assert_int(ei_region_get_height(r), ==, 600); munit_assert_int(ei_region_get_x(r), ==, 700); munit_assert_int(ei_region_get_y(r), ==, 800); munit_assert_double_equal(ei_region_get_physical_scale(r), 3.9, 2 /* precision */); munit_assert_null(ei_region_get_mapping_id(r)); r2 = ei_device_get_region_at(device, 750, 850); munit_assert_ptr_equal(r, r2); r = ei_device_get_region(device, 2); munit_assert_int(ei_region_get_width(r), ==, 900); munit_assert_int(ei_region_get_height(r), ==, 1000); munit_assert_int(ei_region_get_x(r), ==, 1100); munit_assert_int(ei_region_get_y(r), ==, 1200); munit_assert_double_equal(ei_region_get_physical_scale(r), 0.3, 2 /* precision */); munit_assert_null(ei_region_get_mapping_id(r)); r2 = ei_device_get_region_at(device, 1999, 2199); munit_assert_ptr_equal(r, r2); munit_assert_ptr_null(ei_device_get_region_at(device, 2000, 2200)); /* at the edge */ munit_assert_ptr_null(ei_device_get_region(device, 3)); munit_assert_ptr_null(ei_device_get_region_at(device, 0, 100)); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_regions_ref_unref) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); _unref_(eis_device) *device = eis_seat_new_device(seat); eis_device_configure_name(device, __func__); eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE); /* region never added */ struct eis_region *r1 = eis_device_new_region(device); eis_region_unref(r1); eis_device_add(device); } peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer_absolute(peck); munit_assert_ptr_null(ei_device_get_region(device, 0)); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_regions_late_add) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); _unref_(eis_device) *device = eis_seat_new_device(seat); eis_device_configure_name(device, __func__); eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER_ABSOLUTE); eis_device_add(device); /* region added after device_add -> ignored */ _unref_(eis_region) *r1 = eis_device_new_region(device); eis_region_set_size(r1, 100, 200); /* Bug: eis_region_add: device already (dis)connected */ with_nonfatal_eis_bug(peck) eis_region_add(r1); } peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer_absolute(peck); munit_assert_ptr_null(ei_device_get_region(device, 0)); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_pointer_abs) { _unref_(peck) *peck = peck_new(); struct ei_device *device = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { device = peck_ei_get_default_pointer_absolute(peck); for (int i = 0; i < 10; i++) { ei_device_pointer_motion_absolute(device, 1 * i , 2 + i); ei_device_frame(device, peck_ei_now(peck)); } } peck_dispatch_until_stable(peck); with_server(peck) { for (int i = 0; i < 10; i++) { _unref_(eis_event) *e = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION_ABSOLUTE); munit_assert_double_equal(eis_event_pointer_get_absolute_x(e), 1.0 * i, 2 /* precision */); munit_assert_double_equal(eis_event_pointer_get_absolute_y(e), 2.0 + i, 2 /* precision */); } peck_assert_no_eis_events(eis); } with_client(peck) { /* We know our default device has one region */ struct ei_region *r = ei_device_get_region(device, 0); uint32_t maxx = ei_region_get_x(r) + ei_region_get_width(r); uint32_t maxy = ei_region_get_y(r) + ei_region_get_height(r); /* outside of pointer range, expect to be discarded */ ei_device_pointer_motion_absolute(device, maxx + 1, maxy/2); ei_device_frame(device, peck_ei_now(peck)); ei_device_pointer_motion_absolute(device, maxx/2 , maxy + 1); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { peck_assert_no_eis_events(eis); /* Don't auto-handle the DEVICE_CLOSED event */ peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); } with_client(peck) { ei_device_close(device); /* absmotion after remove must not trigger an event */ with_nonfatal_ei_bug(peck) ei_device_pointer_motion_absolute(device, 100, 200); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *stop = peck_eis_next_event(eis, EIS_EVENT_DEVICE_STOP_EMULATING); _unref_(eis_event) *closed = peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED); peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_scroll) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); ei_device_scroll_delta(device, 1.1, 2.2); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_discrete(device, 3, 4); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *first = peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA); munit_assert_double_equal(eis_event_scroll_get_dx(first), 1.1, 2 /* precision */); munit_assert_double_equal(eis_event_scroll_get_dy(first), 2.2, 2 /* precision */); _unref_(eis_event) *second = peck_eis_next_event(eis, EIS_EVENT_SCROLL_DISCRETE); munit_assert_int(eis_event_scroll_get_discrete_dx(second), ==, 3); munit_assert_int(eis_event_scroll_get_discrete_dy(second), ==, 4); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_scroll_stop) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); ei_device_scroll_delta(device, 1.1, 2.2); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_stop(device, true, false); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_stop(device, false, true); ei_device_frame(device, peck_ei_now(peck)); /* This should not generate an event */ ei_device_scroll_stop(device, true, true); ei_device_frame(device, peck_ei_now(peck)); /* But scrolling again will re-enable stopping */ ei_device_scroll_delta(device, 3.3, 4.4); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_stop(device, true, true); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_delta(device, 3.3, 4.4); /* This one is a client bug and shouldn't trigger an event */ ei_device_scroll_stop(device, false, false); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *scroll = peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA); _unref_(eis_event) *first = peck_eis_next_event(eis, EIS_EVENT_SCROLL_STOP); munit_assert(eis_event_scroll_get_stop_x(first)); munit_assert(!eis_event_scroll_get_stop_y(first)); _unref_(eis_event) *second = peck_eis_next_event(eis, EIS_EVENT_SCROLL_STOP); munit_assert(!eis_event_scroll_get_stop_x(second)); munit_assert(eis_event_scroll_get_stop_y(second)); /* third one doesn't trigger an event */ _unref_(eis_event) *again = peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA); _unref_(eis_event) *fourth = peck_eis_next_event(eis, EIS_EVENT_SCROLL_STOP); munit_assert(eis_event_scroll_get_stop_x(fourth)); munit_assert(eis_event_scroll_get_stop_y(fourth)); _unref_(eis_event) *again_again = peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA); peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_scroll_cancel) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); ei_device_scroll_delta(device, 1.1, 2.2); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_cancel(device, true, false); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_cancel(device, false, true); ei_device_frame(device, peck_ei_now(peck)); /* This should not generate an event */ ei_device_scroll_cancel(device, true, true); ei_device_frame(device, peck_ei_now(peck)); /* But scrolling again will re-enable stopping */ ei_device_scroll_delta(device, 3.3, 4.4); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_cancel(device, true, true); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_delta(device, 3.3, 4.4); /* This one is a client bug and shouldn't trigger an event */ ei_device_scroll_cancel(device, false, false); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *scroll = peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA); _unref_(eis_event) *first = peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL); munit_assert(eis_event_scroll_get_stop_x(first)); munit_assert(!eis_event_scroll_get_stop_y(first)); _unref_(eis_event) *second = peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL); munit_assert(!eis_event_scroll_get_stop_x(second)); munit_assert(eis_event_scroll_get_stop_y(second)); /* third one doesn't trigger an event */ _unref_(eis_event) *again = peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA); _unref_(eis_event) *fourth = peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL); munit_assert(eis_event_scroll_get_stop_x(fourth)); munit_assert(eis_event_scroll_get_stop_y(fourth)); _unref_(eis_event) *again_again = peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA); peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_scroll_stop_cancel) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); /* cancel after stop is fine, stop after cancel is ignored */ with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); ei_device_scroll_delta(device, 1.1, 2.2); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_stop(device, true, false); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_cancel(device, true, false); ei_device_frame(device, peck_ei_now(peck)); ei_device_scroll_cancel(device, false, true); ei_device_frame(device, peck_ei_now(peck)); /* This should not generate an event */ ei_device_scroll_stop(device, true, true); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *scroll = peck_eis_next_event(eis, EIS_EVENT_SCROLL_DELTA); _unref_(eis_event) *stop = peck_eis_next_event(eis, EIS_EVENT_SCROLL_STOP); munit_assert(eis_event_scroll_get_stop_x(stop)); munit_assert(!eis_event_scroll_get_stop_y(stop)); _unref_(eis_event) *first = peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL); munit_assert(eis_event_scroll_get_stop_x(first)); munit_assert(!eis_event_scroll_get_stop_y(first)); _unref_(eis_event) *second = peck_eis_next_event(eis, EIS_EVENT_SCROLL_CANCEL); munit_assert(!eis_event_scroll_get_stop_x(second)); munit_assert(eis_event_scroll_get_stop_y(second)); /* third one doesn't trigger an event */ peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_touch) { _unref_(peck) *peck = peck_new(); struct ei_device *device = NULL; uint32_t maxx = 0, maxy = 0; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { device = peck_ei_get_default_touch(peck); /* We know our default device has one region */ struct ei_region *r = ei_device_get_region(device, 0); maxx = ei_region_get_x(r) + ei_region_get_width(r); maxy = ei_region_get_y(r) + ei_region_get_height(r); _unref_(ei_touch) *t = ei_device_touch_new(device); ei_touch_down(t, 1, 2); ei_device_frame(device, peck_ei_now(peck)); ei_touch_motion(t, 200, 500); ei_device_frame(device, peck_ei_now(peck)); ei_touch_up(t); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *down = peck_eis_touch_down(eis, 1, 2); uint32_t tid = eis_event_touch_get_id(down); _unref_(eis_event) *motion = peck_eis_touch_motion(eis, 200, 500); munit_assert_uint32(eis_event_touch_get_id(motion), ==, tid); _unref_(eis_event) *up = peck_eis_touch_up(eis); munit_assert_uint32(eis_event_touch_get_id(up), ==, tid); peck_assert_no_eis_events(eis); } with_client(peck) { _unref_(ei_touch) *t = ei_device_touch_new(device); /* outside clip range, expect touch to be dropped */ with_nonfatal_ei_bug(peck) { ei_touch_down(t, maxx + 1, maxy/2); ei_device_frame(device, peck_ei_now(peck)); /* ignored because the touch down was out of range */ ei_touch_motion(t, maxx + 1, maxy/3); ei_device_frame(device, peck_ei_now(peck)); /* ignored because the touch down was out of range */ ei_touch_up(t); } ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_touch) *t = ei_device_touch_new(device); /* outside clip range, expect touch to be dropped */ with_nonfatal_ei_bug(peck) { ei_touch_down(t, maxx/2, maxy + 1); ei_device_frame(device, peck_ei_now(peck)); /* ignored because the touch down was out of range */ ei_touch_motion(t, maxx/3, maxy + 1); ei_device_frame(device, peck_ei_now(peck)); /* ignored because the touch down was out of range */ ei_touch_up(t); } ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { peck_assert_no_eis_events(eis); } with_client(peck) { _unref_(ei_touch) *t = ei_device_touch_new(device); ei_touch_down(t, 100, 200); ei_device_frame(device, peck_ei_now(peck)); /* outside allowed range, generates a touch up */ with_nonfatal_ei_bug(peck) ei_touch_motion(t, maxx + 1, 200); ei_device_frame(device, peck_ei_now(peck)); /* touch is already considered up */ with_nonfatal_ei_bug(peck) ei_touch_up(t); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200); _unref_(eis_event) *up = peck_eis_touch_up(eis); peck_assert_no_eis_events(eis); } with_client(peck) { _unref_(ei_touch) *t = ei_device_touch_new(device); ei_touch_down(t, 100, 100); ei_device_frame(device, peck_ei_now(peck)); /* client forgets to touch up, touch_unref takes care of it */ } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 100); _unref_(eis_event) *up = peck_eis_touch_up(eis); peck_assert_no_eis_events(eis); } with_client(peck) { /* touch only allocated, not actually set down */ _unref_(ei_touch) *t1 = ei_device_touch_new(device); /* touch never set down */ _unref_(ei_touch) *t2 = ei_device_touch_new(device); with_nonfatal_ei_bug(peck) ei_touch_up(t2); ei_device_frame(device, peck_ei_now(peck)); /* touch never set down */ _unref_(ei_touch) *t3 = ei_device_touch_new(device); with_nonfatal_ei_bug(peck) ei_touch_motion(t3, 100, 200); ei_device_frame(device, peck_ei_now(peck)); with_nonfatal_ei_bug(peck) ei_touch_up(t3); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { peck_assert_no_eis_events(eis); } with_client(peck) { /* touch re-used */ _unref_(ei_touch) *t = ei_device_touch_new(device); ei_touch_down(t, 100, 200); ei_device_frame(device, peck_ei_now(peck)); ei_touch_up(t); ei_device_frame(device, peck_ei_now(peck)); with_nonfatal_ei_bug(peck) ei_touch_down(t, 200, 300); ei_device_frame(device, peck_ei_now(peck)); with_nonfatal_ei_bug(peck) ei_touch_motion(t, 300, 400); ei_device_frame(device, peck_ei_now(peck)); with_nonfatal_ei_bug(peck) ei_touch_up(t); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200); _unref_(eis_event) *up = peck_eis_touch_up(eis); peck_assert_no_eis_events(eis); } with_client(peck) { /* double-down, double-up */ _unref_(ei_touch) *t = ei_device_touch_new(device); ei_touch_down(t, 100, 200); ei_device_frame(device, peck_ei_now(peck)); with_nonfatal_ei_bug(peck) ei_touch_down(t, 200, 300); /* ignored */ ei_device_frame(device, peck_ei_now(peck)); ei_touch_motion(t, 300, 400); ei_device_frame(device, peck_ei_now(peck)); ei_touch_up(t); ei_device_frame(device, peck_ei_now(peck)); with_nonfatal_ei_bug(peck) ei_touch_up(t); /* ignored */ ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *down = peck_eis_touch_down(eis, 100, 200); _unref_(eis_event) *motion = peck_eis_touch_motion(eis, 300, 400); _unref_(eis_event) *up = peck_eis_touch_up(eis); peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_multitouch) { _unref_(peck) *peck = peck_new(); struct ei_device *device = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); peck_dispatch_until_stable(peck); with_client(peck) { device = peck_ei_get_default_touch(peck); _unref_(ei_touch) *t1 = ei_device_touch_new(device); _unref_(ei_touch) *t2 = ei_device_touch_new(device); ei_touch_down(t1, 1, 2); ei_device_frame(device, peck_ei_now(peck)); ei_touch_motion(t1, 2, 3); ei_device_frame(device, peck_ei_now(peck)); ei_touch_down(t2, 3, 4); ei_device_frame(device, peck_ei_now(peck)); ei_touch_motion(t2, 4, 5); ei_device_frame(device, peck_ei_now(peck)); ei_touch_up(t2); ei_touch_up(t1); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *down1 = peck_eis_touch_down(eis, 1, 2); uint32_t tid1 = eis_event_touch_get_id(down1); _unref_(eis_event) *motion1 = peck_eis_touch_motion(eis, 2, 3); munit_assert_uint32(eis_event_touch_get_id(motion1), ==, tid1); _unref_(eis_event) *down2 = peck_eis_touch_down(eis, 3, 4); uint32_t tid2 = eis_event_touch_get_id(down2); munit_assert_uint32(tid2, !=, tid1); _unref_(eis_event) *motion2 = peck_eis_touch_motion(eis, 4, 5); munit_assert_uint32(eis_event_touch_get_id(motion2), ==, tid2); _unref_(eis_event) *up2 = peck_eis_touch_up(eis); munit_assert_uint32(eis_event_touch_get_id(up2), ==, tid2); _unref_(eis_event) *up1 = peck_eis_touch_up(eis); munit_assert_uint32(eis_event_touch_get_id(up1), ==, tid1); } return MUNIT_OK; } #if HAVE_MEMFD_CREATE MUNIT_TEST(test_ei_keymap_invalid) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); _unref_(eis_device) *device = eis_seat_new_device(seat); const char data[5] = {1, 2, 3, 4, 5}; _unref_(memfile) *fd = memfile_new(data, sizeof(data)); munit_assert_ptr_null(eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB + 1, memfile_get_fd(fd), memfile_get_size(fd))); munit_assert_ptr_null(eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB - 1, memfile_get_fd(fd), memfile_get_size(fd))); munit_assert_ptr_null(eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, -1, memfile_get_size(fd))); munit_assert_ptr_null(eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, memfile_get_fd(fd), 0)); /* Valid keymap, valgrind checks only */ _unref_(eis_keymap) *unused = eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, memfile_get_fd(fd), memfile_get_size(fd)); munit_assert_ptr_not_null(unused); } return MUNIT_OK; } MUNIT_TEST(test_ei_keymap_set) { _unref_(peck) *peck = peck_new(); const char data[5] = {1, 2, 3, 4, 5}; _unref_(memfile) *fd1 = memfile_new(data, sizeof(data)); _unref_(eis_keymap) *keymap = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); _unref_(eis_device) *device = eis_seat_new_device(seat); eis_device_configure_name(device, __func__); eis_device_configure_capability(device, EIS_DEVICE_CAP_KEYBOARD); keymap = eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, memfile_get_fd(fd1), memfile_get_size(fd1)); eis_keymap_add(keymap); munit_assert_ptr_equal(eis_device_keyboard_get_keymap(device), keymap); /* Not possible to overwrite a keymap on a device once it's set */ _unref_(memfile) *fd2 = memfile_new(data, sizeof(data)); _unref_(eis_keymap) *overwrite = eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, memfile_get_fd(fd2), memfile_get_size(fd2)); with_nonfatal_eis_bug(peck) eis_keymap_add(overwrite); munit_assert_ptr_equal(eis_device_keyboard_get_keymap(device), keymap); eis_device_add(device); /* Still impossible to overwrite after add */ _unref_(eis_keymap) *ignored = eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, memfile_get_fd(fd2), memfile_get_size(fd2)); with_nonfatal_eis_bug(peck) eis_keymap_add(ignored); munit_assert_ptr_equal(eis_device_keyboard_get_keymap(device), keymap); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); struct ei_device *ei_device = ei_event_get_device(event); struct ei_keymap *ei_keymap = ei_device_keyboard_get_keymap(ei_device); int fd = ei_keymap_get_fd(ei_keymap); munit_assert_int(fd, !=, -1); munit_assert_uint(ei_keymap_get_size(ei_keymap), ==, memfile_get_size(fd1)); munit_assert_uint(ei_keymap_get_type(ei_keymap), ==, EI_KEYMAP_TYPE_XKB); /* FIXME: read and compare buffer */ ei_device_close(ei_device); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *event = peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED); struct eis_device *d = eis_event_get_device(event); /* Rejecting a device does not unset the keymap because * you're not supposed to do anything with the device anyway */ struct eis_keymap *km = eis_device_keyboard_get_keymap(d); munit_assert_ptr_equal(keymap, km); } return MUNIT_OK; } #endif MUNIT_TEST(test_ei_keyboard_modifiers) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_device *kbd = peck_eis_get_default_keyboard(peck); eis_device_keyboard_send_xkb_modifiers(kbd, 0x1, 0x2, 0x4, 0x8); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_KEYBOARD_MODIFIERS); uint32_t depressed, locked, latched, group; depressed = ei_event_keyboard_get_xkb_mods_depressed(event); latched = ei_event_keyboard_get_xkb_mods_latched(event); locked = ei_event_keyboard_get_xkb_mods_locked(event); group = ei_event_keyboard_get_xkb_group(event); munit_assert_uint(depressed, ==, 0x1); munit_assert_uint(latched, ==, 0x2); munit_assert_uint(locked, ==, 0x4); munit_assert_uint(group, ==, 0x8); } return MUNIT_OK; } MUNIT_TEST(test_ei_frame_timestamp) { _unref_(peck) *peck = peck_new(); uint64_t ts1 = 0, ts2 = 0; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { ts1 = peck_ei_now(peck); struct ei_device *device = peck_ei_get_default_keyboard(peck); ei_device_keyboard_key(device, KEY_Q, true); ei_device_frame(device, ts1); ts2 = peck_ei_now(peck); ei_device_keyboard_key(device, KEY_Q, false); ei_device_frame(device, ts2); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *kbd1 = peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY); _unref_(eis_event) *frame1 = peck_eis_next_event(eis, EIS_EVENT_FRAME); _unref_(eis_event) *kbd2 = peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY); _unref_(eis_event) *frame2 = peck_eis_next_event(eis, EIS_EVENT_FRAME); uint64_t timestamp = eis_event_get_time(frame1); munit_assert_uint64(timestamp, ==, ts1); munit_assert_uint64(timestamp, ==, eis_event_get_time(kbd1)); timestamp = eis_event_get_time(frame2); munit_assert_uint64(timestamp, ==, ts2); munit_assert_uint64(timestamp, ==, eis_event_get_time(kbd2)); peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_no_empty_frames) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_keyboard(peck); ei_device_frame(device, peck_ei_now(peck)); /* Expect to be filtered */ ei_device_keyboard_key(device, KEY_Q, true); ei_device_frame(device, peck_ei_now(peck)); ei_device_frame(device, peck_ei_now(peck)); /* Expect to be filtered */ } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *kbd = peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY); _unref_(eis_event) *frame = peck_eis_next_event(eis, EIS_EVENT_FRAME); peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_buffered_frame) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *kbd = peck_ei_get_default_keyboard(peck); ei_device_keyboard_key(kbd, KEY_Q, true); struct ei_device *pointer = peck_ei_get_default_pointer(peck); ei_device_pointer_motion(pointer, 1.0, 2.0); } peck_dispatch_until_stable(peck); /* No event visible yet, frame hasn't been sent */ with_server(peck) { peck_assert_no_eis_events(eis); } /* Flush in inverse order to original events */ with_client(peck) { struct ei_device *pointer = peck_ei_get_default_pointer(peck); ei_device_frame(pointer, peck_ei_now(peck)); struct ei_device *keyboard = peck_ei_get_default_keyboard(peck); ei_device_frame(keyboard, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *rel = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION); _unref_(eis_event) *frame1 = peck_eis_next_event(eis, EIS_EVENT_FRAME); _unref_(eis_event) *kbd = peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY); _unref_(eis_event) *frame2 = peck_eis_next_event(eis, EIS_EVENT_FRAME); peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_flush_frame) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_keyboard(peck); ei_device_keyboard_key(device, KEY_Q, true); /* Missing call to ei_device_frame() */ with_nonfatal_ei_bug(peck) ei_device_stop_emulating(device); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *kbd = peck_eis_next_event(eis, EIS_EVENT_KEYBOARD_KEY); _unref_(eis_event) *frame = peck_eis_next_event(eis, EIS_EVENT_FRAME); _unref_(eis_event) *stop = peck_eis_next_event(eis, EIS_EVENT_DEVICE_STOP_EMULATING); peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_device_remove_no_stop_emulating_event) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_keyboard(peck); ei_device_keyboard_key(device, KEY_Q, true); ei_device_frame(device, peck_ei_now(peck)); ei_device_keyboard_key(device, KEY_Q, false); ei_device_frame(device, peck_ei_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { struct eis_device *device = peck_eis_get_default_keyboard(peck); eis_device_remove(device); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *removed = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); peck_assert_no_ei_events(ei); } return MUNIT_OK; } MUNIT_TEST(test_passive_ei_device_start_stop_emulating) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_RESUMED); peck_dispatch_until_stable(peck); uint32_t sequence = 123; with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_start_emulating(device, sequence); eis_device_stop_emulating(device); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); _unref_(ei_event) *stop = peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING); } return MUNIT_OK; } MUNIT_TEST(test_passive_ei_device_stop_emulating_when_removing) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_RESUMED); peck_dispatch_until_stable(peck); uint32_t sequence = 123; with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_start_emulating(device, sequence); eis_device_remove(device); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); _unref_(ei_event) *stop = peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING); _unref_(ei_event) *close = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); } return MUNIT_OK; } /* Same as test_ei_device_button_button() but for a passive context */ MUNIT_TEST(test_passive_ei_device_button_button) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 123; with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_start_emulating(device, sequence); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); } with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_button_button(device, BTN_LEFT, true); eis_device_frame(device, peck_eis_now(peck)); eis_device_button_button(device, BTN_RIGHT, true); eis_device_frame(device, peck_eis_now(peck)); eis_device_button_button(device, BTN_RIGHT, false); eis_device_frame(device, peck_eis_now(peck)); eis_device_button_button(device, BTN_LEFT, false); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *ld = peck_ei_next_event(ei, EI_EVENT_BUTTON_BUTTON); munit_assert_int(ei_event_button_get_button(ld), ==, BTN_LEFT); munit_assert_true(ei_event_button_get_is_press(ld)); _unref_(ei_event) *rd = peck_ei_next_event(ei, EI_EVENT_BUTTON_BUTTON); munit_assert_int(ei_event_button_get_button(rd), ==, BTN_RIGHT); munit_assert_true(ei_event_button_get_is_press(rd)); _unref_(ei_event) *ru = peck_ei_next_event(ei, EI_EVENT_BUTTON_BUTTON); munit_assert_int(ei_event_button_get_button(ru), ==, BTN_RIGHT); munit_assert_false(ei_event_button_get_is_press(ru)); _unref_(ei_event) *lu = peck_ei_next_event(ei, EI_EVENT_BUTTON_BUTTON); munit_assert_int(ei_event_button_get_button(lu), ==, BTN_LEFT); munit_assert_false(ei_event_button_get_is_press(lu)); } return MUNIT_OK; } /* Same as test_passive_ei_device_pointer_rel() but for a passive context */ MUNIT_TEST(test_passive_ei_device_keyboard_key) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 123; with_server(peck) { struct eis_device *device = peck_eis_get_default_keyboard(peck); eis_device_start_emulating(device, sequence); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); } with_server(peck) { struct eis_device *device = peck_eis_get_default_keyboard(peck); eis_device_keyboard_key(device, KEY_Q, true); eis_device_frame(device, peck_eis_now(peck)); eis_device_keyboard_key(device, KEY_Q, false); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *press = peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY); munit_assert_int(ei_event_keyboard_get_key(press), ==, KEY_Q); munit_assert_true(ei_event_keyboard_get_key_is_press(press)); _unref_(ei_event) *release = peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY); munit_assert_int(ei_event_keyboard_get_key(release), ==, KEY_Q); munit_assert_false(ei_event_keyboard_get_key_is_press(release)); } return MUNIT_OK; } /* Same as test_ei_device_pointer_rel() but for a passive context */ MUNIT_TEST(test_passive_ei_device_pointer_rel) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 1234; with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_start_emulating(device, sequence); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); } with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_pointer_motion(device, 1, 2); eis_device_frame(device, peck_eis_now(peck)); eis_device_pointer_motion(device, 0.3, 1.4); eis_device_frame(device, peck_eis_now(peck)); eis_device_pointer_motion(device, 100, 200); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *first = peck_ei_next_event(ei, EI_EVENT_POINTER_MOTION); munit_assert_double_equal(ei_event_pointer_get_dx(first), 1.0, 2 /* precision */); munit_assert_double_equal(ei_event_pointer_get_dy(first), 2.0, 2 /* precision */); _unref_(ei_event) *second = peck_ei_next_event(ei, EI_EVENT_POINTER_MOTION); munit_assert_double_equal(ei_event_pointer_get_dx(second), 0.3, 2 /* precision */); munit_assert_double_equal(ei_event_pointer_get_dy(second), 1.4, 2 /* precision */); _unref_(ei_event) *third = peck_ei_next_event(ei, EI_EVENT_POINTER_MOTION); munit_assert_double_equal(ei_event_pointer_get_dx(third), 100, 2 /* precision */); munit_assert_double_equal(ei_event_pointer_get_dy(third), 200, 2 /* precision */); } return MUNIT_OK; } /* Same as test_ei_device_pointer_abs() but for a passive context */ MUNIT_TEST(test_passive_ei_device_pointer_abs) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); struct eis_device *device = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER_ABSOLUTE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 123; with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer_absolute(peck); eis_device_start_emulating(device, sequence); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); } with_server(peck) { device = peck_eis_get_default_pointer_absolute(peck); for (int i = 0; i < 10; i++) { eis_device_pointer_motion_absolute(device, 1 * i , 2 + i); eis_device_frame(device, peck_eis_now(peck)); } } peck_dispatch_until_stable(peck); with_client(peck) { for (int i = 0; i < 10; i++) { _unref_(ei_event) *e = peck_ei_next_event(ei, EI_EVENT_POINTER_MOTION_ABSOLUTE); munit_assert_double_equal(ei_event_pointer_get_absolute_x(e), 1.0 * i, 2 /* precision */); munit_assert_double_equal(ei_event_pointer_get_absolute_y(e), 2.0 + i, 2 /* precision */); } peck_assert_no_ei_events(ei); } with_server(peck) { /* We know our default device has one region */ struct eis_region *r = eis_device_get_region(device, 0); uint32_t maxx = eis_region_get_x(r) + eis_region_get_width(r); uint32_t maxy = eis_region_get_y(r) + eis_region_get_height(r); /* outside of pointer range, expect to be discarded */ eis_device_pointer_motion_absolute(device, maxx + 1, maxy/2.0); eis_device_frame(device, peck_eis_now(peck)); eis_device_pointer_motion_absolute(device, maxx/2.0 , maxy + 1); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { peck_assert_no_ei_events(ei); } with_server(peck) { eis_device_remove(device); /* absmotion after remove must not trigger an event */ eis_device_pointer_motion_absolute(device, 100, 200); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *stop = peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING); _unref_(ei_event) *closed = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); peck_assert_no_ei_events(ei); } return MUNIT_OK; } /* same as test_ei_device_scroll_delta but for a passive context */ MUNIT_TEST(test_passive_ei_device_scroll_delta) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 123; with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_start_emulating(device, sequence); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); } with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_scroll_delta(device, 1.1, 2.2); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_discrete(device, 3, 4); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *first = peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA); munit_assert_double_equal(ei_event_scroll_get_dx(first), 1.1, 2 /* precision */); munit_assert_double_equal(ei_event_scroll_get_dy(first), 2.2, 2 /* precision */); _unref_(ei_event) *second = peck_ei_next_event(ei, EI_EVENT_SCROLL_DISCRETE); munit_assert_int(ei_event_scroll_get_discrete_dx(second), ==, 3); munit_assert_int(ei_event_scroll_get_discrete_dy(second), ==, 4); } return MUNIT_OK; } /* same as test_ei_device_scroll_stop but for a passive context */ MUNIT_TEST(test_passive_ei_device_scroll_stop) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 456; with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_start_emulating(device, sequence); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); } with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_scroll_delta(device, 1.1, 2.2); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_stop(device, true, false); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_stop(device, false, true); eis_device_frame(device, peck_eis_now(peck)); /* This should not generate an event */ eis_device_scroll_stop(device, true, true); eis_device_frame(device, peck_eis_now(peck)); /* But scrolling again will re-enable stopping */ eis_device_scroll_delta(device, 3.3, 4.4); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_stop(device, true, true); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_delta(device, 3.3, 4.4); /* This one is a client bug and shouldn't trigger an event */ eis_device_scroll_stop(device, false, false); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *scroll = peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA); _unref_(ei_event) *first = peck_ei_next_event(ei, EI_EVENT_SCROLL_STOP); munit_assert(ei_event_scroll_get_stop_x(first)); munit_assert(!ei_event_scroll_get_stop_y(first)); _unref_(ei_event) *second = peck_ei_next_event(ei, EI_EVENT_SCROLL_STOP); munit_assert(!ei_event_scroll_get_stop_x(second)); munit_assert(ei_event_scroll_get_stop_y(second)); /* third one doesn't trigger an event */ _unref_(ei_event) *again = peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA); _unref_(ei_event) *fourth = peck_ei_next_event(ei, EI_EVENT_SCROLL_STOP); munit_assert(ei_event_scroll_get_stop_x(fourth)); munit_assert(ei_event_scroll_get_stop_y(fourth)); _unref_(ei_event) *again_again = peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA); peck_assert_no_ei_events(ei); } return MUNIT_OK; } /* same as test_ei_device_scroll_cancel but for a passive context */ MUNIT_TEST(test_passive_ei_device_scroll_cancel) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 546; with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_start_emulating(device, sequence); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); } with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_scroll_delta(device, 1.1, 2.2); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_cancel(device, true, false); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_cancel(device, false, true); eis_device_frame(device, peck_eis_now(peck)); /* This should not generate an event */ eis_device_scroll_cancel(device, true, true); eis_device_frame(device, peck_eis_now(peck)); /* But scrolling again will re-enable stopping */ eis_device_scroll_delta(device, 3.3, 4.4); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_cancel(device, true, true); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_delta(device, 3.3, 4.4); /* This one is a client bug and shouldn't trigger an event */ eis_device_scroll_cancel(device, false, false); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *scroll = peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA); _unref_(ei_event) *first = peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL); munit_assert(ei_event_scroll_get_stop_x(first)); munit_assert(!ei_event_scroll_get_stop_y(first)); _unref_(ei_event) *second = peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL); munit_assert(!ei_event_scroll_get_stop_x(second)); munit_assert(ei_event_scroll_get_stop_y(second)); /* third one doesn't trigger an event */ _unref_(ei_event) *again = peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA); _unref_(ei_event) *fourth = peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL); munit_assert(ei_event_scroll_get_stop_x(fourth)); munit_assert(ei_event_scroll_get_stop_y(fourth)); _unref_(ei_event) *again_again = peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA); peck_assert_no_ei_events(ei); } return MUNIT_OK; } /* same as test_ei_device_scroll_stop_cancel but for a passive context */ MUNIT_TEST(test_passive_ei_device_scroll_stop_cancel) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 456; with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_start_emulating(device, sequence); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); } /* cancel after stop is fine, stop after cancel is ignored */ with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_scroll_delta(device, 1.1, 2.2); eis_device_frame(device, peck_eis_now(peck)); peck_mark(peck); eis_device_scroll_stop(device, true, false); peck_mark(peck); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_cancel(device, true, false); peck_mark(peck); eis_device_frame(device, peck_eis_now(peck)); eis_device_scroll_cancel(device, false, true); eis_device_frame(device, peck_eis_now(peck)); peck_mark(peck); /* This should not generate an event */ eis_device_scroll_stop(device, true, true); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *scroll = peck_ei_next_event(ei, EI_EVENT_SCROLL_DELTA); _unref_(ei_event) *stop = peck_ei_next_event(ei, EI_EVENT_SCROLL_STOP); munit_assert(ei_event_scroll_get_stop_x(stop)); munit_assert(!ei_event_scroll_get_stop_y(stop)); _unref_(ei_event) *first = peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL); munit_assert(ei_event_scroll_get_stop_x(first)); munit_assert(!ei_event_scroll_get_stop_y(first)); _unref_(ei_event) *second = peck_ei_next_event(ei, EI_EVENT_SCROLL_CANCEL); munit_assert(!ei_event_scroll_get_stop_x(second)); munit_assert(ei_event_scroll_get_stop_y(second)); /* third one doesn't trigger an event */ peck_assert_no_ei_events(ei); } return MUNIT_OK; } /* same as test_ei_device_touch but for a passive context */ MUNIT_TEST(test_passive_ei_device_touch) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); struct eis_device *device = NULL; uint32_t maxx = 0, maxy = 0; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 123; with_server(peck) { device = peck_eis_get_default_touch(peck); eis_device_start_emulating(device, sequence); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); } with_server(peck) { device = peck_eis_get_default_touch(peck); /* We know our default device has one region */ struct eis_region *r = eis_device_get_region(device, 0); maxx = eis_region_get_x(r) + eis_region_get_width(r); maxy = eis_region_get_y(r) + eis_region_get_height(r); eis_device_start_emulating(device, ++sequence); _unref_(eis_touch) *t = eis_device_touch_new(device); eis_touch_down(t, 1, 2); eis_device_frame(device, peck_eis_now(peck)); eis_touch_motion(t, 200, 500); eis_device_frame(device, peck_eis_now(peck)); eis_touch_up(t); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *down = peck_ei_touch_down(ei, 1, 2); uint32_t tid = ei_event_touch_get_id(down); _unref_(ei_event) *motion = peck_ei_touch_motion(ei, 200, 500); munit_assert_uint32(ei_event_touch_get_id(motion), ==, tid); _unref_(ei_event) *up = peck_ei_touch_up(ei); munit_assert_uint32(ei_event_touch_get_id(up), ==, tid); peck_assert_no_ei_events(ei); } with_server(peck) { _unref_(eis_touch) *t = eis_device_touch_new(device); /* outside clip range, expect touch to be dropped */ with_nonfatal_eis_bug(peck) eis_touch_down(t, maxx + 1, maxy/2); eis_device_frame(device, peck_eis_now(peck)); /* ignored because the touch down was out of range */ eis_touch_motion(t, maxx + 1, maxy/3); eis_device_frame(device, peck_eis_now(peck)); /* ignored because the touch down was out of range */ with_nonfatal_eis_bug(peck) eis_touch_up(t); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_touch) *t = eis_device_touch_new(device); /* outside clip range, expect touch to be dropped */ with_nonfatal_eis_bug(peck) eis_touch_down(t, maxx/2, maxy + 1); eis_device_frame(device, peck_eis_now(peck)); /* ignored because the touch down was out of range */ eis_touch_motion(t, maxx/3, maxy + 1); eis_device_frame(device, peck_eis_now(peck)); /* ignored because the touch down was out of range */ with_nonfatal_eis_bug(peck) eis_touch_up(t); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { peck_assert_no_ei_events(ei); } with_server(peck) { _unref_(eis_touch) *t = eis_device_touch_new(device); eis_touch_down(t, 100, 200); eis_device_frame(device, peck_eis_now(peck)); /* outside allowed range, generates a touch up */ with_nonfatal_eis_bug(peck) eis_touch_motion(t, maxx + 1, 200); eis_device_frame(device, peck_eis_now(peck)); /* touch is already considered up */ with_nonfatal_eis_bug(peck) eis_touch_up(t); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200); _unref_(ei_event) *up = peck_ei_touch_up(ei); peck_assert_no_ei_events(ei); } with_server(peck) { _unref_(eis_touch) *t = eis_device_touch_new(device); eis_touch_down(t, 100, 100); eis_device_frame(device, peck_eis_now(peck)); /* client forgets to touch up, touch_unref takes care of it */ } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 100); _unref_(ei_event) *up = peck_ei_touch_up(ei); peck_assert_no_ei_events(ei); } with_server(peck) { /* touch only allocated, not actually set down */ struct eis_touch *t1 = eis_device_touch_new(device); /* touch never set down */ _unref_(eis_touch) *t2 = eis_device_touch_new(device); with_nonfatal_eis_bug(peck) eis_touch_up(t2); eis_device_frame(device, peck_eis_now(peck)); /* touch never set down */ _unref_(eis_touch) *t3 = eis_device_touch_new(device); with_nonfatal_eis_bug(peck) eis_touch_motion(t3, 100, 200); eis_device_frame(device, peck_eis_now(peck)); with_nonfatal_eis_bug(peck) eis_touch_up(t3); eis_device_frame(device, peck_eis_now(peck)); with_nonfatal_eis_bug(peck) eis_touch_unref(t1); } peck_dispatch_until_stable(peck); with_client(peck) { peck_assert_no_ei_events(ei); } with_server(peck) { /* touch re-used */ _unref_(eis_touch) *t = eis_device_touch_new(device); eis_touch_down(t, 100, 200); eis_device_frame(device, peck_eis_now(peck)); eis_touch_up(t); eis_device_frame(device, peck_eis_now(peck)); with_nonfatal_eis_bug(peck) eis_touch_down(t, 200, 300); eis_device_frame(device, peck_eis_now(peck)); with_nonfatal_eis_bug(peck) eis_touch_motion(t, 300, 400); eis_device_frame(device, peck_eis_now(peck)); with_nonfatal_eis_bug(peck) eis_touch_up(t); eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200); _unref_(ei_event) *up = peck_ei_touch_up(ei); peck_assert_no_ei_events(ei); } with_server(peck) { /* double-down, double-up */ _unref_(eis_touch) *t = eis_device_touch_new(device); eis_touch_down(t, 100, 200); eis_device_frame(device, peck_eis_now(peck)); with_nonfatal_eis_bug(peck) eis_touch_down(t, 200, 300); /* ignored */ eis_device_frame(device, peck_eis_now(peck)); eis_touch_motion(t, 300, 400); eis_device_frame(device, peck_eis_now(peck)); eis_touch_up(t); eis_device_frame(device, peck_eis_now(peck)); with_nonfatal_eis_bug(peck) eis_touch_up(t); /* ignored */ eis_device_frame(device, peck_eis_now(peck)); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *down = peck_ei_touch_down(ei, 100, 200); _unref_(ei_event) *motion = peck_ei_touch_motion(ei, 300, 400); _unref_(ei_event) *up = peck_ei_touch_up(ei); peck_assert_no_ei_events(ei); } return MUNIT_OK; } /* same as test_ei_device_multitouch but for a passive context */ MUNIT_TEST(test_passive_ei_device_multitouch) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_TOUCH); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); peck_dispatch_until_stable(peck); uint32_t sequence = 345; with_server(peck) { struct eis_device *device = peck_eis_get_default_touch(peck); _unref_(eis_touch) *t1 = eis_device_touch_new(device); _unref_(eis_touch) *t2 = eis_device_touch_new(device); eis_device_start_emulating(device, sequence); eis_touch_down(t1, 1, 2); eis_device_frame(device, peck_eis_now(peck)); eis_touch_motion(t1, 2, 3); eis_device_frame(device, peck_eis_now(peck)); eis_touch_down(t2, 3, 4); eis_device_frame(device, peck_eis_now(peck)); eis_touch_motion(t2, 4, 5); eis_device_frame(device, peck_eis_now(peck)); eis_touch_up(t2); eis_touch_up(t1); eis_device_frame(device, peck_eis_now(peck)); eis_device_stop_emulating(device); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); _unref_(ei_event) *down1 = peck_ei_touch_down(ei, 1, 2); uint32_t tid1 = ei_event_touch_get_id(down1); _unref_(ei_event) *motion1 = peck_ei_touch_motion(ei, 2, 3); munit_assert_uint32(ei_event_touch_get_id(motion1), ==, tid1); _unref_(ei_event) *down2 = peck_ei_touch_down(ei, 3, 4); uint32_t tid2 = ei_event_touch_get_id(down2); munit_assert_uint32(tid2, !=, tid1); _unref_(ei_event) *motion2 = peck_ei_touch_motion(ei, 4, 5); munit_assert_uint32(ei_event_touch_get_id(motion2), ==, tid2); _unref_(ei_event) *up2 = peck_ei_touch_up(ei); munit_assert_uint32(ei_event_touch_get_id(up2), ==, tid2); _unref_(ei_event) *up1 = peck_ei_touch_up(ei); munit_assert_uint32(ei_event_touch_get_id(up1), ==, tid1); _unref_(ei_event) *stop = peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING); } return MUNIT_OK; } MUNIT_TEST(test_passive_ei_frame_timestamp) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); uint64_t ts1 = 0, ts2 = 0; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 345; with_server(peck) { struct eis_device *device = peck_eis_get_default_keyboard(peck); eis_device_start_emulating(device, sequence); ts1 = peck_eis_now(peck); eis_device_keyboard_key(device, KEY_Q, true); eis_device_frame(device, ts1); ts2 = peck_eis_now(peck); eis_device_keyboard_key(device, KEY_Q, false); eis_device_frame(device, ts2); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); _unref_(ei_event) *kbd1 = peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY); _unref_(ei_event) *frame1 = peck_ei_next_event(ei, EI_EVENT_FRAME); _unref_(ei_event) *kbd2 = peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY); _unref_(ei_event) *frame2 = peck_ei_next_event(ei, EI_EVENT_FRAME); uint64_t timestamp = ei_event_get_time(frame1); munit_assert_uint64(timestamp, ==, ts1); munit_assert_uint64(timestamp, ==, ei_event_get_time(kbd1)); timestamp = ei_event_get_time(frame2); munit_assert_uint64(timestamp, ==, ts2); munit_assert_uint64(timestamp, ==, ei_event_get_time(kbd2)); peck_assert_no_ei_events(ei); } return MUNIT_OK; } MUNIT_TEST(test_passive_ei_flush_frame) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_RESUME_DEVICE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_dispatch_until_stable(peck); uint32_t sequence = 678; with_server(peck) { struct eis_device *device = peck_eis_get_default_keyboard(peck); eis_device_start_emulating(device, sequence); eis_device_keyboard_key(device, KEY_Q, true); /* Missing call to ei_device_frame() */ with_nonfatal_eis_bug(peck) eis_device_stop_emulating(device); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *start = peck_ei_next_event(ei, EI_EVENT_DEVICE_START_EMULATING); munit_assert_uint(ei_event_emulating_get_sequence(start), ==, sequence); _unref_(ei_event) *kbd = peck_ei_next_event(ei, EI_EVENT_KEYBOARD_KEY); _unref_(ei_event) *frame = peck_ei_next_event(ei, EI_EVENT_FRAME); _unref_(ei_event) *stop = peck_ei_next_event(ei, EI_EVENT_DEVICE_STOP_EMULATING); peck_assert_no_ei_events(ei); } return MUNIT_OK; } libei-1.2.1/test/test-ei-seat.c000066400000000000000000000146551456005336000162430ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-munit.h" #include "eierpecken.h" MUNIT_TEST(test_ei_seat_bind_unbind) { _unref_(peck) *peck = peck_new(); _unref_(ei_seat) *seat = NULL; _unref_(ei_device) *dev1 = NULL; _unref_(ei_device) *dev2 = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); seat = ei_seat_ref(ei_event_get_seat(event)); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, NULL); } /* server has the Bind event now and creates devices */ peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *e1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); _unref_(ei_event) *discard1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_RESUMED); _unref_(ei_event) *e2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); _unref_(ei_event) *discard2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_RESUMED); dev1 = ei_device_ref(ei_event_get_device(e1)); dev2 = ei_device_ref(ei_event_get_device(e2)); ei_seat_unbind_capabilities(seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, NULL); } /* Dispatch, server is aware of the ei_seat_unbind() */ peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *e1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); _unref_(ei_event) *e2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); struct ei_device *d1 = ei_event_get_device(e1); struct ei_device *d2 = ei_event_get_device(e2); munit_assert(d1 != d2); munit_assert(d1 == dev1 || d1 == dev2); munit_assert(d2 == dev1 || d2 == dev2); _unref_(ei_event) *eseat = peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); struct ei_seat *s = ei_event_get_seat(eseat); munit_assert_ptr_equal(s, seat); } return MUNIT_OK; } MUNIT_TEST(test_ei_seat_bind_unbind_noref) { _unref_(peck) *peck = peck_new(); struct ei_seat *seat = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); seat = ei_seat_ref(ei_event_get_seat(event)); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, NULL); } /* server has the Bind event now and creates devices */ peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *e1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); _unref_(ei_event) *discard1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_RESUMED); _unref_(ei_event) *e2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); _unref_(ei_event) *discard2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_RESUMED); /* Drop our ref before unbinding. This is technically wrong - * must not use seat after unref, but we know there's at least * one ref inside libei for this seat, so this tests ensures * we don't rely on a caller ref to keep everything alive. */ ei_seat_unref(seat); ei_seat_unbind_capabilities(seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, NULL); } /* Dispatch, server is aware of the ei_seat_unbind() */ peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *e1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); _unref_(ei_event) *e2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); _unref_(ei_event) *eseat = peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); } return MUNIT_OK; } MUNIT_TEST(test_ei_seat_bind_unbind_immediately) { _unref_(peck) *peck = peck_new(); struct ei_seat *seat = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_KEYBOARD); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); seat = ei_event_get_seat(event); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, NULL); ei_seat_unbind_capabilities(seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, NULL); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *e1 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); _unref_(ei_event) *e2 = peck_ei_next_event(ei, EI_EVENT_DEVICE_RESUMED); _unref_(ei_event) *e3 = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); _unref_(ei_event) *e4 = peck_ei_next_event(ei, EI_EVENT_DEVICE_RESUMED); _unref_(ei_event) *e5 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); _unref_(ei_event) *e6 = peck_ei_next_event(ei, EI_EVENT_DEVICE_REMOVED); _unref_(ei_event) *e7 = peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); } return MUNIT_OK; } libei-1.2.1/test/test-ei.c000066400000000000000000000523501456005336000153030ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include #include "util-munit.h" #include "util-time.h" #include "util-strings.h" #include "eierpecken.h" MUNIT_TEST(test_ei_ref_unref) { struct ei *ei = ei_new(NULL); struct ei *refd = ei_ref(ei); munit_assert_ptr_equal(ei, refd); struct ei *unrefd = ei_unref(ei); munit_assert_ptr_null(unrefd); unrefd = ei_unref(ei); munit_assert_ptr_null(unrefd); /* memleak only shows up in valgrind */ return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_immediately) { _unref_(peck) *peck = peck_new(); /* Client is immediately rejected */ peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_REJECT_CLIENT); peck_dispatch_until_stable(peck); /* Expect the client to get a disconnect event */ with_client(peck) { ei_dispatch(ei); _unref_(ei_event) *disconnect = peck_ei_next_event(ei, EI_EVENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_self_immediately) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_dispatch_until_stable(peck); /* Disconnect before server processed CONNECT */ with_client(peck) { peck_drop_ei(peck); ei_unref(ei); } peck_dispatch_until_stable(peck); /* Expect the client to get a disconnect event */ with_server(peck) { _unref_(eis_event) *connect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_CONNECT); _unref_(eis_event) *disconnect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_after_connect) { _unref_(peck) *peck = peck_new(); _unref_(eis_client) *client = NULL; peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_dispatch_until_stable(peck); with_server(peck) { eis_dispatch(eis); _unref_(eis_event) *e = peck_eis_next_event(eis, EIS_EVENT_CLIENT_CONNECT); client = eis_client_ref(eis_event_get_client(e)); eis_client_connect(client); } with_client(peck) { ei_dispatch(ei); _unref_(ei_event) *e = peck_ei_next_event(ei, EI_EVENT_CONNECT); } with_server(peck) { eis_client_disconnect(client); } with_client(peck) { ei_dispatch(ei); _unref_(ei_event) *e = peck_ei_next_event(ei, EI_EVENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_self_after_connect) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_dispatch_until_stable(peck); with_client(peck) { peck_drop_ei(peck); ei_unref(ei); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *connect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_CONNECT); _unref_(eis_event) *disconnect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_after_seat) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *connect = peck_ei_next_event(ei, EI_EVENT_CONNECT); _unref_(ei_event) *seat = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); } with_server(peck) { struct eis_client *client = peck_eis_get_default_client(peck); eis_client_disconnect(client); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *seat = peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); _unref_(ei_event) *disconnect = peck_ei_next_event(ei, EI_EVENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_self_after_seat) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *connect = peck_ei_next_event(ei, EI_EVENT_CONNECT); _unref_(ei_event) *seat = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); peck_drop_ei(peck); /* Disconnect from client */ ei_unref(ei); /* There is no way to disconnect from the server without * destroying the context, so we don't care about the actual * events here */ } peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *disconnect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_after_bind_before_received) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); struct ei_seat *seat = ei_event_get_seat(event); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, NULL); } /* We have *not* called eis_dispatch, so the seat bind hasn't been * processed by the server yet */ with_server(peck) { struct eis_client *client = peck_eis_get_default_client(peck); eis_client_disconnect(client); } with_client(peck) { ei_dispatch(ei); _unref_(ei_event) *seat_removed = peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); _unref_(ei_event) *disconnect = peck_ei_next_event(ei, EI_EVENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_self_after_bind_before_received) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); struct ei_seat *seat = ei_event_get_seat(event); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, NULL); /* Disconnect before the server can process the bind event */ peck_drop_ei(peck); ei_unref(ei); } peck_dispatch_eis(peck); with_server(peck) { /* Server got the bind event but client disconnected * immediately after */ _unref_(eis_event) *bind = peck_eis_next_event(eis, EIS_EVENT_SEAT_BIND); _unref_(eis_event) *unbind = peck_eis_next_event(eis, EIS_EVENT_SEAT_BIND); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_POINTER)); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_KEYBOARD)); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_TOUCH)); _unref_(eis_event) *disconnect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_after_bind_after_received) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); struct ei_seat *seat = ei_event_get_seat(event); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, NULL); } /* Receive the Bind event but don't actually add any devices, * disconnect the client instead */ peck_dispatch_eis(peck); with_server(peck) { struct eis_client *client = peck_eis_get_default_client(peck); eis_client_disconnect(client); } with_client(peck) { ei_dispatch(ei); _unref_(ei_event) *seat_removed = peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); _unref_(ei_event) *disconnect = peck_ei_next_event(ei, EI_EVENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_self_after_bind_after_received) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); struct ei_seat *seat = ei_event_get_seat(event); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, NULL); } /* Make sure server sees Bind, then disconnect from server */ peck_dispatch_eis(peck); with_client(peck) { peck_drop_ei(peck); ei_unref(ei); } peck_dispatch_eis(peck); with_server(peck) { _unref_(eis_event) *bind = peck_eis_next_event(eis, EIS_EVENT_SEAT_BIND); _unref_(eis_event) *unbind = peck_eis_next_event(eis, EIS_EVENT_SEAT_BIND); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_POINTER)); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_KEYBOARD)); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_TOUCH)); _unref_(eis_event) *disconnect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_after_unbind_before_received) { _unref_(peck) *peck = peck_new(); _unref_(ei_seat) *seat = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); seat = ei_seat_ref(ei_event_get_seat(event)); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, NULL); } peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); /* server has the Bind event now */ peck_dispatch_until_stable(peck); with_client(peck) { ei_seat_unbind_capabilities(seat, EI_DEVICE_CAP_POINTER, NULL); } /* No server dispatch here so the server isn't aware of the * ei_seat_unbind() call. Disconnect the client */ with_server(peck) { struct eis_client *client = peck_eis_get_default_client(peck); eis_client_disconnect(client); } with_client(peck) { ei_dispatch(ei); _unref_(ei_event) *seat_removed = peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); _unref_(ei_event) *disconnect = peck_ei_next_event(ei, EI_EVENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_ei_disconnect_after_unbind_after_received) { _unref_(peck) *peck = peck_new(); _unref_(ei_seat) *seat = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); seat = ei_seat_ref(ei_event_get_seat(event)); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, NULL); } /* server has the Bind event now */ peck_dispatch_until_stable(peck); with_client(peck) { ei_seat_unbind_capabilities(seat, EI_DEVICE_CAP_POINTER, NULL); } /* Dispatch, server is aware of the ei_seat_unbind() */ peck_dispatch_eis(peck); with_server(peck) { struct eis_client *client = peck_eis_get_default_client(peck); eis_client_disconnect(client); } peck_dispatch_ei(peck); with_client(peck) { _unref_(ei_event) *seat_removed = peck_ei_next_event(ei, EI_EVENT_SEAT_REMOVED); _unref_(ei_event) *disconnect = peck_ei_next_event(ei, EI_EVENT_DISCONNECT); } return MUNIT_OK; } MUNIT_TEST(test_client_is_sender) { _unref_(peck) *peck = peck_new_context(PECK_EI_SENDER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *connect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_CONNECT); struct eis_client *client = eis_event_get_client(connect); munit_assert_true(eis_client_is_sender(client)); } return MUNIT_OK; } MUNIT_TEST(test_client_is_receiver) { _unref_(peck) *peck = peck_new_context(PECK_EI_RECEIVER); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *connect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_CONNECT); struct eis_client *client = eis_event_get_client(connect); munit_assert_false(eis_client_is_sender(client)); } return MUNIT_OK; } /* Emulates the XWayland behavior for calling * xdotool mousemove_relative -- -1 10 */ MUNIT_TEST(test_xdotool_rel_motion) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSTART); peck_dispatch_until_stable(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); ei_device_pointer_motion(device, -1, 10); ei_device_frame(device, peck_ei_now(peck)); ei_device_close(device); ei_unref(ei); peck_drop_ei(peck); } peck_dispatch_eis(peck); with_server(peck) { _unref_(eis_event) *motion = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION); _unref_(eis_event) *stop = peck_eis_next_event(eis, EIS_EVENT_DEVICE_STOP_EMULATING); _unref_(eis_event) *close = peck_eis_next_event(eis, EIS_EVENT_DEVICE_CLOSED); _unref_(eis_event) *unbind = peck_eis_next_event(eis, EIS_EVENT_SEAT_BIND); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_POINTER)); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_KEYBOARD)); munit_assert_false(eis_event_seat_has_capability(unbind, EIS_DEVICE_CAP_TOUCH)); _unref_(eis_event) *disconnect = peck_eis_next_event(eis, EIS_EVENT_CLIENT_DISCONNECT); peck_assert_no_eis_events(eis); } return MUNIT_OK; } MUNIT_TEST(test_ei_exceed_write_buffer) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSTART); peck_dispatch_until_stable(peck); uint64_t toffset = peck_ei_now(peck); unsigned int count = 10000; /* Large enough to require several flushes */ struct eis_event *events[count]; with_server(peck) { peck_drain_eis(eis); } with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); for (unsigned int i = 0; i < count/2; i++) { ei_device_pointer_motion(device, -1, 10); ei_device_frame(device, toffset + i); } } peck_dispatch_eis(peck); unsigned int before_buffer = 0; with_server(peck) { struct eis_event *next; while ((next = eis_get_event(eis))) { events[before_buffer++] = next; munit_assert_uint(before_buffer, <=, count); } } unsigned int nevents = before_buffer; if (before_buffer < count) { /* We sent >socket buffersize events, so we don't expect to receive all of those */ /* Calling dispatch (on both) should flush some more */ peck_dispatch_until_stable(peck); with_server(peck) { struct eis_event *next; while ((next = eis_get_event(eis))) { events[nevents++] = next; munit_assert_uint(nevents, <=, count); if (nevents % 50 == 0) peck_dispatch_ei(peck); _unref_(eis_event) *next = eis_peek_event(eis); if (!next) peck_dispatch_eis(peck); } } }; munit_assert_uint(nevents, ==, count); for (unsigned int i = 0; i < count; i += 2) { _unref_(eis_event) *motion = events[i]; _unref_(eis_event) *frame = events[i+1]; uint64_t time = eis_event_get_time(frame); munit_assert_string_equal(peck_eis_event_name(motion), peck_eis_event_type_name(EIS_EVENT_POINTER_MOTION)); munit_assert_string_equal(peck_eis_event_name(frame), peck_eis_event_type_name(EIS_EVENT_FRAME)); munit_assert_int64(time, ==, toffset + i/2); } /* Our events are as expected but we never got EAGAIN on * the buffer, so let's count this test as skipped */ if (before_buffer == count) return MUNIT_SKIP; return MUNIT_OK; } MUNIT_TEST(test_ei_exceed_write_buffer_cleanup) { _unref_(peck) *peck = peck_new(); struct ei *ei = peck_get_ei(peck); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSTART); peck_dispatch_until_stable(peck); uint64_t toffset = peck_ei_now(peck); unsigned int count = 10000; /* Large enough to require several flushes */ with_server(peck) { peck_drain_eis(eis); } with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); for (unsigned int i = 0; i < count/2; i++) { ei_device_pointer_motion(device, -1, 10); ei_device_frame(device, toffset + i); } } peck_dispatch_eis(peck); unsigned int before_buffer = 0; with_server(peck) { struct eis_event *next; while ((next = eis_get_event(eis))) { munit_assert_uint(before_buffer, <=, count); eis_event_unref(next); } } /* Our events are as expected but we never got EAGAIN on * the buffer, so let's count this test as skipped */ if (before_buffer == count) return MUNIT_SKIP; /* Make sure cleanup is handled properly */ peck_drop_ei(peck); ei_unref(ei); return MUNIT_OK; } MUNIT_TEST(test_ei_invalid_object_ids) { _unref_(peck) *peck = peck_new(); _unref_(eis_device) *eis_device = NULL; _unref_(eis_device) *dummy = NULL; peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_ALL); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTODEVICES); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_AUTOSTART); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); eis_device = eis_seat_new_device(seat); eis_device_configure_name(eis_device, __func__); eis_device_configure_capability(eis_device, EIS_DEVICE_CAP_POINTER); eis_device_add(eis_device); eis_device_resume(eis_device); } /* Client has device now, remove it from server but don't tell client yet */ peck_dispatch_until_stable(peck); with_server(peck) { eis_device_pause(eis_device); eis_device_remove(eis_device); } peck_dispatch_eis(peck); with_client(peck) { struct ei_device *device = peck_ei_get_default_pointer(peck); ei_device_start_emulating(device, 0); ei_device_pointer_motion(device, -1, 1); ei_device_frame(device, ei_now(ei)); ei_device_stop_emulating(device); } /* This call should trigger invalid object messages */ peck_dispatch_eis(peck); /* This call should receive the invalid object messages */ peck_dispatch_ei(peck); /* Create a second device to test the defunct cleaning code, this one is * just used to send messages that will trigger ei_dispatch() */ with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); dummy = eis_seat_new_device(seat); eis_device_configure_name(dummy, __func__); eis_device_configure_capability(dummy, EIS_DEVICE_CAP_POINTER); eis_device_add(dummy); eis_device_resume(dummy); } peck_dispatch_until_stable(peck); /* trigger the defunct object code - we can't actually test anything here * beyond hoping it crashes if there's a bug */ peck_ei_add_time_offset(peck, s2us(6)); for (int i = 0; i < 30; i++) { with_server(peck) { eis_device_pause(dummy); eis_device_resume(dummy); } peck_dispatch_ei(peck); } return MUNIT_OK; } libei-1.2.1/test/test-eis.c000066400000000000000000000257171456005336000154750ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-munit.h" #include "util-version.h" #include "eierpecken.h" MUNIT_TEST(eistest_ref_unref) { struct eis *eis = eis_new(NULL); struct eis *refd = eis_ref(eis); munit_assert_ptr_equal(eis, refd); struct eis *unrefd = eis_unref(eis); munit_assert_ptr_null(unrefd); unrefd = eis_unref(eis); munit_assert_ptr_null(unrefd); /* memleak only shows up in valgrind */ return MUNIT_OK; } MUNIT_TEST(eistest_name) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); /* The name is set by peck_new() and immutable after the * backend was set, which peck_new() does for us as well. * So the name we should see is the one hardcoded in peck_new() */ with_client(peck) { with_nonfatal_ei_bug(peck) ei_configure_name(ei, "this name should not be used"); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *event = eis_get_event(eis); munit_assert_ptr_not_null(event); munit_assert_int(eis_event_get_type(event), ==, EIS_EVENT_CLIENT_CONNECT); struct eis_client *client = eis_event_get_client(event); munit_assert_string_equal(eis_client_get_name(client), "eierpecken test context"); } return MUNIT_OK; } MUNIT_TEST(eistest_cliend_bind_all_caps) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *seat_added = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); struct ei_seat *seat = ei_event_get_seat(seat_added); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_POINTER_ABSOLUTE, EI_DEVICE_CAP_KEYBOARD, EI_DEVICE_CAP_TOUCH, EI_DEVICE_CAP_BUTTON, EI_DEVICE_CAP_SCROLL, NULL); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *bind = peck_eis_next_event(eis, EIS_EVENT_SEAT_BIND); munit_assert_true(eis_event_seat_has_capability(bind, EIS_DEVICE_CAP_POINTER)); munit_assert_true(eis_event_seat_has_capability(bind, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_true(eis_event_seat_has_capability(bind, EIS_DEVICE_CAP_KEYBOARD)); munit_assert_true(eis_event_seat_has_capability(bind, EIS_DEVICE_CAP_TOUCH)); } return MUNIT_OK; } MUNIT_TEST(eistest_cliend_bind_some_caps) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_NONE); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_CONNECT); peck_dispatch_until_stable(peck); /* Before the clients binds to the seat, our seat has all caps */ with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); munit_assert_true(eis_seat_has_capability(seat, EIS_DEVICE_CAP_KEYBOARD)); munit_assert_true(eis_seat_has_capability(seat, EIS_DEVICE_CAP_POINTER)); munit_assert_true(eis_seat_has_capability(seat, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_true(eis_seat_has_capability(seat, EIS_DEVICE_CAP_TOUCH)); } with_client(peck) { _unref_(ei_event) *event = peck_ei_next_event(ei, EI_EVENT_SEAT_ADDED); struct ei_seat *seat = ei_event_get_seat(event); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_KEYBOARD, NULL); ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_TOUCH, NULL); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *bind_kbd = peck_eis_next_event(eis, EIS_EVENT_SEAT_BIND); munit_assert_true(eis_event_seat_has_capability(bind_kbd, EIS_DEVICE_CAP_KEYBOARD)); munit_assert_false(eis_event_seat_has_capability(bind_kbd, EIS_DEVICE_CAP_POINTER)); munit_assert_false(eis_event_seat_has_capability(bind_kbd, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_false(eis_event_seat_has_capability(bind_kbd, EIS_DEVICE_CAP_TOUCH)); _unref_(eis_event) *bind_touch = peck_eis_next_event(eis, EIS_EVENT_SEAT_BIND); munit_assert_true(eis_event_seat_has_capability(bind_touch, EIS_DEVICE_CAP_KEYBOARD)); munit_assert_false(eis_event_seat_has_capability(bind_touch, EIS_DEVICE_CAP_POINTER)); munit_assert_false(eis_event_seat_has_capability(bind_touch, EIS_DEVICE_CAP_POINTER_ABSOLUTE)); munit_assert_true(eis_event_seat_has_capability(bind_touch, EIS_DEVICE_CAP_TOUCH)); } return MUNIT_OK; } MUNIT_TEST(eistest_device_resume_pause_twice) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_enable_ei_behavior(peck, PECK_EI_BEHAVIOR_HANDLE_ADDED); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); /* Resuming multiple times should only trigger one event */ eis_device_resume(device); eis_device_resume(device); /* noop */ eis_device_resume(device); /* noop */ } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *resumed = peck_ei_next_event(ei, EI_EVENT_DEVICE_RESUMED); peck_assert_no_ei_events(ei); } /* Pausing multiple times should only trigger one event */ with_server(peck) { struct eis_device *device = peck_eis_get_default_pointer(peck); eis_device_pause(device); eis_device_pause(device); /* noop */ eis_device_pause(device); /* noop */ } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *paused = peck_ei_next_event(ei, EI_EVENT_DEVICE_PAUSED); peck_assert_no_ei_events(ei); } return MUNIT_OK; } MUNIT_TEST(eistest_device_ignore_paused_device) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_START_EMULATING); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ADD_POINTER); peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *added = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); struct ei_device *device = ei_event_get_device(added); peck_assert_no_ei_events(ei); /* device was never resumed */ with_nonfatal_ei_bug(peck) ei_device_pointer_motion(device, 1, 1); } uint32_t sequence = 100; for (size_t i = 0; i < 3; i++) { struct eis_device *device = peck_eis_get_default_pointer(peck); /* Device is paused */ with_server(peck) { peck_assert_no_eis_events(eis); eis_device_resume(device); } peck_dispatch_until_stable(peck); /* Device is resumed */ with_client(peck) { _unref_(ei_event) *resumed = peck_ei_next_event(ei, EI_EVENT_DEVICE_RESUMED); struct ei_device *device = ei_event_get_device(resumed); peck_assert_no_ei_events(ei); with_emulation(device, ++sequence) { ei_device_pointer_motion(device, 1, 1); ei_device_frame(device, ei_now(ei)); } } peck_dispatch_until_stable(peck); /* Device is resumed */ with_server(peck) { _unref_(eis_event) *rel = peck_eis_next_event(eis, EIS_EVENT_POINTER_MOTION); _unref_(eis_event) *stop = peck_eis_next_event(eis, EIS_EVENT_DEVICE_STOP_EMULATING); peck_assert_no_eis_events(eis); eis_device_pause(device); } peck_dispatch_until_stable(peck); /* Device is paused */ with_client(peck) { _unref_(ei_event) *paused = peck_ei_next_event(ei, EI_EVENT_DEVICE_PAUSED); struct ei_device *device = ei_event_get_device(paused); peck_assert_no_ei_events(ei); with_nonfatal_ei_bug(peck) { ei_device_pointer_motion(device, 1, 1); ei_device_frame(device, ei_now(ei)); } } } return MUNIT_OK; } MUNIT_TEST(eistest_regions) { _unref_(peck) *peck = peck_new(); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_ACCEPT_CLIENT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_DEFAULT_SEAT); peck_enable_eis_behavior(peck, PECK_EIS_BEHAVIOR_HANDLE_BIND_SEAT); peck_dispatch_until_stable(peck); with_server(peck) { struct eis_seat *seat = peck_eis_get_default_seat(peck); _unref_(eis_device) *ptr = eis_seat_new_device(seat); eis_device_configure_capability(ptr, EIS_DEVICE_CAP_POINTER_ABSOLUTE); eis_device_configure_capability(ptr, EIS_DEVICE_CAP_BUTTON); eis_device_configure_capability(ptr, EIS_DEVICE_CAP_SCROLL); eis_device_configure_name(ptr, "region device"); _unref_(eis_region) *region = eis_device_new_region(ptr); eis_region_set_size(region, 100, 200); eis_region_set_offset(region, 300, 400); eis_region_set_physical_scale(region, 5.6); eis_region_add(region); eis_device_add(ptr); eis_device_resume(ptr); } peck_dispatch_until_stable(peck); with_client(peck) { _unref_(ei_event) *added = peck_ei_next_event(ei, EI_EVENT_DEVICE_ADDED); _unref_(ei_event) *resumed = peck_ei_next_event(ei, EI_EVENT_DEVICE_RESUMED); struct ei_device *device = ei_event_get_device(resumed); ei_device_start_emulating(device, 1); } peck_dispatch_until_stable(peck); with_server(peck) { _unref_(eis_event) *start = peck_eis_next_event(eis, EIS_EVENT_DEVICE_START_EMULATING); struct eis_device *device = eis_event_get_device(start); struct eis_region *r = eis_device_get_region(device, 0); munit_assert_int(eis_region_get_width(r), ==, 100); munit_assert_int(eis_region_get_height(r), ==, 200); munit_assert_int(eis_region_get_x(r), ==, 300); munit_assert_int(eis_region_get_y(r), ==, 400); munit_assert_double(eis_region_get_physical_scale(r), ==, 5.6); r = eis_device_get_region(device, 1); munit_assert_null(r); } return MUNIT_OK; } libei-1.2.1/test/test-main.c000066400000000000000000000002051456005336000156220ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #include "util-munit.h" int main(int argc, char **argv) { return munit_tests_run(argc, argv); } libei-1.2.1/test/test_oeffis.py000066400000000000000000000321221456005336000164440ustar00rootroot00000000000000#!/usr/bin/env python3 # # SPDX-License-Identifier: MIT # # This file is formatted with Python Black # # Introduction # ============ # # This is a Python-based test suite making use of DBusMock to test the # liboeffis.so C library. # # The main components are: # - LibOeffis: the Python class wrapping liboeffis.so via ctypes. # This is a manually maintained mapping, any API additions/changes must # updated here. # - Oeffis: a pythonic wrapper around LibOeffis so the tests look like Python # - TestOeffis: the test class for all tests that must talk to DBus # # DBusMock integration # ==================== # # DBusMock works in that a **separate process** is started that provides a # DBus session bus that our tests connect to. Templates for dbusmock provide # the behavior of that bus. Note that the mocked bus cannot be controlled # from our test code, it's a separate process. Unless you set up special # DBus signals/methods to talk to it, which we don't. # # Any test that requires DBus looks like this: # # ```python # class TestOeffis(): # .... # def test_foo(self, daemon, mainloop): # # now you can talk to the RemoteDesktop portal # ... # ``` # See the RemoteDesktop template for parameters that can be passed in. # # DBusMock templates # ------------------ # # See the templates/ directory for the templates used by DBusMock. Templates # are named after the portal. Available parameters are in the `load()` function. # from ctypes import c_char_p, c_int, c_uint32, c_void_p from typing import Iterator, List, Tuple, Type, Optional, TextIO from gi.repository import GLib # type: ignore from dbus.mainloop.glib import DBusGMainLoop import attr import ctypes import dbus import dbus.proxies import dbusmock import fcntl import os import pytest import socket import subprocess DBusGMainLoop(set_as_default=True) PREFIX = "oeffis_" # Uncomment this to have dbus-monitor listen on the normal session address # rather than the test DBus. This can be useful for cases where *something* # messes up and tests run against the wrong bus. # # session_dbus_address = os.environ["DBUS_SESSION_BUS_ADDRESS"] def version_at_least(have, required) -> bool: for h, r in zip(have.split("."), required.split(".")): if h < r: return False elif h > r: return True return True @attr.s class _Api: name: str = attr.ib() args: Tuple[Type[ctypes._SimpleCData], ...] = attr.ib() return_type: Optional[Type[ctypes._SimpleCData]] = attr.ib() @property def basename(self) -> str: return self.name[len(PREFIX) :] @attr.s class _Enum: name: str = attr.ib() value: int = attr.ib() @property def basename(self) -> str: return self.name[len(PREFIX) :] class LibOeffis(object): """ liboeffis.so wrapper. This is a singleton ctypes wrapper into liboeffis.so with minimal processing. Example: >>> lib = LibOeffis.instance() >>> ctx = lib.oeffis_new(None) >>> lib.oeffis_unref(ctx) >>> print(lib.OEFFIS_EVENT_CLOSED) In most cases you probably want to use the ``Oeffis`` class instead. """ _lib = None @staticmethod def _cdll(): return ctypes.CDLL("liboeffis.so.1", use_errno=True) @classmethod def _load(cls): cls._lib = cls._cdll() for api in cls._api_prototypes: func = getattr(cls._lib, api.name) func.argtypes = api.args func.restype = api.return_type setattr(cls, api.name, func) for e in cls._enums: setattr(cls, e.name, e.value) _api_prototypes: List[_Api] = [ _Api(name="oeffis_new", args=(c_void_p,), return_type=c_void_p), _Api(name="oeffis_ref", args=(c_void_p,), return_type=c_void_p), _Api(name="oeffis_unref", args=(c_void_p,), return_type=c_void_p), _Api(name="oeffis_set_user_data", args=(c_void_p, c_void_p), return_type=None), _Api(name="oeffis_get_user_data", args=(c_void_p,), return_type=c_void_p), _Api(name="oeffis_get_fd", args=(c_void_p,), return_type=c_int), _Api(name="oeffis_get_eis_fd", args=(c_void_p,), return_type=c_int), _Api(name="oeffis_create_session", args=(c_void_p, c_uint32), return_type=None), _Api( name="oeffis_create_session_on_bus", args=(c_void_p, c_char_p, c_uint32), return_type=None, ), _Api(name="oeffis_dispatch", args=(c_void_p,), return_type=None), _Api(name="oeffis_get_event", args=(c_void_p,), return_type=c_int), _Api(name="oeffis_get_error_message", args=(c_void_p,), return_type=c_char_p), ] _enums: List[_Enum] = [ _Enum(name="OEFFIS_DEVICE_ALL_DEVICES", value=0), _Enum(name="OEFFIS_DEVICE_KEYBOARD", value=1), _Enum(name="OEFFIS_DEVICE_POINTER", value=2), _Enum(name="OEFFIS_DEVICE_TOUCHSCREEN", value=4), _Enum(name="OEFFIS_EVENT_NONE", value=0), _Enum(name="OEFFIS_EVENT_CONNECTED_TO_EIS", value=1), _Enum(name="OEFFIS_EVENT_CLOSED", value=2), _Enum(name="OEFFIS_EVENT_DISCONNECTED", value=3), ] @classmethod def instance(cls): if cls._lib is None: cls._load() return cls class Oeffis: """ Convenience wrapper to make using liboeffis a bit more pythonic. >>> o = Oeffis() >>> fd = o.fd >>> o.create_session(o.DEVICE_POINTER) """ def __init__(self, userdata=None): l = LibOeffis.instance() self.ctx = l.oeffis_new(userdata) # type: ignore def wrapper(func): return lambda *args, **kwargs: func(self.ctx, *args, **kwargs) for api in l._api_prototypes: # skip some APIs that are not be exposed because they don't make sense # to have in python. if api.name not in ( "oeffis_ref", "oeffis_unref", "oeffis_get_user_data", "oeffis_set_user_data", ): func = getattr(l, api.name) setattr(self, api.basename, wrapper(func)) for e in l._enums: val = getattr(l, e.name) setattr(self, e.basename, val) @property def fd(self) -> TextIO: """ Return the fd we need to monitor for oeffis_dispatch() """ return os.fdopen(self.get_fd(), "rb") # type: ignore @property def eis_fd(self) -> Optional[socket.socket]: """Return the socket connecting us to the EIS implementation or None if we're not ready/disconnected""" fd = self.get_eis_fd() # type: ignore if fd != -1: return socket.socket(fileno=fd) else: return None @property def error_message(self) -> Optional[str]: return self.get_error_message() # type: ignore def __del__(self): LibOeffis.instance().oeffis_unref(self.ctx) # type: ignore @pytest.fixture() def liboeffis(): return LibOeffis.instance() def test_ref_unref(liboeffis): o = liboeffis.oeffis_new(None) assert o is not None o2 = liboeffis.oeffis_ref(o) assert o2 == o assert liboeffis.oeffis_unref(o) is None assert liboeffis.oeffis_unref(o2) is None assert liboeffis.oeffis_unref(None) is None def test_set_user_data(liboeffis): o = liboeffis.oeffis_new(None) assert liboeffis.oeffis_get_user_data(o) is None liboeffis.oeffis_unref(o) data = ctypes.pointer(ctypes.c_int(52)) o = liboeffis.oeffis_new(data) assert o is not None def test_ctx(): oeffis = Oeffis() assert oeffis.error_message is None fd = oeffis.fd assert fd is not None eisfd = oeffis.eis_fd assert eisfd is None def test_error_out(): # Bus doesn't exist oeffis = Oeffis() oeffis.create_session_on_bus(b"org.freedesktop.OeffisTest", oeffis.DEVICE_POINTER) oeffis.dispatch() e = oeffis.get_event() assert e == oeffis.EVENT_DISCONNECTED assert oeffis.error_message is not None @pytest.fixture() def session_bus_unmonitored() -> Iterator[dbusmock.DBusTestCase]: """ Fixture that yields a newly created session bus """ bus = dbusmock.DBusTestCase() bus.start_session_bus() bus.setUp() yield bus bus.tearDown() bus.tearDownClass() @pytest.fixture() def session_bus(session_bus_unmonitored) -> Iterator[dbusmock.DBusTestCase]: """ Fixture that yields a newly created session bus with dbus-monitor running on that bus (printing to stdout). """ import subprocess env = os.environ.copy() try: env["DBUS_SESSION_BUS_ADDRESS"] = session_dbus_address except NameError: # See comment above pass argv = ["dbus-monitor", "--session"] mon = subprocess.Popen(argv, env=env) def stop_dbus_monitor(): mon.terminate() mon.wait() GLib.timeout_add(2000, stop_dbus_monitor) yield session_bus_unmonitored mon.terminate() mon.wait() @pytest.fixture() def mock( session_bus, portal_name="RemoteDesktop", params=None, extra_templates=[] ) -> Iterator[dbus.proxies.ProxyObject]: """ Fixture that starts a DBusMock daemon in a separate process and returns the ProxyObject for the mock. If extra_templates is specified, it is a list of tuples with the portal name as first value and the param dict to be passed to that template as second value, e.g. ("ScreenCast", {...}). """ p_mock, obj_portal = session_bus.spawn_server_template( template=f"templates/{portal_name.lower()}.py", parameters=params or {}, stdout=subprocess.PIPE, ) flags = fcntl.fcntl(p_mock.stdout, fcntl.F_GETFL) fcntl.fcntl(p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) for t, tparams in extra_templates: template = f"templates/{t.lower()}.py" obj_portal.AddTemplate( template, dbus.Dictionary(tparams, signature="sv"), dbus_interface=dbusmock.MOCK_IFACE, ) yield obj_portal if p_mock.stdout: out = (p_mock.stdout.read() or b"").decode("utf-8") if out: print(out) p_mock.stdout.close() p_mock.terminate() p_mock.wait() @pytest.fixture() def mainloop() -> Iterator[GLib.MainLoop]: """ Yields a mainloop that automatically quits after a fixed timeout, but only on the first run. That's usually enough for tests, if you need to call mainloop.run() repeatedly ensure that a timeout handler is set to ensure quick test case failure in case of error. """ loop = GLib.MainLoop() GLib.timeout_add(2000, loop.quit) yield loop @pytest.mark.skipif( not version_at_least(dbusmock.__version__, "0.28.5"), reason="dbusmock >= 0.28.5 required", ) class TestOeffis: """ Test class that sets up a mocked DBus session bus to be used by liboeffis.so. """ def test_create_session(self, mock, mainloop): oeffis = Oeffis() oeffis.create_session(oeffis.DEVICE_POINTER | oeffis.DEVICE_KEYBOARD) # type: ignore oeffis.dispatch() # type: ignore def _dispatch(source, condition): oeffis.dispatch() # type: ignore return True GLib.io_add_watch(oeffis.fd, 0, GLib.IO_IN, _dispatch) mainloop.run() e = oeffis.get_event() # type: ignore assert e == oeffis.EVENT_CONNECTED_TO_EIS, oeffis.error_message # type: ignore eisfd = oeffis.eis_fd assert eisfd is not None assert eisfd.recv(64) == b"VANILLA" # that's what the template sends def test_create_session_all_devices(self, mock, mainloop): oeffis = Oeffis() oeffis.create_session(oeffis.DEVICE_ALL_DEVICES) # type: ignore oeffis.dispatch() # type: ignore def _dispatch(source, condition): oeffis.dispatch() # type: ignore return True GLib.io_add_watch(oeffis.fd, 0, GLib.IO_IN, _dispatch) mainloop.run() e = oeffis.get_event() # type: ignore assert e == oeffis.EVENT_CONNECTED_TO_EIS, oeffis.error_message # type: ignore mock_interface = dbus.Interface(mock, dbusmock.MOCK_IFACE) method_calls = mock_interface.GetMethodCalls("SelectDevices") assert len(method_calls) > 0 _, args = method_calls[-1] options = args[1] assert "handle_token" in options # if OEFFIS_DEVICE_ALL_DEVICES is selected, liboeffis skips the types option assert "types" not in options def test_version_compare(): assert version_at_least("1", "1.0") assert version_at_least("1.0", "1.0") assert version_at_least("1.1", "1.0") assert version_at_least("1.0.1", "1.0.0") assert version_at_least("1.0.2", "1.0.1") assert version_at_least("1.1", "1.0.2") assert version_at_least("1.1.1", "1.0.2") assert version_at_least("1.0.2.dev1234", "1.0.2") assert version_at_least("2", "1.3") assert not version_at_least("1.0", "1.1") assert not version_at_least("1.0.2", "1.0.3") assert not version_at_least("1.0.2.dev1234", "1.0.3") # Try loading the instance once, if that fails we're probably in # the source directory so let's skip everything. try: LibOeffis.instance() except OSError: pytest.skip(allow_module_level=True) libei-1.2.1/test/test_protocol.py000066400000000000000000001113711456005336000170360ustar00rootroot00000000000000#!/usr/bin/python3 # # SPDX-License-Identifier: MIT # # # EIS protocol test suite. This suite tests an EIS implementation, by default the # eis-demo-server to see how whether it handles protocol messages correctly. # # To test another implementation: # - set LIBEI_TEST_SOCKET to the path your EIS implementation is listening on # - set LIBEI_TEST_SERVER to the executable of your EIS implementation, # or the empty string to connect to a running process # # To run $LIBEI_TEST_SERVER in valgrind, set LIBEI_USE_VALGRIND to a boolean true. # # e.g. # $ export LIBEI_TEST_SOCKET=/run/user/1000/eis-0 # $ export LIBEI_TEST_SERVER="" # $ pytest3 -v --log-level=DEBUG -k 'some string' # # Will run that test against whatever is providing that socket. from functools import reduce from typing import Generator, Optional from pathlib import Path import attr import itertools import os import pytest import subprocess import time import shlex import signal import socket import structlog try: from eiproto import ( hexlify, Context, Interface, InterfaceName, MessageHeader, EiCallback, EiConnection, EiHandshake, EiSeat, ) except ModuleNotFoundError as e: # This file needs to be processed by meson, so let's skip when this fails in the source dir if e.name == "eiproto": pytest.skip(allow_module_level=True) else: raise e logger = structlog.get_logger() VALGRIND_EXITCODE = 3 def VERSION_V(v): """Noop function that helps with grepping for hardcoded version numbers""" return v @pytest.fixture def socketpath(tmp_path) -> Path: test_socket_override = os.environ.get("LIBEI_TEST_SOCKET") if test_socket_override: return Path(test_socket_override) return Path(tmp_path) / "eis-0" @pytest.fixture def valgrind() -> list[str]: """ Return the list of arguments to run our eis_executable in valgrind """ if bool(os.environ.get("LIBEI_USE_VALGRIND", False)): valgrind = [ "valgrind", "--leak-check=full", f"--error-exitcode={VALGRIND_EXITCODE}", ] else: valgrind = [] return valgrind @pytest.fixture def eis_executable(valgrind, socketpath) -> Optional[list[str]]: """ Returns a list of arguments of the EIS executable to run, to be passed into Popen. Returns None if we're expected to connect to an already running instance. """ program = os.environ.get("LIBEI_TEST_SERVER", None) # if the variable is empty, use an existing running server if program == "": return None # If it's not set at all, we use our eis-demo-server if program is None: program = f"@LIBEI_TEST_SERVER@ --socketpath={socketpath} --verbose" # set by meson to eis-demo-server return valgrind + shlex.split(program) @pytest.fixture def eis(socketpath, eis_executable) -> Generator["Eis", None, None]: if not eis_executable: yield Eis.create_existing_implementation(socketpath) else: eis = Eis.create(socketpath, eis_executable) yield eis eis.terminate() @attr.s class Ei: sock: socket.socket = attr.ib() context: Context = attr.ib() connection: Optional[EiConnection] = attr.ib(default=None) seats: list[EiSeat] = attr.ib(init=False, default=attr.Factory(list)) object_ids: Generator[int, None, None] = attr.ib( init=False, default=attr.Factory(lambda: itertools.count(3)) ) _data: bytes = attr.ib(init=False, default=attr.Factory(bytes)) # type: ignore @property def data(self) -> bytes: return self._data def send(self, msg: bytes) -> None: logger.debug(f"sending {len(msg)} bytes", bytes=hexlify(msg)) self.sock.sendmsg([msg]) def find_objects_by_interface(self, interface: str) -> list[Interface]: return [o for o in self.context.objects.values() if o.name == interface] def callback_roundtrip(self) -> bool: assert self.connection is not None cb = EiCallback.create(next(self.object_ids), VERSION_V(1)) self.context.register(cb) self.send(self.connection.Sync(cb.object_id, cb.version)) return self.wait_for( lambda: cb not in self.find_objects_by_interface(InterfaceName.EI_CALLBACK) ) @property def handshake(self) -> EiHandshake: setup = self.context.objects[0] assert isinstance(setup, EiHandshake) return setup def init_default_sender_connection( self, interface_versions: dict[str, int] = {} ) -> None: setup = self.handshake self.send(setup.HandshakeVersion(VERSION_V(1))) self.send(setup.ContextType(EiHandshake.EiContextType.SENDER)) self.send(setup.Name("test client")) for iname in filter(lambda i: i != InterfaceName.EI_HANDSHAKE, InterfaceName): version = interface_versions.get(iname, VERSION_V(1)) self.send(setup.InterfaceVersion(iname, version)) self.send(setup.Finish()) self.dispatch() def wait_for_seat(self, timeout=2) -> bool: def seat_is_done(): return self.seats and [ call for call in self.seats[0].calllog if call.name == "Done" ] return self.wait_for(seat_is_done, timeout) def wait_for_connection(self, timeout=2) -> bool: return self.wait_for(lambda: self.connection is not None, timeout) def wait_for(self, callable, timeout=2) -> bool: expire = time.time() + timeout while not callable(): self.dispatch() if time.time() > expire: return False time.sleep(0.01) return True def seat_fill_capability_masks(self, seat: EiSeat): """ Set up the seat to fill the interface masks for each Capability and add the bind_mask() helper function to compile a mask from interface names. """ def seat_cap(seat, mask, intf_name): seat._interface_masks[intf_name] = mask seat._interface_masks = {} seat.connect("Capability", seat_cap) def bind_mask(interfaces: list[InterfaceName]) -> int: return reduce( lambda mask, v: mask | v, [seat._interface_masks[i] for i in interfaces], 0, ) seat.bind_mask = bind_mask def recv(self) -> bytes: try: data = self.sock.recv(1024) while data: self._data += data data = self.sock.recv(1024) except BlockingIOError: pass return self.data def dispatch(self, timeout=0.1) -> None: if not self.data: expire = time.time() + timeout while not self.recv(): now = time.time() if now >= expire: break time.sleep(min(0.01, expire - now)) if now >= expire: break while self.data: logger.debug("data pending dispatch: ", bytes=hexlify(self.data[:64])) header = MessageHeader.from_data(self.data) logger.debug("dispatching message: ", header=header) consumed = self.context.dispatch(self.data) if consumed == 0: break self.pop(consumed) def pop(self, count: int) -> None: self._data = self._data[count:] @classmethod def create(cls, socketpath: Path): sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM | socket.SOCK_NONBLOCK) while not socketpath.exists(): time.sleep(0.01) for _ in range(3): try: sock.connect(os.fspath(socketpath)) break except ConnectionRefusedError: time.sleep(0.1) else: assert False, "Failed to connect to EIS" ctx = Context.create() ei = cls(sock=sock, context=ctx) # callback for new objects def register_cb(interface: Interface) -> None: if isinstance(interface, EiConnection): assert ei.connection is None ei.connection = interface # Automatic ping/pong handler def ping(conn, id, version, new_objects={}): pingpong = new_objects["ping"] ei.send(pingpong.Done(0)) ei.connection.connect("Ping", ping) elif isinstance(interface, EiSeat): assert interface not in ei.seats seat = interface ei.seat_fill_capability_masks(seat) ei.seats.append(seat) def unregister_cb(interface: Interface) -> None: if interface == ei.connection: assert ei.connection is not None ei.connection = None elif interface in ei.seats: ei.seats.remove(interface) ctx.connect("register", register_cb) ctx.connect("unregister", unregister_cb) return ei @attr.s class Eis: process: Optional[subprocess.Popen] = attr.ib() ei: Ei = attr.ib() _stdout: Optional[str] = attr.ib(init=False, default=None) _stderr: Optional[str] = attr.ib(init=False, default=None) def terminate(self) -> None: if self.process is None: return def kill_gently(process) -> Generator[None, None, None]: process.send_signal(signal.SIGINT) yield process.terminate() yield process.kill() stdout, stderr = None, None for _ in kill_gently(self.process): try: stdout, stderr = self.process.communicate(timeout=1) break except subprocess.TimeoutExpired: pass if stdout: for line in stdout.split("\n"): logger.info(f"stdout: {line}") if stderr: for line in stderr.split("\n"): logger.info(f"stderr: {line}") self.process.wait() rc = self.process.returncode if rc not in [0, -signal.SIGTERM]: if rc == VALGRIND_EXITCODE: assert ( rc != VALGRIND_EXITCODE ), "valgrind reported errors, see valgrind error messages" else: assert ( rc == -signal.SIGTERM ), f"Process exited with {signal.Signals(-rc).name}" self.process = None # allow this to be called multiple times @classmethod def create_existing_implementation(cls, socketpath) -> "Eis": ei = Ei.create(socketpath) return cls(process=None, ei=ei) @classmethod def create(cls, socketpath, executable) -> "Eis": process = subprocess.Popen( executable, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", text=True, bufsize=1, universal_newlines=True, ) ei = Ei.create(socketpath) return cls(process=process, ei=ei) class TestEiProtocol: @property def using_demo_server(self) -> bool: return "@LIBEI_TEST_SERVER@".endswith("eis-demo-server") def test_server_sends_version_event_immediately(self, eis): """ The server is expected to send ei_handshake.interface_version immediately on connect """ ei = eis.ei ei.dispatch() setup = ei.context.objects[0] assert isinstance(setup, EiHandshake) ei.wait_for(lambda: bool(setup.calllog)) call = setup.calllog[0] assert call.name == "HandshakeVersion" assert call.args["version"] == VERSION_V(1) eis.terminate() def test_server_sends_interface_version_events(self, eis): """ The server is expected to send ei_handshake.interface_version immediately on connect """ ei = eis.ei ei.dispatch() setup = ei.handshake ei.init_default_sender_connection() ei.dispatch() ei.wait_for_connection() announced_interfaces = [] for call in setup.calllog: if call.name == "InterfaceVersion": announced_interfaces.append((call.args["name"], call.args["version"])) assert (InterfaceName.EI_CALLBACK, VERSION_V(1)) in announced_interfaces # Right now all our versions are 1, so let's ensure that's true for _, version in announced_interfaces: assert version == VERSION_V(1) eis.terminate() def test_server_sends_min_interface_version(self, eis): ei = eis.ei ei.dispatch() setup = ei.handshake # Assign a random high number of the interfaces we claim to support interface_versions: dict[str, int] = { iface.name: idx + 3 for idx, iface in enumerate(InterfaceName) if iface != InterfaceName.EI_HANDSHAKE } ei.init_default_sender_connection(interface_versions=interface_versions) ei.dispatch() ei.wait_for_connection() announced_interfaces = [] for call in setup.calllog: if call.name == "InterfaceVersion": announced_interfaces.append((call.args["name"], call.args["version"])) assert (InterfaceName.EI_CALLBACK, VERSION_V(1)) in announced_interfaces # Right now all our EIS versions are 1, despite whatever we announce for _, version in announced_interfaces: assert version == VERSION_V(1) eis.terminate() def test_send_wrong_context_type(self, eis): """ Connect with an invalid context type, expect to be disconnected """ ei = eis.ei ei.dispatch() # Pick some random type (and make sure it's not a valid type in the current API) invalid_type = 4 try: EiHandshake.EiContextType(invalid_type) assert ( False ), f"{invalid_type} should not be a valid ContextType, this test needs an update" except ValueError: pass ei.send(ei.handshake.HandshakeVersion(VERSION_V(1))) ei.send(ei.handshake.ContextType(invalid_type)) try: # The server either disconnects the socket because we sent garbage # or immediately disconnects us after the .done request ei.dispatch() for interface in [ InterfaceName.EI_CONNECTION, InterfaceName.EI_CALLBACK, InterfaceName.EI_PINGPONG, ]: ei.send( ei.handshake.InterfaceVersion(interface, VERSION_V(1)) ) # these are required ei.send(ei.handshake.Finish()) ei.dispatch() ei.wait_for_connection(timeout=1) # ok, still not socket-disconnected, let's make sure # we did immediately get a Disconnected message if ei.connection: assert ei.connection is not None call = ei.connection.calllog[0] assert call.name == "Disconnected" assert call.args["reason"] == EiConnection.EiDisconnectReason.ERROR assert call.args["explanation"] is not None # Now let's trigger a BrokenPipeError ei.send(bytes(16)) assert False, "The server should have disconnected us" except (ConnectionResetError, BrokenPipeError): pass eis.terminate() def test_connect_and_disconnect(self, eis): """ Connect to the server with a valid sequence, then disconnect once we get the connection object """ ei = eis.ei # drain any messages ei.dispatch() # Establish our connection ei.init_default_sender_connection() ei.wait_for_connection() # This should've set our connection object assert ei.connection is not None connection = ei.connection ei.send(connection.Disconnect()) try: # Send disconnect twice, just to test that case, should be ignored by the # server ei.send(connection.Disconnect()) ei.dispatch() time.sleep(0.1) ei.dispatch() except (ConnectionResetError, BrokenPipeError): pass ei.wait_for( lambda: bool([c for c in connection.calllog if c.name == "Disconnected"]) ) for call in connection.calllog: assert call.name != "Disconnected", "No disconnect event allowed here" try: ei.send(connection.Disconnect()) assert False, "Expected socket to be closed" except BrokenPipeError: pass eis.terminate() @pytest.mark.skipif( not getattr(int, "bit_count", None), reason="int.bit_count() required" ) def test_connect_receive_seat(self, eis): """ Ensure we get a seat object after setting our connection """ ei = eis.ei ei.dispatch() setup = ei.handshake # Establish our connection ei.send(setup.HandshakeVersion(VERSION_V(1))) ei.send(setup.ContextType(EiHandshake.EiContextType.SENDER)) ei.send(setup.Name("test client")) for interface in [ InterfaceName.EI_CONNECTION, InterfaceName.EI_CALLBACK, InterfaceName.EI_PINGPONG, InterfaceName.EI_POINTER, InterfaceName.EI_POINTER_ABSOLUTE, InterfaceName.EI_KEYBOARD, InterfaceName.EI_TOUCHSCREEN, ]: ei.send( setup.InterfaceVersion(interface, VERSION_V(1)) ) # these are required ei.send( setup.InterfaceVersion(InterfaceName.EI_SEAT, VERSION_V(100)) ) # excessive version ei.send( setup.InterfaceVersion(InterfaceName.EI_DEVICE, VERSION_V(100)) ) # excessive version ei.send(setup.Finish()) ei.dispatch() ei.wait_for_seat() assert ei.seats for seat in ei.seats: assert seat.version == 1 # we have 100, but the server only has 1 for call in seat.calllog: if call.name == "Capability": assert call.args["mask"].bit_count() == 1 assert InterfaceName(call.args["interface"]) if self.using_demo_server: all_caps = [ call.args["interface"] for call in seat.calllog if call.name == "Capability" ] assert sorted(all_caps) == sorted( [ i.value for i in ( InterfaceName.EI_POINTER, InterfaceName.EI_POINTER_ABSOLUTE, InterfaceName.EI_BUTTON, InterfaceName.EI_SCROLL, InterfaceName.EI_KEYBOARD, InterfaceName.EI_TOUCHSCREEN, ) ] ) for call in seat.calllog: if call.name == "Name": assert call.args["name"] is not None if self.using_demo_server: assert call.args["name"] == "default" break else: assert False, f"Expected ei_seat.name, but got none in {seat.calllog}" for call in seat.calllog: if call.name == "Done": break else: assert False, f"Expected ei_seat.done, but got none in {seat.calllog}" def test_connect_no_seat_without_ei_seat(self, eis): """ Ensure we do not get a seat object if we don't announce support for ei_seat """ ei = eis.ei ei.dispatch() setup = ei.handshake # Establish our connection ei.send(setup.HandshakeVersion(VERSION_V(1))) ei.send(setup.ContextType(EiHandshake.EiContextType.SENDER)) ei.send(setup.Name("test client")) for interface in [ InterfaceName.EI_CONNECTION, InterfaceName.EI_CALLBACK, InterfaceName.EI_PINGPONG, ]: ei.send( setup.InterfaceVersion(interface, VERSION_V(1)) ) # these are required # Do not announce ei_seat support ei.send(setup.Finish()) ei.dispatch() assert not ei.seats eis.terminate() def test_seat_bind_no_caps(self, eis): """ Ensure nothing happens if we bind to a seat with capabilities outside what is supported """ ei = eis.ei ei.dispatch() ei.init_default_sender_connection() ei.wait_for_seat() seat = ei.seats[0] ei.send(seat.Bind(0x00)) # binding to no caps is fine ei.dispatch() time.sleep(0.1) ei.dispatch() eis.terminate() def test_seat_bind_invalid_caps_expect_disconnection(self, eis): ei = eis.ei ei.dispatch() ei.init_default_sender_connection() ei.wait_for_seat() connection = ei.connection assert connection is not None seat = ei.seats[0] ei.send(seat.Bind(0x1)) # binding to invalid caps should get us disconnected try: ei.dispatch() time.sleep(0.1) ei.dispatch() for call in seat.calllog: if call.name == "Destroyed": break else: assert False, "Expected seat to get destroyed but didn't" for call in connection.calllog: if call.name == "Disconnected": assert call.args["reason"] == EiConnection.EiDisconnectReason.VALUE assert "Invalid capabilities" in call.args["explanation"] break else: assert False, "Expected disconnection event" except ConnectionResetError: pass eis.terminate() @pytest.mark.parametrize("bind_first", (True, False)) def test_seat_release_expect_destroyed(self, eis, bind_first): ei = eis.ei ei.dispatch() ei.init_default_sender_connection() ei.wait_for_seat() seat = ei.seats[0] have_seat_destroyed = False def destroyed_cb(_, serial): nonlocal have_seat_destroyed have_seat_destroyed = True seat.connect("Destroyed", destroyed_cb) if bind_first: ei.send( seat.Bind( seat.bind_mask( [ InterfaceName.EI_POINTER, InterfaceName.EI_BUTTON, InterfaceName.EI_SCROLL, ] ) ) ) ei.send(seat.Release()) ei.dispatch() ei.wait_for(lambda: have_seat_destroyed) def test_connection_sync(self, eis): """ Test the ei_connection.sync() callback mechanism """ ei = eis.ei ei.dispatch() ei.init_default_sender_connection() ei.wait_for_seat() cb = EiCallback.create(next(ei.object_ids), VERSION_V(1)) ei.context.register(cb) assert ei.connection is not None ei.send(ei.connection.Sync(cb.object_id, cb.version)) ei.dispatch() assert cb.calllog[0].name == "Done" assert cb.calllog[0].args["callback_data"] == 0 # hardcoded in libeis for now def test_invalid_object(self, eis): """ Send a message for an invalid object and ensure we get the event back """ ei = eis.ei ei.dispatch() ei.init_default_sender_connection() ei.wait_for_seat() seat: EiSeat = ei.seats[0] have_invalid_object_event = False have_sync = False def invalid_object_cb(_, last_serial, id): nonlocal have_invalid_object_event assert id == seat.object_id have_invalid_object_event = True ei.connection.connect("InvalidObject", invalid_object_cb) release = seat.Release() ei.send(release) ei.dispatch() cb = EiCallback.create(next(ei.object_ids), VERSION_V(1)) ei.context.register(cb) def sync_cb(_, unused): nonlocal have_sync have_sync = True cb.connect("Done", sync_cb) # Send the invalid object request ei.send(release) ei.send(ei.connection.Sync(cb.object_id, cb.version)) ei.wait_for(lambda: have_sync) assert have_invalid_object_event, "Expected invalid_object event, got none" def test_disconnect_before_setup_finish(self, eis): ei = eis.ei ei.dispatch() ei.send(ei.handshake.ContextType(EiHandshake.EiContextType.SENDER)) ei.sock.close() time.sleep(0.5) # Not much we can test here other than hoping the EIS implementation doesn't segfault @pytest.mark.parametrize( "missing_interface", ( InterfaceName.EI_CALLBACK, InterfaceName.EI_CONNECTION, InterfaceName.EI_PINGPONG, InterfaceName.EI_SEAT, InterfaceName.EI_DEVICE, InterfaceName.EI_POINTER, ), ) def test_connect_without_ei_interfaces(self, eis, missing_interface): ei = eis.ei ei.dispatch() setup = ei.handshake ei.send(setup.HandshakeVersion(VERSION_V(1))) ei.send(setup.ContextType(EiHandshake.EiContextType.SENDER)) ei.send(setup.Name("test client")) for iname in filter(lambda i: i != InterfaceName.EI_HANDSHAKE, InterfaceName): if iname != missing_interface: ei.send(setup.InterfaceVersion(iname, VERSION_V(1))) @attr.s class Status: connected: bool = attr.ib(default=False) disconnected: bool = attr.ib(default=False) seats: bool = attr.ib(default=False) devices: bool = attr.ib(default=False) status = Status() try: def on_device(seat, id, version, new_objects={}): assert missing_interface not in [InterfaceName.EI_DEVICE] status.devices = True def on_seat(connection, id, version, new_objects={}): assert missing_interface not in [InterfaceName.EI_SEAT] seat = new_objects["seat"] assert seat is not None seat.connect("Device", on_device) def on_done(seat): if missing_interface != InterfaceName.EI_POINTER: mask = seat.bind_mask([InterfaceName.EI_POINTER]) else: # Need to bind to *something* to get at least one device mask = seat.bind_mask([InterfaceName.EI_KEYBOARD]) ei.send(seat.Bind(mask)) seat.connect("Done", on_done) status.seats = True def on_disconnected(connection, last_serial, reason, explanation): assert missing_interface in [ InterfaceName.EI_SEAT, InterfaceName.EI_DEVICE, ] status.disconnected = True def on_connection(setup, serial, id, version, new_objects={}): # these three must be present, otherwise we get disconnected assert missing_interface not in [ InterfaceName.EI_CONNECTION, InterfaceName.EI_CALLBACK, InterfaceName.EI_PINGPONG, ] status.connected = True connection = new_objects["connection"] assert connection is not None connection.connect("Seat", on_seat) connection.connect("Disconnected", on_disconnected) setup.connect("Connection", on_connection) ei.send(setup.Finish()) ei.dispatch() if missing_interface in [ InterfaceName.EI_CONNECTION, InterfaceName.EI_CALLBACK, InterfaceName.EI_PINGPONG, ]: # valgrind is slow, so let's wait for it to catch up time.sleep(0.3) ei.dispatch() assert not status.connected assert not status.disconnected # we never get the Disconnected event ei.send(bytes(16)) assert False, "We should've been disconnected by now" ei.wait_for(lambda: status.connected) if missing_interface in [InterfaceName.EI_DEVICE, InterfaceName.EI_SEAT]: assert ei.wait_for(lambda: status.disconnected) assert ( status.disconnected ), f"Expected to be disconnected for missing {missing_interface}" else: assert ( missing_interface == InterfaceName.EI_POINTER ) # otherwise we shouldn't get here assert ei.callback_roundtrip(), "Callback roundtrip failed" assert status.connected assert not status.disconnected assert ei.wait_for(lambda: status.seats) assert ei.wait_for(lambda: status.devices) assert status.devices except BrokenPipeError: assert missing_interface in [ InterfaceName.EI_CONNECTION, InterfaceName.EI_CALLBACK, InterfaceName.EI_PINGPONG, ] @pytest.mark.parametrize("test_for", ("repeat-id", "invalid-id", "decreasing-id")) def test_invalid_object_id(self, eis, test_for): """ Expect to get disconnected if we allocate a client ID in the server ID range or if we allocate the same client id twice """ ei = eis.ei ei.dispatch() ei.init_default_sender_connection() ei.dispatch() ei.wait_for_connection() @attr.s class Status: disconnected: bool = attr.ib(default=False) reason: int = attr.ib(default=0) explanation: Optional[str] = attr.ib(default=None) status = Status() def on_disconnected(connection, last_serial, reason, explanation): status.disconnected = True status.reason = reason status.explanation = explanation ei.connection.connect("Disconnected", on_disconnected) if test_for == "invalid-id": # random id in the server range, 0xff..00 is used by the connection # and some of the next few ids might have been used by pingpongs cb = EiCallback.create(0xFF00000000000100, VERSION_V(1)) ei.context.register(cb) ei.send(ei.connection.Sync(cb.object_id, cb.version)) elif test_for == "repeat-id": cb = EiCallback.create(0x100, VERSION_V(1)) ei.context.register(cb) ei.send(ei.connection.Sync(cb.object_id, cb.version)) cb = EiCallback.create(0x100, VERSION_V(1)) ei.send(ei.connection.Sync(cb.object_id, cb.version)) elif test_for == "decreasing-id": cb = EiCallback.create(0x101, VERSION_V(1)) ei.context.register(cb) ei.send(ei.connection.Sync(cb.object_id, cb.version)) cb = EiCallback.create(0x100, VERSION_V(1)) ei.context.register(cb) ei.send(ei.connection.Sync(cb.object_id, cb.version)) else: assert False, "Unhandled test parameter" ei.wait_for(lambda: status.disconnected) assert status.disconnected assert ( status.reason == EiConnection.EiDisconnectReason.PROTOCOL ), status.explanation assert status.explanation is not None def test_invalid_callback_version(self, eis): """ Expect to get disconnected if we allocate a client object outside the agreed version range. Right now only callbacks are client-created, so that's all we can test here. """ ei = eis.ei ei.dispatch() ei.init_default_sender_connection() ei.dispatch() ei.wait_for_connection() @attr.s class Status: disconnected: bool = attr.ib(default=False) reason: int = attr.ib(default=0) explanation: Optional[str] = attr.ib(default=None) status = Status() def on_disconnected(connection, last_serial, reason, explanation): status.disconnected = True status.reason = reason status.explanation = explanation ei.connection.connect("Disconnected", on_disconnected) cb = EiCallback.create(0x100, VERSION_V(100)) ei.context.register(cb) ei.send(ei.connection.Sync(cb.object_id, cb.version)) ei.wait_for(lambda: status.disconnected) assert status.disconnected assert ( status.reason == EiConnection.EiDisconnectReason.PROTOCOL ), status.explanation assert status.explanation is not None @pytest.mark.parametrize( "wanted_interface", ( InterfaceName.EI_POINTER, InterfaceName.EI_KEYBOARD, InterfaceName.EI_TOUCHSCREEN, ), ) def test_connect_receive_device(self, eis, wanted_interface): """ Ensure we get a device object after binding to a seat """ ei = eis.ei @attr.s class Status: capability: Optional[Interface] = attr.ib(default=None) # type: ignore status = Status() def on_interface(device, object, name, version, new_objects): logger.debug( "new capability", device=device, object=object, name=name, version=version, ) if name == wanted_interface: status.capability = new_objects["object"] def on_new_device(seat, device, version, new_objects): logger.debug("new device", object=new_objects["device"]) new_objects["device"].connect("Interface", on_interface) def on_new_object(o: Interface): logger.debug("new object", object=o) if o.name == InterfaceName.EI_SEAT: ei.seat_fill_capability_masks(o) o.connect("Device", on_new_device) ei.context.connect("register", on_new_object) ei.dispatch() ei.init_default_sender_connection() ei.wait_for_seat() seat = ei.seats[0] ei.send( seat.Bind( seat.bind_mask( [ InterfaceName.EI_POINTER, InterfaceName.EI_POINTER_ABSOLUTE, InterfaceName.EI_KEYBOARD, InterfaceName.EI_TOUCHSCREEN, ] ) ) ) ei.wait_for(lambda: status.capability is not None) assert status.capability is not None assert status.capability.name == wanted_interface assert status.capability.version == VERSION_V(1) @pytest.mark.parametrize( "wanted_pointer", (InterfaceName.EI_POINTER, InterfaceName.EI_POINTER_ABSOLUTE), ) def test_connect_receive_pointer(self, eis, wanted_pointer): """ Ensure we get the correct pointer device after binding """ ei = eis.ei @attr.s class Status: pointers: dict[InterfaceName, Interface] = attr.ib(default=attr.Factory(dict)) # type: ignore all_caps: int = attr.ib(default=0) status = Status() def on_interface(device, object, name, version, new_objects): logger.debug( "new capability", device=device, object=object, name=name, version=version, ) if name in [InterfaceName.EI_POINTER, InterfaceName.EI_POINTER_ABSOLUTE]: status.pointers[InterfaceName(name)] = new_objects["object"] def on_new_device(seat, device, version, new_objects): logger.debug("new device", object=new_objects["device"]) new_objects["device"].connect("Interface", on_interface) def on_new_object(o: Interface): logger.debug("new object", object=o) if o.name == InterfaceName.EI_SEAT: ei.seat_fill_capability_masks(o) o.connect("Device", on_new_device) ei.context.connect("register", on_new_object) ei.dispatch() ei.init_default_sender_connection() ei.wait_for_seat() seat = ei.seats[0] ei.send(seat.Bind(seat.bind_mask([wanted_pointer]))) ei.wait_for(lambda: status.pointers) assert status.pointers[wanted_pointer] is not None assert len(status.pointers) == 1 libei-1.2.1/test/test_scanner.py000066400000000000000000000125511456005336000166260ustar00rootroot00000000000000#!/usr/bin/env python3 # We can't import ei-scanner, so let's go via this route. The proper # handling would be to have ei-scanner be the entry point for a ei_scanner # module but.. well, one day we'll do that maybe. import pytest try: from eiscanner import parse, scanner, Protocol except ImportError: print("Run tests from within the build directory") pytest.skip(allow_module_level=True) from pathlib import Path # set to the protocol file by meson protofile = "@PROTOFILE@" @pytest.fixture def protocol_xml() -> Path: return Path(protofile) @pytest.fixture def protocol(protocol_xml: Path, component: str) -> Protocol: print(f"protocol for component {component}") return parse(protocol_xml, component) @pytest.mark.skipif( protofile.startswith("@"), reason="Protocol XML file path invalid, run tests in the build dir", ) class TestScanner: @pytest.mark.parametrize("component", ("eis", "ei", "brei")) def test_ei_names(self, component: str, protocol: Protocol): for interface in protocol.interfaces: assert interface.name.startswith(component) assert not interface.plainname.startswith(component) assert not interface.plainname.startswith("ei_") # just some manual checks assert "handshake" in [i.plainname for i in protocol.interfaces] assert "connection" in [i.plainname for i in protocol.interfaces] assert "button" in [i.plainname for i in protocol.interfaces] @pytest.mark.parametrize("component", ("ei",)) def test_interface_arg(self, protocol: Protocol): intf = next((i for i in protocol.interfaces if i.name == "ei_device")) event = next((e for e in intf.events if e.name == "interface")) obj, interface_name, version = event.arguments assert obj.interface_arg == interface_name assert obj.interface_arg_for is None assert interface_name.interface_arg_for == obj assert interface_name.interface_arg is None assert version.interface_arg is None assert version.interface_arg_for is None def iterate_args(self, protocol: Protocol): for interface in protocol.interfaces: for request in interface.requests: for arg in request.arguments: yield interface, request, arg for event in interface.events: for arg in event.arguments: yield interface, event, arg @pytest.mark.parametrize("component", ("ei",)) def test_versione_arg(self, protocol: Protocol): for interface, message, arg in self.iterate_args(protocol): if arg.protocol_type == "new_id": if f"{interface.plainname}.{message.name}" not in [ "connection.sync", ]: assert ( arg.version_arg is not None ), f"{interface.name}.{message.name}::{arg.name}" assert ( arg.version_arg.name == "version" ), f"{interface.name}.{message.name}::{arg.name}" elif arg.name == "version": if f"{interface.plainname}.{message.name}" not in [ "handshake.handshake_version", "handshake.interface_version", ]: assert ( arg.version_arg_for is not None ), f"{interface.name}.{message.name}::{arg.name}" assert ( arg.version_arg_for.name != "version" ), f"{interface.name}.{message.name}::{arg.name}" else: assert ( arg.version_arg is None ), f"{interface.name}.{message.name}::{arg.name}" assert ( arg.version_arg_for is None ), f"{interface.name}.{message.name}::{arg.name}" @pytest.mark.parametrize("method", ("yamlfile", "jsonfile", "string")) def test_cli_extra_data(self, tmp_path, method): result_path = tmp_path / "result" tmpl_path = tmp_path / "template" with open(tmpl_path, "w") as template: template.write(">{{extra.foo}}<") if method == "yamlfile": extra_path = tmp_path / "extra_data.yml" with open(extra_path, "w") as extra_data: extra_data.write("foo: 'yes'") extra_data_arg = f"--jinja-extra-data-file={extra_path}" elif method == "jsonfile": extra_path = tmp_path / "extra_data.json" with open(extra_path, "w") as extra_data: extra_data.write('{"foo": "yes"}') extra_data_arg = f"--jinja-extra-data-file={extra_path}" elif method == "string": extra_data = '{"foo": "yes"}' extra_data_arg = f"--jinja-extra-data={extra_data}" else: pytest.fail(f"Unsupported method {method}") try: scanner( [ f"--output={result_path}", extra_data_arg, protofile, str(tmpl_path), ] ) except SystemExit as e: pytest.fail(reason=f"Unexpected system exit code {e}") assert result_path.exists() with open(result_path) as fd: result = fd.read() assert result == ">yes<" libei-1.2.1/test/unit-tests.c000066400000000000000000000024331456005336000160450ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "util-munit.h" int main(int argc, char **argv) { return munit_tests_run(argc, argv); } libei-1.2.1/tools/000077500000000000000000000000001456005336000137415ustar00rootroot00000000000000libei-1.2.1/tools/ei-debug-events.c000066400000000000000000000263641456005336000171030ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2022 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. */ /* A simple tool that provides a libei client that sends a fixed set of * events every second. * * This tool is useful for testing EIS implementations, to make sure we can * a connection, we receive devices and that we can send events. * * Usually, you'd want to: * - run the eis-demo-server (or some other EIS implementation) * - export LIBEI_SOCKET=eis-0, or whatever value was given * - run the ei-demo-client */ #include "config.h" #include #include #include #include #include #include #include #if HAVE_LIBEVDEV #include #else #define libevdev_event_code_get_name(...) "" #endif #include "libei.h" #include "src/util-macros.h" #include "src/util-mem.h" #include "src/util-io.h" DEFINE_UNREF_CLEANUP_FUNC(ei); DEFINE_UNREF_CLEANUP_FUNC(ei_event); #define truefalse(v_) (v_) ? "true" : "false" static void usage(FILE *fp, const char *argv0) { fprintf(fp, "Usage: %s [--verbose] [--socketfd=]\n" "\n" "Start an EI client and print communication from the EIS implementation in YAML format.\n" "The client will bind to all available capabilities on all seats.\n" "\n" "By default, this client connects to $LIBEI_SOCKET if set or $XDG_RUNTIME_DIR/eis-0\n" "\n" "Options:\n" " --socketfd Use the given fd as socket to the EIS implementation\n" " --verbose Enable debugging output\n" " --receiver Enable receiver mode\n" " --sender Enable sender mode (default)\n" "", argv0); } static void print_header(void) { printf("ei:\n"); printf(" version: %s\n", EI_VERSION); } static void print_event_header(struct ei_event *event) { run_only_once { printf("events:\n"); } printf("- type: %s\n", ei_event_type_to_string(ei_event_get_type(event))); } static void print_seat_event(struct ei_event *event) { struct ei_seat *seat = ei_event_get_seat(event); char *caps[6] = {NULL}; size_t idx = 0; if (ei_seat_has_capability(seat, EI_DEVICE_CAP_POINTER)) caps[idx++] = "pointer"; if (ei_seat_has_capability(seat, EI_DEVICE_CAP_POINTER_ABSOLUTE)) caps[idx++] = "pointer-absolute"; if (ei_seat_has_capability(seat, EI_DEVICE_CAP_KEYBOARD)) caps[idx++] = "keyboard"; if (ei_seat_has_capability(seat, EI_DEVICE_CAP_TOUCH)) caps[idx++] = "touch"; _cleanup_free_ char *capabilities = strv_join(caps, ", "); printf(" seat: %s\n", ei_seat_get_name(seat)); printf(" capabilities: [%s]\n", capabilities); } static void print_device(struct ei_event *event) { struct ei_device *device = ei_event_get_device(event); struct ei_seat *seat = ei_event_get_seat(event); printf(" device: %s\n", ei_device_get_name(device)); printf(" seat: %s\n", ei_seat_get_name(seat)); } static void print_device_details(struct ei_event *event) { struct ei_device *device = ei_event_get_device(event); print_device(event); char *caps[6] = {NULL}; size_t idx = 0; if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) caps[idx++] = "pointer"; if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) caps[idx++] = "pointer-absolute"; if (ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) caps[idx++] = "keyboard"; if (ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)) caps[idx++] = "touch"; _cleanup_free_ char *capabilities = strv_join(caps, ", "); printf(" type: %s\n", ei_device_get_type(device) == EI_DEVICE_TYPE_VIRTUAL ? "virtual" : "physical"); printf(" capabilities: [%s]\n", capabilities); idx = 0; struct ei_region *region; while ((region = ei_device_get_region(device, idx++))) { if (idx == 1) printf(" regions:\n"); uint32_t w = ei_region_get_width(region); uint32_t h = ei_region_get_height(region); uint32_t x = ei_region_get_x(region); uint32_t y = ei_region_get_y(region); double scale = ei_region_get_physical_scale(region); const char *mapping_id = ei_region_get_mapping_id(region); printf(" - { x: %u, y: %u, w: %u, h: %u, scale: %.2f, mapping_id: '%s' }\n", x, y, w, h, scale, mapping_id ? mapping_id : ""); } struct ei_keymap *keymap = ei_device_keyboard_get_keymap(device); if (keymap) { switch (ei_keymap_get_type(keymap)) { case EI_KEYMAP_TYPE_XKB: printf(" keymap: xkb\n"); break; } } } static void print_motion_event(struct ei_event *event) { print_device(event); double dx = ei_event_pointer_get_dx(event); double dy = ei_event_pointer_get_dy(event); printf(" motion: [%f, %f]\n", dx, dy); } static void print_abs_event(struct ei_event *event) { print_device(event); double x = ei_event_pointer_get_absolute_x(event); double y = ei_event_pointer_get_absolute_y(event); printf(" position: [%f, %f]\n", x, y); } static void print_button_event(struct ei_event *event) { print_device(event); uint32_t button = ei_event_button_get_button(event); bool press = ei_event_button_get_is_press(event); printf(" button: %u # %s\n", button, libevdev_event_code_get_name(EV_KEY, button)); printf(" press: %s\n", press ? "true" : "false"); } static void print_key_event(struct ei_event *event) { print_device(event); uint32_t key = ei_event_keyboard_get_key(event); bool press = ei_event_keyboard_get_key_is_press(event); printf(" key: %u # %s\n", key, libevdev_event_code_get_name(EV_KEY, key)); printf(" press: %s\n", press ? "true" : "false"); } static void print_touch_event(struct ei_event *event) { print_device(event); uint32_t touchid = ei_event_touch_get_id(event); printf(" touchid: %u\n", touchid); if (ei_event_get_type(event) != EI_EVENT_TOUCH_UP) { double x = ei_event_touch_get_x(event); double y = ei_event_touch_get_y(event); printf(" position: [%f, %f]\n", x, y); } } static void print_scroll_event(struct ei_event *event) { print_device(event); double x = ei_event_scroll_get_dx(event); double y = ei_event_scroll_get_dy(event); printf(" scroll: [%f, %f]\n", x, y); } static void print_scroll_discrete_event(struct ei_event *event) { print_device(event); int32_t x = ei_event_scroll_get_discrete_dx(event); int32_t y = ei_event_scroll_get_discrete_dy(event); printf(" scroll: [%d, %d]\n", x, y); } static void print_scroll_stop_event(struct ei_event *event) { print_device(event); bool x = ei_event_scroll_get_stop_x(event); bool y = ei_event_scroll_get_stop_y(event); printf(" scroll: [%s, %s]\n", truefalse(x), truefalse(y)); } static void print_modifiers_event(struct ei_event *event) { print_device(event); printf(" group: %u", ei_event_keyboard_get_xkb_group(event)); printf(" depressed: \"0x%x\"", ei_event_keyboard_get_xkb_mods_depressed(event)); printf(" latched: \"0x%x\"", ei_event_keyboard_get_xkb_mods_latched(event)); printf(" locked: \"0x%x\"", ei_event_keyboard_get_xkb_mods_locked(event)); } int main(int argc, char **argv) { enum { MODE_RECEIVER, MODE_SENDER, } mode = MODE_SENDER; bool verbose = false; _cleanup_close_ int socketfd = -1; while (1) { enum { OPT_SOCKETFD, OPT_VERBOSE, OPT_RECEIVER, OPT_SENDER, }; static struct option long_opts[] = { {"socketfd", required_argument, 0, OPT_SOCKETFD}, {"verbose", no_argument, 0, OPT_VERBOSE}, {"receiver", no_argument, 0, OPT_RECEIVER}, {"sender", no_argument, 0, OPT_SENDER}, {"help", no_argument, 0, 'h'}, {.name = NULL}, }; int optind = 0; int c = getopt_long(argc, argv, "h", long_opts, &optind); if (c == -1) break; switch(c) { case 'h': usage(stdout, argv[0]); return EXIT_SUCCESS; case OPT_VERBOSE: verbose = true; break; case OPT_SOCKETFD: if (!xatoi(optarg, &socketfd)) { fprintf(stderr, "Invalid socketfd: %s", optarg); return 2; } break; case OPT_SENDER: mode = MODE_SENDER; break; case OPT_RECEIVER: mode = MODE_RECEIVER; break; default: usage(stderr, argv[0]); return EXIT_FAILURE; } } _unref_(ei) *ei = NULL; if (mode == MODE_RECEIVER) ei = ei_new_receiver(NULL); else ei = ei_new_sender(NULL); if (verbose) ei_log_set_priority(ei, EI_LOG_PRIORITY_DEBUG); ei_configure_name(ei, "ei-debug-events"); int rc = -EINVAL; if (socketfd == -1) { const char SOCKETNAME[] = "eis-0"; rc = ei_setup_backend_socket(ei, getenv("LIBEI_SOCKET") ? NULL : SOCKETNAME); } else { rc = ei_setup_backend_fd(ei, socketfd); } if (rc != 0) { fprintf(stderr, "Failed to setup backend: %s\n", strerror(errno)); return 1; } print_header(); struct pollfd fds = { .fd = ei_get_fd(ei), .events = POLLIN, .revents = 0, }; while (poll(&fds, 1, 2000) > -1) { ei_dispatch(ei); while (true) { _unref_(ei_event) *e = ei_get_event(ei); if (!e) break; struct ei_seat *seat = ei_event_get_seat(e); print_event_header(e); switch(ei_event_get_type(e)) { case EI_EVENT_CONNECT: break; case EI_EVENT_DISCONNECT: goto finished; case EI_EVENT_SEAT_ADDED: ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_POINTER_ABSOLUTE, EI_DEVICE_CAP_KEYBOARD, EI_DEVICE_CAP_TOUCH, EI_DEVICE_CAP_BUTTON, EI_DEVICE_CAP_SCROLL, NULL); _fallthrough_; case EI_EVENT_SEAT_REMOVED: print_seat_event(e); break; case EI_EVENT_DEVICE_ADDED: print_device_details(e); break; case EI_EVENT_DEVICE_REMOVED: case EI_EVENT_DEVICE_RESUMED: case EI_EVENT_DEVICE_PAUSED: case EI_EVENT_DEVICE_START_EMULATING: case EI_EVENT_DEVICE_STOP_EMULATING: case EI_EVENT_FRAME: print_device(e); break; case EI_EVENT_POINTER_MOTION: print_motion_event(e); break; case EI_EVENT_POINTER_MOTION_ABSOLUTE: print_abs_event(e); break; case EI_EVENT_BUTTON_BUTTON: print_button_event(e); break; case EI_EVENT_KEYBOARD_KEY: print_key_event(e); break; case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_MOTION: case EI_EVENT_TOUCH_UP: print_touch_event(e); break; case EI_EVENT_SCROLL_DELTA: print_scroll_event(e); break; case EI_EVENT_SCROLL_DISCRETE: print_scroll_discrete_event(e); break; case EI_EVENT_SCROLL_STOP: case EI_EVENT_SCROLL_CANCEL: print_scroll_stop_event(e); break; case EI_EVENT_KEYBOARD_MODIFIERS: print_modifiers_event(e); break; } } } finished: return 0; } libei-1.2.1/tools/ei-demo-client.c000066400000000000000000000367241456005336000167140ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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. */ /* A simple tool that provides a libei client that sends a fixed set of * events every second. * * This tool is useful for testing EIS implementations, to make sure we can * a connection, we receive devices and that we can send events. * * Usually, you'd want to: * - run the eis-demo-server (or some other EIS implementation) * - export LIBEI_SOCKET=eis-0, or whatever value was given * - run the ei-demo-client */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #if HAVE_LIBXKBCOMMON #include #endif #include "libei.h" #include "src/util-macros.h" #include "src/util-mem.h" #include "src/util-memfile.h" #include "src/util-color.h" #include "src/util-strings.h" #include "src/util-time.h" DEFINE_UNREF_CLEANUP_FUNC(ei); DEFINE_UNREF_CLEANUP_FUNC(ei_device); DEFINE_UNREF_CLEANUP_FUNC(ei_event); static inline void _printf_(1, 2) colorprint(const char *format, ...) { static uint64_t color = 0; run_only_once { color = rgb(1, 1, 1) | rgb_bg(230, 0, 230); } cprintf(color, "EI socket client: "); va_list args; va_start(args, format); vprintf(format, args); va_end(args); } #if HAVE_LIBXKBCOMMON DEFINE_UNREF_CLEANUP_FUNC(xkb_context); DEFINE_UNREF_CLEANUP_FUNC(xkb_keymap); DEFINE_UNREF_CLEANUP_FUNC(xkb_state); #endif static void setup_xkb_keymap(struct ei_keymap *keymap) { #if HAVE_LIBXKBCOMMON if (!keymap) return; _unref_(xkb_context) *ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!ctx) return; size_t sz = ei_keymap_get_size(keymap); _cleanup_free_ char *buf = xalloc(sz + 1); read(ei_keymap_get_fd(keymap), buf, sz); buf[sz] = '\0'; _unref_(xkb_keymap) *xkbmap = xkb_keymap_new_from_string(ctx, buf, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!xkbmap) return; _unref_(xkb_state) *xkbstate = xkb_state_new(xkbmap); if (!xkbstate) return; char layout[6 * 7 + 1] = {0}; /* 6 keys, 7 bytes per key min */ for (unsigned int evcode = KEY_Q; evcode <= KEY_Y; evcode++) { char utf8[7]; xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkbstate, evcode + 8); xkb_keysym_to_utf8(keysym, utf8, sizeof(utf8)); strcat(layout, utf8); } colorprint("XKB keymap: %s\n", layout); #endif } static void handle_keymap(struct ei_event *event) { struct ei_device *device = ei_event_get_device(event); if (!ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) return; struct ei_keymap *keymap = ei_device_keyboard_get_keymap(device); if (!keymap) return; enum ei_keymap_type type = ei_keymap_get_type(keymap); switch (type) { case EI_KEYMAP_TYPE_XKB: setup_xkb_keymap(keymap); break; } } static void handle_regions(struct ei_device *device) { uint32_t idx = 0; struct ei_region *r; while ((r = ei_device_get_region (device, idx++))) { int x, y, w, h; x = ei_region_get_x(r); y = ei_region_get_y(r); w = ei_region_get_width(r); h = ei_region_get_height(r); colorprint("%s has region %dx%d@%d,%d\n", ei_device_get_name(device), w, h, x, y); } } static void usage(FILE *fp, const char *argv0) { fprintf(fp, "Usage: %s [--verbose] [--socket|--portal] [--busname=a.b.c.d] [--layout=us] [--interval=2000]\n" "\n" "Start an EI demo client. The client will connect to EIS\n" "with the chosen backend (default: socket) and emulate pointer\n" "and keyboard events in a loop.\n" "\n" "Options:\n" " --socket Use the socket backend. The socket path is $LIBEI_SOCKET if set, \n" " otherwise $XDG_RUNTIME_DIR/eis-0\n" " --portal Use the portal backend.\n" " --busname Use the given busname (default: org.freedesktop.portal.Desktop)\n" " --verbose Enable debugging output\n" " --receiver Create a receiver EIS context, receiving events instead of sending them\n" " --interval Interval in milliseconds between polling\n" " --iterations Limit the number of iterations and disconnect once reached\n" "", argv0); } int main(int argc, char **argv) { enum { SOCKET, PORTAL, } backend = SOCKET; bool verbose = false; bool receiver = false; unsigned int interval = 2000; uint32_t iterations = UINT32_MAX; _cleanup_free_ char *busname = xstrdup("org.freedesktop.portal.Desktop"); while (1) { enum { OPT_BACKEND_SOCKET, OPT_BACKEND_PORTAL, OPT_BUSNAME, OPT_VERBOSE, OPT_RECEIVER, OPT_INTERVAL, OPT_ITERATIONS, }; static struct option long_opts[] = { {"socket", no_argument, 0, OPT_BACKEND_SOCKET}, {"portal", no_argument, 0, OPT_BACKEND_PORTAL}, {"busname", required_argument, 0, OPT_BUSNAME}, {"verbose", no_argument, 0, OPT_VERBOSE}, {"receiver", no_argument, 0, OPT_RECEIVER}, {"interval", required_argument, 0, OPT_INTERVAL}, {"iterations", required_argument, 0, OPT_ITERATIONS}, {"help", no_argument, 0, 'h'}, {.name = NULL}, }; int optind = 0; int c = getopt_long(argc, argv, "h", long_opts, &optind); if (c == -1) break; switch(c) { case 'h': usage(stdout, argv[0]); return EXIT_SUCCESS; case OPT_VERBOSE: verbose = true; break; case OPT_BACKEND_SOCKET: backend = SOCKET; break; case OPT_BACKEND_PORTAL: backend = PORTAL; break; case OPT_BUSNAME: free(busname); busname = xstrdup(optarg); break; case OPT_RECEIVER: receiver = true; break; case OPT_INTERVAL: interval = atoi(optarg); break; case OPT_ITERATIONS: iterations = atoi(optarg); break; default: usage(stderr, argv[0]); return EXIT_FAILURE; } } _unref_(ei) *ei = NULL; if (receiver) ei = ei_new_receiver(NULL); else ei = ei_new_sender(NULL); assert(ei); if (verbose) ei_log_set_priority(ei, EI_LOG_PRIORITY_DEBUG); ei_configure_name(ei, "ei-demo-client"); int rc = -EINVAL; if (backend == SOCKET) { const char SOCKETNAME[] = "eis-0"; colorprint("connecting to %s\n", SOCKETNAME); rc = ei_setup_backend_socket(ei, getenv("LIBEI_SOCKET") ? NULL : SOCKETNAME); } else if (backend == PORTAL) { #if ENABLE_LIBEI_PORTAL colorprint("connecting to %s\n", busname); rc = ei_setup_backend_portal_busname(ei, busname); #endif } if (rc != 0) { fprintf(stderr, "init failed: %s\n", strerror(-rc)); return 1; } struct pollfd fds = { .fd = ei_get_fd(ei), .events = POLLIN, .revents = 0, }; _unref_(ei_device) *ptr = NULL; _unref_(ei_device) *kbd = NULL; _unref_(ei_device) *abs = NULL; _unref_(ei_device) *touch = NULL; bool stop = false; bool have_ptr = false; bool have_kbd = false; bool have_abs = false; bool have_touch = false; struct ei_seat *default_seat = NULL; uint32_t sequence = 0; uint32_t iteration = 0; while (!stop && poll(&fds, 1, interval) > -1) { ++iteration; ei_dispatch(ei); while (!stop) { _unref_(ei_event) *e = ei_get_event(ei); if (!e) break; switch(ei_event_get_type(e)) { case EI_EVENT_CONNECT: colorprint("connected\n"); break; case EI_EVENT_DISCONNECT: { colorprint("disconnected us\n"); stop = true; break; } case EI_EVENT_SEAT_ADDED: { if (default_seat) { colorprint("ignoring other seats\n"); break; } default_seat = ei_seat_ref(ei_event_get_seat(e)); colorprint("seat added: %s\n", ei_seat_get_name(default_seat)); ei_seat_bind_capabilities(default_seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, EI_DEVICE_CAP_POINTER_ABSOLUTE, EI_DEVICE_CAP_TOUCH, EI_DEVICE_CAP_BUTTON, EI_DEVICE_CAP_SCROLL, NULL); break; } case EI_EVENT_SEAT_REMOVED: /* Don't need to close the devices, libei will * give us the right events */ if (ei_event_get_seat(e) == default_seat) default_seat = ei_seat_unref(default_seat); break; case EI_EVENT_DEVICE_ADDED: { struct ei_device *device = ei_event_get_device(e); if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER)) { colorprint("New pointer device: %s\n", ei_device_get_name(device)); ptr = ei_device_ref(device); } if (ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) { colorprint("New keyboard device: %s\n", ei_device_get_name(device)); kbd = ei_device_ref(device); handle_keymap(e); } if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE)) { colorprint("New abs pointer device: %s\n", ei_device_get_name(device)); abs = ei_device_ref(device); handle_regions(device); } if (ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)) { colorprint("New touch device: %s\n", ei_device_get_name(device)); touch = ei_device_ref(device); handle_regions(device); } } break; case EI_EVENT_DEVICE_RESUMED: if (ei_event_get_device(e) == ptr) { if (!receiver) ei_device_start_emulating(ptr, ++sequence); colorprint("Pointer device was resumed\n"); have_ptr = true; } if (ei_event_get_device(e) == kbd) { if (!receiver) ei_device_start_emulating(kbd, ++sequence); colorprint("Keyboard device was resumed\n"); have_kbd = true; } if (ei_event_get_device(e) == abs) { if (!receiver) ei_device_start_emulating(abs, ++sequence); colorprint("Abs pointer device was resumed\n"); have_abs = true; } if (ei_event_get_device(e) == touch) { if (!receiver) ei_device_start_emulating(touch, ++sequence); colorprint("Touch device was resumed\n"); have_touch = true; } break; case EI_EVENT_DEVICE_PAUSED: if (ei_event_get_device(e) == ptr) { colorprint("Pointer device was paused\n"); have_ptr = false; } if (ei_event_get_device(e) == kbd) { colorprint("Keyboard device was paused\n"); have_kbd = false; } if (ei_event_get_device(e) == abs) { colorprint("Abs pointer device was paused\n"); have_abs = false; } if (ei_event_get_device(e) == touch) { colorprint("Touch device was paused\n"); have_touch = false; } break; case EI_EVENT_DEVICE_REMOVED: { colorprint("our device was removed\n"); break; } case EI_EVENT_FRAME: break; case EI_EVENT_DEVICE_START_EMULATING: { struct ei_device *device = ei_event_get_device(e); colorprint("Device %s is ready to send events\n", ei_device_get_name(device)); } break; case EI_EVENT_DEVICE_STOP_EMULATING: { struct ei_device *device = ei_event_get_device(e); colorprint("Device %s will no longer send events\n", ei_device_get_name(device)); } break; case EI_EVENT_POINTER_MOTION: { colorprint("motion by %.2f/%.2f\n", ei_event_pointer_get_dx(e), ei_event_pointer_get_dy(e)); } break; case EI_EVENT_POINTER_MOTION_ABSOLUTE: { colorprint("absmotion to %.2f/%.2f\n", ei_event_pointer_get_absolute_x(e), ei_event_pointer_get_absolute_y(e)); } break; case EI_EVENT_BUTTON_BUTTON: { colorprint("button %u (%s)\n", ei_event_button_get_button(e), ei_event_button_get_is_press(e) ? "press" : "release"); } break; case EI_EVENT_SCROLL_DELTA: { colorprint("scroll %.2f/%.2f\n", ei_event_scroll_get_dx(e), ei_event_scroll_get_dy(e)); } break; case EI_EVENT_SCROLL_DISCRETE: { colorprint("scroll discrete %d/%d\n", ei_event_scroll_get_discrete_dx(e), ei_event_scroll_get_discrete_dy(e)); } break; case EI_EVENT_KEYBOARD_KEY: { colorprint("key %u (%s)\n", ei_event_keyboard_get_key(e), ei_event_keyboard_get_key_is_press(e) ? "press" : "release"); } break; case EI_EVENT_TOUCH_DOWN: case EI_EVENT_TOUCH_MOTION: { colorprint("touch %s %u %.2f/%.2f\n", ei_event_get_type(e) == EI_EVENT_TOUCH_DOWN ? "down" : "motion", ei_event_touch_get_id(e), ei_event_touch_get_x(e), ei_event_touch_get_y(e)); } break; case EI_EVENT_TOUCH_UP: { colorprint("touch up %u\n", ei_event_touch_get_id(e)); } break; default: abort(); } } if (iteration >= iterations || stop) break; if (!receiver) { uint64_t now = ei_now(ei); uint64_t interval = ms2us(10); /* pretend events are 10ms apart */ colorprint("now: %" PRIu64 "\n", now); if (have_ptr) { colorprint("sending motion event\n"); ei_device_pointer_motion(ptr, -1, 1); /* BTN_LEFT */ colorprint("sending button event\n"); ei_device_button_button(ptr, BTN_LEFT, true); ei_device_frame(ptr, now); now += interval; ei_device_button_button(ptr, BTN_LEFT, false); ei_device_frame(ptr, now); now += interval; colorprint("sending scroll events\n"); ei_device_scroll_delta(ptr, 1, 1); ei_device_frame(ptr, now); now += interval; ei_device_scroll_discrete(ptr, 120, 120); ei_device_frame(ptr, now); now += interval; } if (have_kbd) { static int key = 0; colorprint("sending key event\n"); ei_device_keyboard_key(kbd, KEY_Q + key, true); /* KEY_Q */ ei_device_frame(kbd, now); now += interval; ei_device_keyboard_key(kbd, KEY_Q + key, false); /* KEY_Q */ ei_device_frame(kbd, now); now += interval; key = (key + 1) % 6; } if (have_abs) { static int x, y; colorprint("sending abs event\n"); ei_device_pointer_motion_absolute(abs, 150 + ++x, 150 - ++y); ei_device_frame(abs, now); now += interval; } if (have_touch) { static int x, y; static int counter = 0; static struct ei_touch *t; switch (counter++ % 5) { case 0: colorprint("sending touch down event\n"); t = ei_device_touch_new(touch); ei_touch_down(t, 100 + ++x, 200 - ++y); ei_device_frame(touch, now); break; case 4: colorprint("sending touch down event\n"); ei_touch_up(t); ei_device_frame(touch, now); t = ei_touch_unref(t); break; default: ei_touch_motion(t, 100 + ++x, 200 - ++y); ei_device_frame(touch, now); break; } } } } colorprint("shutting down\n"); if (ptr) ei_device_close(ptr); if (kbd) ei_device_close(kbd); if (abs) ei_device_close(abs); if (touch) ei_device_close(touch); if (default_seat) { ei_seat_bind_capabilities(default_seat, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_KEYBOARD, EI_DEVICE_CAP_POINTER_ABSOLUTE, EI_DEVICE_CAP_TOUCH, EI_DEVICE_CAP_BUTTON, EI_DEVICE_CAP_SCROLL, NULL); ei_seat_unref(default_seat); } ei = ei_unref(ei); return 0; } libei-1.2.1/tools/eis-demo-server-uinput.c000066400000000000000000000211571456005336000204430ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 #include "util-color.h" #include "util-mem.h" #include "util-strings.h" #include "eis-demo-server.h" DEFINE_TRIVIAL_CLEANUP_FUNC(struct libevdev *, libevdev_free); DEFINE_TRIVIAL_CLEANUP_FUNC(struct libevdev_uinput *, libevdev_uinput_destroy); #define _cleanup_libevdev_ _cleanup_(libevdev_freep) #define _cleanup_libevdev_uinput_ _cleanup_(libevdev_uinput_destroyp) DEFINE_UNREF_CLEANUP_FUNC(eis_seat); DEFINE_UNREF_CLEANUP_FUNC(eis_device); static inline void _printf_(1, 2) colorprint(const char *format, ...) { static uint64_t color = 0; run_only_once { color = rgb(255, 255, 255) | rgb_bg(20, 70, 0); } cprintf(color, "EIS uinput server:"); printf(" "); va_list args; va_start(args, format); vprintf(format, args); va_end(args); } struct uinput_context { struct libevdev_uinput *ptr; struct libevdev_uinput *kbd; }; static int create_mouse(struct eis_demo_server *server, struct eis_seat *seat, struct eis_device **device_return) { struct eis_client *client = eis_seat_get_client(seat); _cleanup_free_ char *devicename = xaprintf("%s pointer", eis_client_get_name(client)); _unref_(eis_device) *device = eis_seat_new_device(seat); eis_device_configure_name(device, devicename); eis_device_configure_capability(device, EIS_DEVICE_CAP_POINTER); eis_device_configure_capability(device, EIS_DEVICE_CAP_BUTTON); eis_device_configure_capability(device, EIS_DEVICE_CAP_SCROLL); _cleanup_libevdev_ struct libevdev *dev = libevdev_new(); libevdev_set_name(dev, devicename); libevdev_enable_event_code(dev, EV_REL, REL_X, NULL); libevdev_enable_event_code(dev, EV_REL, REL_Y, NULL); libevdev_enable_event_code(dev, EV_KEY, BTN_LEFT, NULL); libevdev_enable_event_code(dev, EV_KEY, BTN_MIDDLE, NULL); libevdev_enable_event_code(dev, EV_KEY, BTN_RIGHT, NULL); libevdev_enable_event_code(dev, EV_KEY, BTN_SIDE, NULL); libevdev_enable_event_code(dev, EV_KEY, BTN_EXTRA, NULL); libevdev_enable_event_code(dev, EV_KEY, BTN_FORWARD, NULL); libevdev_enable_event_code(dev, EV_KEY, BTN_BACK, NULL); _cleanup_libevdev_uinput_ struct libevdev_uinput *uinput = NULL; int err = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uinput); if (err == 0) { colorprint("Pointer device is %s\n", libevdev_uinput_get_devnode(uinput)); eis_device_set_user_data(device, steal(&uinput)); *device_return = steal(&device); } return err; } static int create_keyboard(struct eis_demo_server *server, struct eis_seat *seat, struct eis_device **device_return) { struct eis_client *client = eis_seat_get_client(seat); _cleanup_free_ char *devicename = xaprintf("%s keyboard", eis_client_get_name(client)); _unref_(eis_device) *device = eis_seat_new_device(seat); eis_device_configure_name(device, devicename); eis_device_configure_capability(device, EIS_DEVICE_CAP_KEYBOARD); _cleanup_libevdev_ struct libevdev *dev = libevdev_new(); libevdev_set_name(dev, devicename); for (unsigned code = 0; code <= KEY_MICMUTE; code++) libevdev_enable_event_code(dev, EV_KEY, code, NULL); _cleanup_libevdev_uinput_ struct libevdev_uinput *uinput = NULL; int err = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uinput); if (err == 0) { colorprint("Keyboard device is %s\n", libevdev_uinput_get_devnode(uinput)); eis_device_set_user_data(device, steal(&uinput)); *device_return = steal(&device); } return err; } static int eis_demo_server_uinput_handle_event(struct eis_demo_server *server, struct eis_event *e) { switch(eis_event_get_type(e)) { case EIS_EVENT_CLIENT_CONNECT: { struct eis_client *client = eis_event_get_client(e); eis_client_connect(client); colorprint("new client: %s, accepting. creating new seat 'default'\n", eis_client_get_name(client)); _unref_(eis_seat) *seat = eis_client_new_seat(client, "default"); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER_ABSOLUTE); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_KEYBOARD); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TOUCH); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_BUTTON); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_SCROLL); eis_seat_add(seat); break; } case EIS_EVENT_CLIENT_DISCONNECT: { struct eis_client *client = eis_event_get_client(e); colorprint("client %s disconnected\n", eis_client_get_name(client)); eis_client_disconnect(client); break; } case EIS_EVENT_SEAT_BIND: { struct eis_seat *seat = eis_event_get_seat(e); /* FIXME: does not handle device removal */ if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER)) { struct eis_device *device = NULL; int rc = create_mouse(server, seat, &device); if (rc != 0) { colorprint("Failed to create device: %s\n", strerror(-rc)); return rc; } eis_device_add(device); eis_device_resume(device); } if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD)) { struct eis_device *device = NULL; int rc = create_keyboard(server, seat, &device); if (rc != 0) { colorprint("Failed to create device: %s\n", strerror(-rc)); return rc; } eis_device_add(device); eis_device_resume(device); } /* Note: our device has a dangling ref here. It's a * demo-server so we can just ignore that */ } break; case EIS_EVENT_DEVICE_CLOSED: { struct eis_device *device = eis_event_get_device(e); colorprint("device closed\n"); struct libevdev_uinput *uinput = eis_device_get_user_data(device); if (uinput) libevdev_uinput_destroy(uinput); eis_device_remove(device); eis_device_unref(device); /* because we know we have a dangling ref */ break; } case EIS_EVENT_POINTER_MOTION: { /* Note: we drop subpixel here */ struct eis_device *device = eis_event_get_device(e); int x = eis_event_pointer_get_dx(e), y = eis_event_pointer_get_dy(e); colorprint("REL_X %d, REL_Y %d\n", x, y); struct libevdev_uinput *uinput = eis_device_get_user_data(device); if (uinput) { libevdev_uinput_write_event(uinput, EV_REL, REL_X, x); libevdev_uinput_write_event(uinput, EV_REL, REL_Y, y); libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); } } break; case EIS_EVENT_BUTTON_BUTTON: { struct eis_device *device = eis_event_get_device(e); uint32_t button = eis_event_button_get_button(e); bool state = eis_event_button_get_is_press(e); colorprint("%s %s\n", libevdev_event_code_get_name(EV_KEY, button), state ? "press" : "release"); struct libevdev_uinput *uinput = eis_device_get_user_data(device); if (uinput) { libevdev_uinput_write_event(uinput, EV_KEY, button, state ? 1 : 0); libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); } } break; case EIS_EVENT_KEYBOARD_KEY: { struct eis_device *device = eis_event_get_device(e); uint32_t key = eis_event_keyboard_get_key(e); bool state = eis_event_keyboard_get_key_is_press(e); colorprint("%s %s\n", libevdev_event_code_get_name(EV_KEY, key), state ? "press" : "release"); struct libevdev_uinput *uinput = eis_device_get_user_data(device); if (uinput) { libevdev_uinput_write_event(uinput, EV_KEY, key, state ? 1 : 0); libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0); } } break; default: abort(); } return 0; } int eis_demo_server_setup_uinput_handler(struct eis_demo_server *server) { struct uinput_context *ctx = xalloc(sizeof *ctx); server->handler.data = ctx; server->handler.handle_event = eis_demo_server_uinput_handle_event; return 0; } libei-1.2.1/tools/eis-demo-server.c000066400000000000000000000530101456005336000171120ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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. */ /* A simple tool that provides an EIS implementation that prints incoming * events. * * This tool is useful for testing libei event flow, to make sure the * connection works and events arrive at the EIS implementation. * * The server has hardcoded regions and creates fixed devices, it's really * just for basic purposes. * * Usually, you'd want to: * - run the eis-demo-server (or some other EIS implementation) * - export LIBEI_SOCKET=eis-0, or whatever value was given * - run the libei client */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #if HAVE_LIBXKBCOMMON #include #endif #include "src/util-color.h" #include "src/util-mem.h" #include "src/util-memfile.h" #include "src/util-strings.h" #include "src/util-time.h" #include "eis-demo-server.h" static bool stop = false; static void sighandler(int signal) { stop = true; } static OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_demo_client); static void log_handler(struct eis *eis, enum eis_log_priority priority, const char *message, struct eis_log_context *ctx) { struct lut { const char *color; const char *prefix; } lut[] = { { .color = ansi_colorcode[RED], .prefix = "", }, /* debug starts at 10 */ { .color = ansi_colorcode[HIGHLIGHT], .prefix = "DEBUG", }, { .color = ansi_colorcode[GREEN], .prefix = "INFO", }, { .color = ansi_colorcode[BLUE], .prefix = "WARN", }, { .color = ansi_colorcode[RED], .prefix = "ERROR", }, }; static time_t last_time = 0; const char *reset_code = ansi_colorcode[RESET]; run_only_once { if (!isatty(STDOUT_FILENO)) { struct lut *l; ARRAY_FOR_EACH(lut, l) l->color = ""; reset_code = ""; } } time_t now = time(NULL); char timestamp[64]; if (last_time != now) { struct tm *tm = localtime(&now); strftime(timestamp, sizeof(timestamp), "%T", tm); } else { xsnprintf(timestamp, sizeof(timestamp), "..."); } size_t idx = priority/10; assert(idx < ARRAY_LENGTH(lut)); fprintf(stdout, " EIS: %8s | %s%4s%s | %s\n", timestamp, lut[idx].color, lut[idx].prefix, reset_code, message); last_time = now; } static void eis_demo_client_destroy(struct eis_demo_client *democlient) { list_remove(&democlient->link); eis_client_unref(democlient->client); eis_device_unref(democlient->ptr); eis_device_unref(democlient->abs); eis_device_unref(democlient->kbd); eis_device_unref(democlient->touchscreen); } static OBJECT_IMPLEMENT_CREATE(eis_demo_client); static struct eis_demo_client * eis_demo_client_new(struct eis_demo_server *server, struct eis_client *client) { struct eis_demo_client *c = eis_demo_client_create(NULL); c->client = eis_client_ref(client); list_append(&server->clients, &c->link); return c; } static struct eis_demo_client * eis_demo_client_find(struct eis_demo_server *server, struct eis_client *client) { struct eis_demo_client *c; list_for_each(c, &server->clients, link) { if (c->client == client) return c; } return NULL; } DEFINE_UNREF_CLEANUP_FUNC(eis); DEFINE_UNREF_CLEANUP_FUNC(eis_event); DEFINE_UNREF_CLEANUP_FUNC(eis_keymap); DEFINE_UNREF_CLEANUP_FUNC(eis_seat); DEFINE_UNREF_CLEANUP_FUNC(eis_region); static void unlink_free(char **path) { if (*path) { unlink(*path); free(*path); } } #define _cleanup_unlink_free_ _cleanup_(unlink_free) static inline void _printf_(1, 2) colorprint(const char *format, ...) { static uint64_t color = 0; run_only_once { color = rgb(1, 1, 1) | rgb_bg(255, 127, 0); } cprintf(color, "EIS socket server: "); va_list args; va_start(args, format); vprintf(format, args); va_end(args); } #if HAVE_MEMFD_CREATE && HAVE_LIBXKBCOMMON DEFINE_UNREF_CLEANUP_FUNC(xkb_context); DEFINE_UNREF_CLEANUP_FUNC(xkb_keymap); DEFINE_UNREF_CLEANUP_FUNC(xkb_state); #endif static void setup_keymap(struct eis_demo_server *server, struct eis_device *device) { #if HAVE_MEMFD_CREATE && HAVE_LIBXKBCOMMON colorprint("Using server layout: %s\n", server->layout); _unref_(xkb_context) *ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!ctx) return; struct xkb_rule_names names = { .rules = "evdev", .model = "pc105", .layout = server->layout, }; _unref_(xkb_keymap) *keymap = xkb_keymap_new_from_names(ctx, &names, 0); if (!keymap) return; const char *str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1); size_t len = strlen(str) - 1; struct memfile *f = memfile_new(str, len); if (!f) return; _unref_(eis_keymap) *k = eis_device_new_keymap(device, EIS_KEYMAP_TYPE_XKB, memfile_get_fd(f), memfile_get_size(f)); eis_keymap_add(k); memfile_unref(f); _unref_(xkb_state) *state = xkb_state_new(keymap); if (!state) return; server->ctx = steal(&ctx); server->keymap = steal(&keymap); server->state = steal(&state); #endif } static void handle_key(struct eis_demo_server *server, uint32_t keycode, bool is_press) { char keysym_name[64] = {0}; #if HAVE_LIBXKBCOMMON if (server->state) { uint32_t xkbkc = keycode + 8; xkb_state_update_key(server->state, xkbkc, is_press ? XKB_KEY_DOWN : XKB_KEY_UP); xkb_state_key_get_utf8(server->state, xkbkc, keysym_name, sizeof(keysym_name)); } #endif colorprint("key %u (%s) [%s]\n", keycode, is_press ? "press" : "release", keysym_name); } static struct eis_device * add_device(struct eis_demo_server *server, struct eis_client *client, struct eis_seat *seat, enum eis_device_capability cap) { static uint32_t sequence; struct eis_device *device = NULL; switch (cap) { case EIS_DEVICE_CAP_POINTER: { struct eis_device *ptr = eis_seat_new_device(seat); eis_device_configure_name(ptr, "test pointer"); eis_device_configure_capability(ptr, EIS_DEVICE_CAP_POINTER); eis_device_configure_capability(ptr, EIS_DEVICE_CAP_BUTTON); eis_device_configure_capability(ptr, EIS_DEVICE_CAP_SCROLL); _unref_(eis_region) *rel_region = eis_device_new_region(ptr); eis_region_set_mapping_id(rel_region, "demo region"); eis_region_set_size(rel_region, 1920, 1080); eis_region_set_offset(rel_region, 0, 0); eis_region_add(rel_region); colorprint("Creating pointer device %s for %s\n", eis_device_get_name(ptr), eis_client_get_name(client)); eis_device_add(ptr); eis_device_resume(ptr); if (!eis_client_is_sender(client)) eis_device_start_emulating(ptr, ++sequence); device = steal(&ptr); break; } case EIS_DEVICE_CAP_POINTER_ABSOLUTE: { struct eis_device *abs = eis_seat_new_device(seat); eis_device_configure_name(abs, "test abs pointer"); eis_device_configure_capability(abs, EIS_DEVICE_CAP_POINTER_ABSOLUTE); eis_device_configure_capability(abs, EIS_DEVICE_CAP_BUTTON); eis_device_configure_capability(abs, EIS_DEVICE_CAP_SCROLL); _unref_(eis_region) *region = eis_device_new_region(abs); eis_region_set_mapping_id(region, "demo region"); eis_region_set_size(region, 1920, 1080); eis_region_set_offset(region, 0, 0); eis_region_add(region); colorprint("Creating abs pointer device %s for %s\n", eis_device_get_name(abs), eis_client_get_name(client)); eis_device_add(abs); eis_device_resume(abs); if (!eis_client_is_sender(client)) eis_device_start_emulating(abs, ++sequence); device = steal(&abs); break; } case EIS_DEVICE_CAP_KEYBOARD: { struct eis_device *kbd = eis_seat_new_device(seat); eis_device_configure_name(kbd, "test keyboard"); eis_device_configure_capability(kbd, EIS_DEVICE_CAP_KEYBOARD); if (server->layout) setup_keymap(server, kbd); colorprint("Creating keyboard device %s for %s\n", eis_device_get_name(kbd), eis_client_get_name(client)); eis_device_add(kbd); eis_device_resume(kbd); if (!eis_client_is_sender(client)) eis_device_start_emulating(kbd, ++sequence); device = steal(&kbd); break; } case EIS_DEVICE_CAP_TOUCH: { struct eis_device *touchscreen = eis_seat_new_device(seat); eis_device_configure_name(touchscreen, "test touchscreen"); eis_device_configure_capability(touchscreen, EIS_DEVICE_CAP_TOUCH); colorprint("Creating touchscreen device %s for %s\n", eis_device_get_name(touchscreen), eis_client_get_name(client)); eis_device_add(touchscreen); eis_device_resume(touchscreen); if (!eis_client_is_sender(client)) eis_device_start_emulating(touchscreen, ++sequence); device = steal(&touchscreen); break; } case EIS_DEVICE_CAP_BUTTON: case EIS_DEVICE_CAP_SCROLL: /* Mixed in with pointer/abs - good enough for a demo server */ break; } return device; } /** * The simplest event handler. Connect any client and any device and just * printf the events as the come in. This is an incomplete implementation, * it just does the basics for pointers and keyboards atm. */ static int eis_demo_server_printf_handle_event(struct eis_demo_server *server, struct eis_event *e) { switch(eis_event_get_type(e)) { case EIS_EVENT_CLIENT_CONNECT: { struct eis_client *client = eis_event_get_client(e); bool is_sender = eis_client_is_sender(client); colorprint("new %s client: %s\n", is_sender ? "sender" : "receiver", eis_client_get_name(client)); eis_demo_client_new(server, client); if (!is_sender) server->nreceiver_clients++; /* insert sophisticated authentication here */ eis_client_connect(client); colorprint("accepting client, creating new seat 'default'\n"); _unref_(eis_seat) *seat = eis_client_new_seat(client, "default"); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_POINTER_ABSOLUTE); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_KEYBOARD); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_TOUCH); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_BUTTON); eis_seat_configure_capability(seat, EIS_DEVICE_CAP_SCROLL); eis_seat_add(seat); /* Note: we don't have a ref to this seat ourselves anywhere */ break; } case EIS_EVENT_CLIENT_DISCONNECT: { struct eis_client *client = eis_event_get_client(e); bool is_sender = eis_client_is_sender(client); if (!is_sender) server->nreceiver_clients--; colorprint("client %s disconnected\n", eis_client_get_name(client)); eis_client_disconnect(client); eis_demo_client_unref(eis_demo_client_find(server, client)); break; } case EIS_EVENT_SEAT_BIND: { struct eis_client *client = eis_event_get_client(e); struct eis_demo_client *democlient = eis_demo_client_find(server, client); assert(democlient); struct eis_seat *seat = eis_event_get_seat(e); if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER)) { if (!democlient->ptr) democlient->ptr = add_device(server, client, seat, EIS_DEVICE_CAP_POINTER); } else { if (democlient->ptr) { eis_device_remove(democlient->ptr); democlient->ptr = eis_device_unref(democlient->ptr); } } if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) { if (!democlient->abs) democlient->abs = add_device(server, client, seat, EIS_DEVICE_CAP_POINTER_ABSOLUTE); } else { if (democlient->abs) { eis_device_remove(democlient->abs); democlient->abs = eis_device_unref(democlient->abs); } } if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD)) { if (!democlient->kbd) democlient->kbd = add_device(server, client, seat, EIS_DEVICE_CAP_KEYBOARD); } else { if (democlient->kbd) { eis_device_remove(democlient->kbd); democlient->kbd = eis_device_unref(democlient->kbd); } } if (eis_event_seat_has_capability(e, EIS_DEVICE_CAP_TOUCH)) { if (!democlient->touchscreen) democlient->touchscreen = add_device(server, client, seat, EIS_DEVICE_CAP_TOUCH); } else { if (democlient->touchscreen) { eis_device_remove(democlient->touchscreen); democlient->touchscreen = eis_device_unref(democlient->touchscreen); } } /* Special "Feature", if all caps are unbound remove the seat. * This is a demo server after all, so let's demo this. */ if (!eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_POINTER_ABSOLUTE) && !eis_event_seat_has_capability(e, EIS_DEVICE_CAP_KEYBOARD)) eis_seat_remove(seat); break; } case EIS_EVENT_DEVICE_CLOSED: { struct eis_client *client = eis_event_get_client(e); struct eis_demo_client *democlient = eis_demo_client_find(server, client); assert(democlient); struct eis_device *device = eis_event_get_device(e); eis_device_remove(device); if (democlient->ptr == device) democlient->ptr = NULL; if (democlient->abs == device) democlient->abs = NULL; if (democlient->kbd == device) democlient->kbd = NULL; if (democlient->touchscreen == device) democlient->touchscreen = NULL; eis_device_unref(device); } break; case EIS_EVENT_DEVICE_START_EMULATING: { struct eis_device *device = eis_event_get_device(e); colorprint("Device %s is ready to send events\n", eis_device_get_name(device)); } break; case EIS_EVENT_DEVICE_STOP_EMULATING: { struct eis_device *device = eis_event_get_device(e); colorprint("Device %s will no longer send events\n", eis_device_get_name(device)); } break; case EIS_EVENT_POINTER_MOTION: { colorprint("motion by %.2f/%.2f\n", eis_event_pointer_get_dx(e), eis_event_pointer_get_dy(e)); } break; case EIS_EVENT_POINTER_MOTION_ABSOLUTE: { colorprint("absmotion to %.2f/%.2f\n", eis_event_pointer_get_absolute_x(e), eis_event_pointer_get_absolute_y(e)); } break; case EIS_EVENT_BUTTON_BUTTON: { colorprint("button %u (%s)\n", eis_event_button_get_button(e), eis_event_button_get_is_press(e) ? "press" : "release"); } break; case EIS_EVENT_SCROLL_DELTA: { colorprint("scroll %.2f/%.2f\n", eis_event_scroll_get_dx(e), eis_event_scroll_get_dy(e)); } break; case EIS_EVENT_SCROLL_DISCRETE: { colorprint("scroll discrete %d/%d\n", eis_event_scroll_get_discrete_dx(e), eis_event_scroll_get_discrete_dy(e)); } break; case EIS_EVENT_KEYBOARD_KEY: { handle_key(server, eis_event_keyboard_get_key(e), eis_event_keyboard_get_key_is_press(e)); } break; case EIS_EVENT_TOUCH_DOWN: case EIS_EVENT_TOUCH_MOTION: { colorprint("touch %s %u %.2f/%.2f\n", eis_event_get_type(e) == EIS_EVENT_TOUCH_DOWN ? "down" : "motion", eis_event_touch_get_id(e), eis_event_touch_get_x(e), eis_event_touch_get_y(e)); } break; case EIS_EVENT_TOUCH_UP: { colorprint("touch up %u\n", eis_event_touch_get_id(e)); } break; case EIS_EVENT_FRAME: { colorprint("frame timestamp: %" PRIu64 "\n", eis_event_get_time(e)); } break; default: abort(); } return 0; } static void usage(FILE *fp, const char *argv0) { fprintf(fp, "Usage: %s [--verbose] [--uinput] [--socketpath=/path/to/socket] [--interval=1000]\n" "\n" "Start an EIS demo server. The server accepts all client connections\n" "and devices and prints any events from the client to stdout.\n" "\n" "Options:\n" " --socketpath Use the given socket path. Default: $XDG_RUNTIME_DIR/eis-0\n" " --layout Use the given XKB layout (requires libxkbcommon). Default: none\n" " --uinput Set up each device as uinput device (this requires root)\n" " --interval Interval in milliseconds between polling\n" " --verbose Enable debugging output\n" "", argv0); } int main(int argc, char **argv) { bool verbose = false; bool uinput = false; unsigned int interval = 1000; const char *layout = NULL; _cleanup_unlink_free_ char *socketpath = NULL; const char *xdg = getenv("XDG_RUNTIME_DIR"); if (xdg) socketpath = xaprintf("%s/eis-0", xdg); while (true) { enum { OPT_VERBOSE, OPT_LAYOUT, OPT_SOCKETPATH, OPT_UINPUT, OPT_INTERVAL, }; static struct option long_opts[] = { {"socketpath", required_argument, 0, OPT_SOCKETPATH}, {"layout", required_argument, 0, OPT_LAYOUT}, {"uinput", no_argument, 0, OPT_UINPUT}, {"verbose", no_argument, 0, OPT_VERBOSE}, {"interval", required_argument, 0, OPT_INTERVAL}, {"help", no_argument, 0, 'h'}, {NULL}, }; int optind = 0; int c = getopt_long(argc, argv, "h", long_opts, &optind); if (c == -1) break; switch(c) { case 'h': usage(stdout, argv[0]); return EXIT_SUCCESS; case OPT_SOCKETPATH: free(socketpath); socketpath = xstrdup(optarg); break; case OPT_LAYOUT: layout = optarg; break; case OPT_UINPUT: uinput = true; break; case OPT_VERBOSE: verbose = true; break; case OPT_INTERVAL: interval = atoi(optarg); break; default: usage(stderr, argv[0]); return EXIT_FAILURE; } } if (socketpath == NULL) { fprintf(stderr, "No socketpath given and $XDG_RUNTIME_DIR is not set\n"); return EXIT_FAILURE; } struct eis_demo_server server = { .layout = layout, .handler.handle_event = eis_demo_server_printf_handle_event, }; list_init(&server.clients); if (uinput) { int rc = -ENOTSUP; #if HAVE_LIBEVDEV rc = eis_demo_server_setup_uinput_handler(&server); #endif if (rc != 0) { fprintf(stderr, "Failed to set up uinput handler: %s\n", strerror(-rc)); return EXIT_FAILURE; } } _unref_(eis) *eis = eis_new(NULL); assert(eis); if (verbose) { eis_log_set_priority(eis, EIS_LOG_PRIORITY_DEBUG); eis_log_set_handler(eis, log_handler); } signal(SIGINT, sighandler); int rc = eis_setup_backend_socket(eis, socketpath); if (rc != 0) { fprintf(stderr, "init failed: %s\n", strerror(errno)); return 1; } colorprint("waiting on %s\n", socketpath); struct pollfd fds = { .fd = eis_get_fd(eis), .events = POLLIN, .revents = 0, }; int nevents; while (!stop && (nevents = poll(&fds, 1, interval)) > -1) { if (nevents == 0 && server.nreceiver_clients == 0) continue; uint64_t now = eis_now(eis); colorprint("now: %" PRIu64 "\n", now); eis_dispatch(eis); while (true) { _unref_(eis_event) *e = eis_get_event(eis); if (!e) break; int rc = server.handler.handle_event(&server, e); if (rc != 0) break; } struct eis_demo_client *democlient; const int interval = ms2us(12); /* events are 12ms apart */ list_for_each(democlient, &server.clients, link) { if (eis_client_is_sender(democlient->client)) continue; struct eis_device *ptr = democlient->ptr; struct eis_device *kbd = democlient->kbd; struct eis_device *abs = democlient->abs; struct eis_device *touchscreen = democlient->touchscreen; if (ptr) { colorprint("sending motion event\n"); eis_device_pointer_motion(ptr, -1, 1); /* BTN_LEFT */ colorprint("sending button event\n"); eis_device_button_button(ptr, BTN_LEFT, true); eis_device_frame(ptr, now); now += interval; eis_device_button_button(ptr, BTN_LEFT, false); eis_device_frame(ptr, now); now += interval; colorprint("sending scroll events\n"); eis_device_scroll_delta(ptr, 1, 1); eis_device_frame(ptr, now); now += interval; eis_device_scroll_discrete(ptr, 1, 1); eis_device_frame(ptr, now); now += interval; } if (kbd) { static int key = 0; colorprint("sending key event\n"); eis_device_keyboard_key(kbd, KEY_Q + key, true); /* KEY_Q */ eis_device_frame(kbd, now); now += interval; eis_device_keyboard_key(kbd, KEY_Q + key, false); /* KEY_Q */ eis_device_frame(kbd, now); now += interval; key = (key + 1) % 6; } if (abs) { static int x, y; colorprint("sending abs event\n"); eis_device_pointer_motion_absolute(abs, 150 + ++x, 150 - ++y); eis_device_frame(abs, now); now += interval; } if (touchscreen) { static int x, y; static int counter = 0; struct eis_touch *touch = democlient->touch; switch (counter++ % 5) { case 0: touch = eis_device_touch_new(touchscreen); if (touch) { /* NULL if client was disconnected internally already */ colorprint("sending touch down event\n"); eis_touch_down(touch, 100 + ++x, 200 - ++y); eis_device_frame(touchscreen, now); democlient->touch = touch; } break; case 4: if (touch) { colorprint("sending touch down event\n"); eis_touch_up(touch); eis_device_frame(touchscreen, now); democlient->touch = eis_touch_unref(touch); } break; default: if (touch) { eis_touch_motion(touch, 100 + ++x, 200 - ++y); eis_device_frame(touchscreen, now); } break; } } } } struct eis_demo_client *democlient; list_for_each_safe(democlient, &server.clients, link) { eis_demo_client_unref(democlient); } return 0; } libei-1.2.1/tools/eis-demo-server.h000066400000000000000000000036751456005336000171330ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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 "libeis.h" #include "util-list.h" #include "util-object.h" struct eis_demo_client { struct object object; struct list link; struct eis_client *client; struct eis_device *ptr; struct eis_device *kbd; struct eis_device *abs; struct eis_device *touchscreen; struct eis_touch *touch; }; struct eis_demo_server { const char *layout; #if HAVE_LIBXKBCOMMON struct xkb_context *ctx; struct xkb_keymap *keymap; struct xkb_state *state; #endif /* Event handler */ struct { int (*handle_event)(struct eis_demo_server *server, struct eis_event *e); void *data; } handler; struct list clients; unsigned int nreceiver_clients; }; #if HAVE_LIBEVDEV int eis_demo_server_setup_uinput_handler(struct eis_demo_server *server); #endif libei-1.2.1/tools/meson.build000066400000000000000000000021521456005336000161030ustar00rootroot00000000000000src_eis_demo_server = files( 'eis-demo-server.c', ) if dep_libevdev.found() src_eis_demo_server += files( 'eis-demo-server-uinput.c', ) endif eis_demo_server = executable('eis-demo-server', src_eis_demo_server, dependencies: [ dep_libutil, dep_libeis, dep_libxkbcommon, dep_libevdev ], include_directories: [inc_builddir], ) executable('ei-demo-client', 'ei-demo-client.c', dependencies: [dep_libutil, dep_libei, dep_libxkbcommon], include_directories: [inc_builddir], ) executable('ei-debug-events', 'ei-debug-events.c', dependencies: [dep_libutil, dep_libei, dep_libevdev], include_directories: [inc_builddir], install: true ) if build_oeffis executable('oeffis-demo-tool', 'oeffis-demo-tool.c', c_args: ['-DMESON_BUILDDIR="@0@"'.format(meson.current_build_dir())], include_directories: [inc_builddir], dependencies: [dep_libutil, dep_liboeffis], ) endif libei-1.2.1/tools/oeffis-demo-tool.c000066400000000000000000000065101456005336000172570ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * Copyright © 2020 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. */ /* A simple tool that connects to an EIS implementation via the RemoteDesktop * portal using liboeffis */ #include "config.h" #include #include #include #include #include #include "src/util-macros.h" #include "src/util-mem.h" #include "src/util-io.h" #include "liboeffis.h" #ifndef MESON_BUILDDIR #error MESON_BUILDDIR must be defined #endif DEFINE_UNREF_CLEANUP_FUNC(oeffis); static bool stop = false; static void sighandler(int signal) { stop = true; } static void start_debug_events(int fd) { pid_t pid = fork(); assert(pid != -1); if (pid == 0) { _cleanup_free_ char *fdstr = xaprintf("%d", fd); execl(MESON_BUILDDIR "/ei-debug-events", "ei-debug-events", "--socketfd", fdstr, NULL); fprintf(stderr, "Failed to fork: %m\n"); exit(1); } } int main(int argc, char **argv) { _unref_(oeffis) *oeffis = oeffis_new(NULL); signal(SIGINT, sighandler); struct pollfd fds = { .fd = oeffis_get_fd(oeffis), .events = POLLIN, .revents = 0, }; oeffis_create_session(oeffis, OEFFIS_DEVICE_POINTER|OEFFIS_DEVICE_KEYBOARD); while (!stop && poll(&fds, 1, 1000) > -1) { oeffis_dispatch(oeffis); enum oeffis_event_type e = oeffis_get_event(oeffis); switch (e) { case OEFFIS_EVENT_NONE: break; case OEFFIS_EVENT_CONNECTED_TO_EIS: { _cleanup_close_ int eisfd = oeffis_get_eis_fd(oeffis); printf("# We have an eisfd: lucky number %d\n", eisfd); /* Note: unless we get closed/disconnected, we are * started now, or will be once the compositor is * happy with it. * * We still need to keep dispatching events though * to be able to receive the Session.Closed signal * or any disconnections. And we must keep the * oeffis context alive - once the context is * destroyed we disconnect from DBus which will * cause the compositor or the portal to invalidate * our EIS fd. */ start_debug_events(eisfd); break; } case OEFFIS_EVENT_DISCONNECTED: fprintf(stderr, "Disconnected: %s\n", oeffis_get_error_message(oeffis)); stop = true; break; case OEFFIS_EVENT_CLOSED: printf("Closing\n"); stop = true; break; } } return 0; }