diff options
33 files changed, 1306 insertions, 313 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..963a79e9 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,225 @@ +# Continuous Integration configuration for at-spi2-core +# +# For documentation on how this works, see devel-docs/gitlab-ci.md +# +# Full documentation for Gitlab CI: https://docs.gitlab.com/ee/ci/ +# +# Introduction to Gitlab CI: https://docs.gitlab.com/ee/ci/quick_start/index.html + +variables: + OPENSUSE_TUMBLEWEED_IMAGE: "registry.gitlab.gnome.org/gnome/at-spi2-core/opensuse:latest" + +# Stages in the CI pipeline in which jobs will be run +stages: +# - style-check + - build + - analysis + - docs + - deploy + +# Base definition for jobs. +# +# We have the package dependencies to install on top of a stock opensuse/tumbleweed image, +# and the rules for when to run each job (on merge requests and on personal branches). +.only-default: + only: + - merge_requests + - branches + except: + - tags + +# C coding style checker. +# +# Disabled for now, since we need to decide to reindent all the code first. +# +# style-check-diff: +# extends: .only-default +# image: fedora:latest +# stage: style-check +# script: +# - dnf install -y clang-tools-extra curl diffutils git +# - sh -x ./.gitlab-ci/run-style-check.sh + + +# Template for the default build recipe. +# +# Depends on these variables: +# @MESON_EXTRA_FLAGS: extra arguments for the meson setup invocation +.build-default: + image: $OPENSUSE_TUMBLEWEED_IMAGE + extends: .only-default + script: + - meson setup ${MESON_EXTRA_FLAGS} --prefix /usr _build . + - meson compile -C _build + - meson install -C _build + - mkdir /tmp/test+dir+with+funny+chars + - export XDG_RUNTIME_DIR=/tmp/test+dir+with+funny+chars # See https://gitlab.gnome.org/GNOME/at-spi2-core/-/issues/48 + - dbus-run-session -- .gitlab-ci/run-tests.sh + artifacts: + reports: + junit: "_build/meson-logs/testlog.junit.xml" + when: always + name: "at-spi2-core-${CI_COMMIT_REF_NAME}" + paths: + - "${CI_PROJECT_DIR}/_build/meson-logs" + - "${CI_PROJECT_DIR}/_build/atspi/Atspi-2.0.gir" + +# Inherit to build the API reference via gi-docgen +# @PROJECT_DEPS: the dependencies of the project (on Fedora) +# @MESON_EXTRA_FLAGS: extra arguments for the meson setup invocation +# @DOCS_FLAGS: doc-related arguments for the meson setup invocation +# @DOCS_PATH: the docs output directory under the build directory +# .gidocgen-build: +# image: fedora:latest +# before_script: +# - export PATH="$HOME/.local/bin:$PATH" +# - dnf install -y python3 python3-pip python3-wheel gobject-introspection-devel graphviz ninja-build redhat-rpm-config +# - dnf install -y ${PROJECT_DEPS} +# - pip3 install --user meson==${MESON_VERSION} gi-docgen jinja2 Markdown markupsafe pygments toml typogrify +# script: +# - meson setup ${MESON_EXTRA_FLAGS} ${DOCS_FLAGS} _docs . +# - meson compile -C _docs +# - | +# pushd "_docs/${DOCS_PATH}" > /dev/null +# tar cf ${CI_PROJECT_NAME}-docs.tar . +# popd > /dev/null +# - mv _docs/${DOCS_PATH}/${CI_PROJECT_NAME}-docs.tar . +# artifacts: +# when: always +# name: 'Documentation' +# expose_as: 'Download the API reference' +# paths: +# - ${CI_PROJECT_NAME}-docs.tar + +# Build and run the test suite. +# +# Look at .build-default for where the artifacts are stored (build/test logs, built binaries). +opensuse-x86_64: + extends: .build-default + stage: build + needs: [] + variables: + MESON_EXTRA_FLAGS: "--buildtype=debug" # -Dwerror=true + +# Run static analysis on the code. +# +# The logs are part of the compilation stderr. +static-scan: + image: $OPENSUSE_TUMBLEWEED_IMAGE + stage: analysis + needs: [] + variables: + MESON_EXTRA_FLAGS: "--buildtype=debug -Dintrospection=no -Ddocs=false" + script: + - meson setup ${MESON_EXTRA_FLAGS} --prefix /usr _scan_build . + - ninja -C _scan_build scan-build + artifacts: + name: "at-spi2-core-${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}" + when: always + paths: + - "_scan_build/meson-logs/scanbuild" + +# Build and run with address sanitizer (asan). +asan-build: + image: $OPENSUSE_TUMBLEWEED_IMAGE + stage: analysis + needs: [] + variables: + MESON_EXTRA_FLAGS: "--buildtype=debug -Db_sanitize=address -Db_lundef=false -Dintrospection=no -Ddocs=false" + script: + - CC=clang meson setup ${MESON_EXTRA_FLAGS} --prefix /usr _build . + - meson compile -C _build + - meson install -C _build + - dbus-run-session -- .gitlab-ci/run-tests.sh + artifacts: + name: "at-spi2-core-${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}" + when: always + paths: + - "_asan_build/meson-logs" + allow_failure: true + +# Run the test suite and extract code coverage information. +# +# See the _coverage/ artifact for the HTML report. +coverage: + image: $OPENSUSE_TUMBLEWEED_IMAGE + stage: analysis + needs: [] + variables: + MESON_EXTRA_FLAGS: "--buildtype=debug -Ddocs=false -Dintrospection=no" + CFLAGS: "-coverage -ftest-coverage -fprofile-arcs" + script: + - meson setup ${MESON_EXTRA_FLAGS} --prefix /usr _build . + - meson compile -C _build + - meson install -C _build + - mkdir -p _coverage + - lcov --config-file .gitlab-ci/lcovrc --directory _build --capture --initial --output-file "_coverage/${CI_JOB_NAME}-baseline.lcov" + - dbus-run-session -- .gitlab-ci/run-tests.sh + - lcov --config-file .gitlab-ci/lcovrc --directory _build --capture --output-file "_coverage/${CI_JOB_NAME}.lcov" + - bash -x .gitlab-ci/gen-coverage.sh + - mkdir -p public/ + - cp -r _coverage public/coverage + coverage: '/^\s+lines\.+:\s+([\d.]+\%)\s+/' + artifacts: + name: "at-spi2-core-${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}" + expire_in: 2 days + when: always + paths: + - "_build/meson-logs" + - public + +# Build the reference documentation. +# +# reference: +# stage: docs +# needs: [] +# extends: .gidocgen-build +# variables: +# PROJECT_DEPS: +# gdk-pixbuf2-devel +# geocode-glib-devel +# gettext +# git +# gobject-introspection-devel +# itstool +# libsoup-devel +# libxml2-devel +# ninja-build +# pylint +# python3 +# python3-gobject +# python3-pip +# python3-wheel +# redhat-rpm-config +# vala +# MESON_VERSION: "0.55.3" +# DOCS_FLAGS: -Dgtk_doc=true +# DOCS_PATH: doc/libgweather-4.0 +# +# +# Publish the generated HTML reference documentation. +# +# pages: +# stage: deploy +# needs: ['reference'] +# script: +# - mkdir public && cd public +# - tar xf ../${CI_PROJECT_NAME}-docs.tar +# artifacts: +# paths: +# - public +# only: +# - master +# - main + +# Publish the test coverage report +pages: + stage: deploy + needs: [ coverage ] + script: + - echo # dummy - contents were generated in another job + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH diff --git a/.gitlab-ci/README.md b/.gitlab-ci/README.md new file mode 100644 index 00000000..9adfaac8 --- /dev/null +++ b/.gitlab-ci/README.md @@ -0,0 +1,76 @@ +# Continuous Integration scripts for at-spi2-core + +Please see the general [documentation for at-spi2-core's Gitlab CI][ci-docs]. + +This directory contains scripts which get called during a run of a CI +pipeline, and utilities to maintain the CI infrastructure. + +## Scripts used during a run of a CI pipeline: + +* `run-tests.sh` - Runs the test suite and prints other diagnostics. + +* `gen-coverage.sh` - After the test suite is run, merges the various + code coverage reports from `lcov`, and generates an HTML report. + +* `lcovrc` - Configuration file for `lcov`, used by `gen-coverage.sh`. + Among other things, this tells `lcov` to exclude branch coverage for + the unreachable branches of `g_return_if_fail()` and friends. + +* `run-style-check.sh` - Runs `clang-format-diff` to test for source + files with inconsistent formatting, and uploads the resulting report + to gitlab so it can be viewed as part of a merge request's analysis. + +* `search-common-ancestor.sh` - Utility used from + `run-style-check.sh`; finds a git branch point from the current + commit. + +## Utilities to maintain the CI infrastructure: + +To make pipelines fast, and avoid a lot of repeated downloads, +at-spi2-core uses pre-built container images for CI pipelines, instead +of using a stock image like opensuse/tumbleweed and then installing +all the dependencies on top of it every time. + +The prebuilt images are stored here: +https://gitlab.gnome.org/GNOME/at-spi2-core/container_registry + +Instead of maintaining those images by hand with `docker` or `podman` +commands, here is a little script (stolen from [glib][glib-ci]) to +maintain them, which you can start exploring with `./run-docker.sh help`. + +This script knows how to build and upload images from Dockerfiles +called `foo.Dockerfile`. The image configurations we have: + +* `opensuse.Dockerfile` - starts with an opensuse/tumbleweed image and + installs the package dependencies for building at-spi2-core. + +If you are one of at-spi2-core's maintainers, you'll want to update +the CI images periodically. First, install `podman` and +`podman-docker`. Then, run this: + +```sh +# "opensuse" in these commands indicates to use the opensuse.Dockerfile configuration + +./run-docker.sh build --base=opensuse # builds the image, takes a while + +./run-docker.sh run --base=opensuse # launch the container; poke around; see that it works + +./run-docker.sh push --base=opensuse # push the image to registry.gitlab.gnome.org +``` + +The `build` subcommand creates an image named +`registry.gitlab.gnome.org/gnome/at-spi2-core/opensuse/tumbleweed:latest` +**that is only stored in your localhost**. + +The `run` subcommand launches a container with that image and gives +you a shell prompt. This is equivalent to `podman run`. + +The `push` subcommand takes that built image and uploads it to +`registry.gitlab.gnome.org`. It will then be visible from +https://gitlab.gnome.org/GNOME/at-spi2-core/container_registry - the +CI configuration in [`.gitlab-ci.yml`](../.gitlab-ci.yml) uses this +image for the pipeline. + +[ci-docs]: ../devel-docs/gitlab-ci.md +[container-registry-docs]: https://gitlab.gnome.org/help/user/packages/container_registry/index +[glib-ci]: https://gitlab.gnome.org/GNOME/glib/-/tree/main/.gitlab-ci diff --git a/.gitlab-ci/gen-coverage.sh b/.gitlab-ci/gen-coverage.sh new file mode 100644 index 00000000..39492565 --- /dev/null +++ b/.gitlab-ci/gen-coverage.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e + +for path in _coverage/*.lcov; do + lcov --config-file .gitlab-ci/lcovrc -r "${path}" '*/_build/*' -o "$(pwd)/${path}" + lcov --config-file .gitlab-ci/lcovrc -e "${path}" "$(pwd)/*" -o "$(pwd)/${path}" +done + +genhtml \ + --ignore-errors=source \ + --config-file .gitlab-ci/lcovrc \ + _coverage/*.lcov \ + -o _coverage/coverage + +cd _coverage +rm -f ./*.lcov + +cat >index.html <<EOL +<html> +<head><title>at-spi2-core Coverage</title></head> +<body> +<div> +<ul> +<li><a href="coverage/index.html">Coverage report</a></li> +</ul> +</div> +</body> +</html> +EOL diff --git a/.gitlab-ci/lcovrc b/.gitlab-ci/lcovrc new file mode 100644 index 00000000..ac5997b7 --- /dev/null +++ b/.gitlab-ci/lcovrc @@ -0,0 +1,13 @@ +# lcov and genhtml configuration +# See http://ltp.sourceforge.net/coverage/lcov/lcovrc.5.php + +# Always enable branch coverage +lcov_branch_coverage = 1 + +# Exclude precondition assertions, as we can never reasonably get full branch +# coverage of them, as they should never normally fail. +# See https://github.com/linux-test-project/lcov/issues/44 +lcov_excl_br_line = LCOV_EXCL_BR_LINE|g_return_if_fail|g_return_val_if_fail|g_assert|g_assert_ + +# Similarly for unreachable assertions. +lcov_excl_line = LCOV_EXCL_LINE|g_return_if_reached|g_return_val_if_reached|g_assert_not_reached diff --git a/.gitlab-ci/opensuse.Dockerfile b/.gitlab-ci/opensuse.Dockerfile new file mode 100644 index 00000000..0bcdfe43 --- /dev/null +++ b/.gitlab-ci/opensuse.Dockerfile @@ -0,0 +1,34 @@ +# Dockerfile to build container images for Gitlab Continuous Integration +# +# This starts with an openSUSE Tumbleweed image, and installs the dependencies +# for building and testing at-spi2-core. +# +# See README.md for documentation. + +FROM opensuse/tumbleweed:latest + +RUN zypper refresh \ + && zypper install -y \ + clang \ + clang-tools \ + findutils \ + gcc \ + dbus-1 \ + dbus-1-devel \ + gettext \ + git \ + glib2-devel \ + gobject-introspection-devel \ + gsettings-desktop-schemas \ + itstool \ + libasan6 \ + libxml2-devel \ + libxkbcommon-devel \ + libXi-devel \ + libXtst-devel \ + lcov \ + meson \ + ninja \ + python38 \ + python38-gobject \ + && zypper clean --all diff --git a/.gitlab-ci/run-docker.sh b/.gitlab-ci/run-docker.sh new file mode 100755 index 00000000..d6be4670 --- /dev/null +++ b/.gitlab-ci/run-docker.sh @@ -0,0 +1,132 @@ +#!/bin/bash + +read_arg() { + # $1 = arg name + # $2 = arg value + # $3 = arg parameter + local rematch='^[^=]*=(.*)$' + if [[ $2 =~ $rematch ]]; then + read -r "$1" <<< "${BASH_REMATCH[1]}" + else + read -r "$1" <<< "$3" + # There is no way to shift our callers args, so + # return 1 to indicate they should do it instead. + return 1 + fi +} + +SUDO_CMD="sudo" +if docker -v |& grep -q podman; then + # Using podman + SUDO_CMD="" + # Docker is actually implemented by podman, and its OCI output + # is incompatible with some of the dockerd instances on GitLab + # CI runners. + export BUILDAH_FORMAT=docker +fi + +set -e + +base="" +base_version="" +build=0 +run=0 +push=0 +list=0 +print_help=0 +no_login=0 + +while (($# > 0)); do + case "${1%%=*}" in + build) build=1;; + run) run=1;; + push) push=1;; + list) list=1;; + help) print_help=1;; + --base|-b) read_arg base "$@" || shift;; + --base-version) read_arg base_version "$@" || shift;; + --no-login) no_login=1;; + *) echo -e "\\e[1;31mERROR\\e[0m: Unknown option '$1'"; exit 1;; + esac + shift +done + +if [ $print_help == 1 ]; then + echo "$0 - Build and run Docker images" + echo "" + echo "Usage: $0 <command> [options] [basename]" + echo "" + echo "Available commands" + echo "" + echo " build --base=<BASENAME> - Build Docker image <BASENAME>.Dockerfile" + echo " run --base=<BASENAME> - Run Docker image <BASENAME>" + echo " push --base=<BASENAME> - Push Docker image <BASENAME> to the registry" + echo " list - List available images" + echo " help - This help message" + echo "" + exit 0 +fi + +cd "$(dirname "$0")" + +if [ $list == 1 ]; then + echo "Available Docker images:" + for f in *.Dockerfile; do + filename=$( basename -- "$f" ) + basename="${filename%.*}" + + echo -e " \\e[1;39m$basename\\e[0m" + done + exit 0 +fi + +# All commands after this require --base to be set +if [ -z "${base}" ]; then + echo "Usage: $0 <command>" + echo "Or use \"$0 help\" for a list of commands" + exit 1 +fi + +if [ ! -f "$base.Dockerfile" ]; then + echo -e "\\e[1;31mERROR\\e[0m: Dockerfile for '$base' not found" + exit 1 +fi + +if [ -z "${base_version}" ]; then + base_version="latest" +else + base_version="v$base_version" +fi + +TAG="registry.gitlab.gnome.org/gnome/at-spi2-core/${base}:${base_version}" + +if [ $build == 1 ]; then + echo -e "\\e[1;32mBUILDING\\e[0m: ${base} as ${TAG}" + $SUDO_CMD docker build \ + --tag "${TAG}" \ + --file "${base}.Dockerfile" . + exit $? +fi + +if [ $push == 1 ]; then + echo -e "\\e[1;32mPUSHING\\e[0m: ${base} as ${TAG}" + + if [ $no_login == 0 ]; then + $SUDO_CMD docker login registry.gitlab.gnome.org + fi + + $SUDO_CMD docker push $TAG + exit $? +fi + +if [ $run == 1 ]; then + echo -e "\\e[1;32mRUNNING\\e[0m: ${base} as ${TAG}" + $SUDO_CMD docker run \ + --rm \ + --volume "$(pwd)/..:/home/user/app" \ + --workdir "/home/user/app" \ + --tty \ + --interactive "${TAG}" \ + bash + exit $? +fi diff --git a/.gitlab-ci/run-style-check.sh b/.gitlab-ci/run-style-check.sh new file mode 100755 index 00000000..9d741bcc --- /dev/null +++ b/.gitlab-ci/run-style-check.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e + +ancestor_horizon=28 # days (4 weeks) + +# Wrap everything in a subshell so we can propagate the exit status. +( + +source .gitlab-ci/search-common-ancestor.sh + +git diff -U0 --no-color "${newest_common_ancestor_sha}" atspi/*.c bus/*.c dbind/*.c registryd/*.c test/*.c | clang-format-diff -p1 > format-diff.log + +) +exit_status=$? + +[ ${exit_status} == 0 ] || exit ${exit_status} + +format_diff="$(<format-diff.log)" + +if [ -n "${format_diff}" ]; then + echo 'body=```diff' > format.log + cat format-diff.log >> format.log + echo '```' >> format.log + [ -n "$CI_MERGE_REQUEST_IID" ] && curl \ + --request POST \ + --header "Private-Token: $STYLE_CHECK_TOKEN" \ + --data-urlencode "$(<format.log)" \ + https://gitlab.gnome.org/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes \ + --insecure + unlink format.log + exit 1 +fi diff --git a/.gitlab-ci/run-tests.sh b/.gitlab-ci/run-tests.sh new file mode 100755 index 00000000..f3ce5c2c --- /dev/null +++ b/.gitlab-ci/run-tests.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -e + +echo "About to run the tests. First we'll launch the accessibility bus by calling GetAddress:" + +dbus-send --print-reply --session --dest=org.a11y.Bus /org/a11y/bus org.a11y.Bus.GetAddress + +ps auxwww + +echo "Now running the tests:" + +meson test -C _build + +echo "After the tests - calling GetAddress again:" + +dbus-send --print-reply --session --dest=org.a11y.Bus /org/a11y/bus org.a11y.Bus.GetAddress diff --git a/.gitlab-ci/search-common-ancestor.sh b/.gitlab-ci/search-common-ancestor.sh new file mode 100755 index 00000000..bac99d2c --- /dev/null +++ b/.gitlab-ci/search-common-ancestor.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +ancestor_horizon=28 # days (4 weeks) + +# We need to add a new remote for the upstream target branch, since this script +# could be running in a personal fork of the repository which has out of date +# branches. +# +# Limit the fetch to a certain date horizon to limit the amount of data we get. +# If the branch was forked from origin/main before this horizon, it should +# probably be rebased. +if ! git ls-remote --exit-code upstream >/dev/null 2>&1 ; then + git remote add upstream https://gitlab.gnome.org/GNOME/${CI_PROJECT_NAME}.git +fi +git fetch --shallow-since="$(date --date="${ancestor_horizon} days ago" +%Y-%m-%d)" upstream + +# Work out the newest common ancestor between the detached HEAD that this CI job +# has checked out, and the upstream target branch (which will typically be +# `upstream/main` or `upstream/gnome-40`). +# `${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}` or `${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}` +# are only defined if we’re running in a merge request pipeline, +# fall back to `${CI_DEFAULT_BRANCH}` or `${CI_COMMIT_BRANCH}` respectively +# otherwise. + +source_branch="${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME:-${CI_COMMIT_BRANCH}}" +git fetch --shallow-since="$(date --date="${ancestor_horizon} days ago" +%Y-%m-%d)" origin "${source_branch}" + +newest_common_ancestor_sha=$(diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "upstream/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-${CI_DEFAULT_BRANCH}}") <(git rev-list --first-parent "origin/${source_branch}") | head -1) +if [ -z "${newest_common_ancestor_sha}" ]; then + echo "Couldn’t find common ancestor with upstream main branch. This typically" + echo "happens if you branched from main a long time ago. Please update" + echo "your clone, rebase, and re-push your branch." + exit 1 +fi diff --git a/README b/README deleted file mode 100644 index b377fca6..00000000 --- a/README +++ /dev/null @@ -1,112 +0,0 @@ -D-Bus AT-SPI ------------- - -This version of at-spi is a major break from version 1.x. -It has been completely rewritten to use D-Bus rather than -ORBIT / CORBA for its transport protocol. - -An outdated page including instructions for testing, project status and -TODO items is at: - - https://wiki.linuxfoundation.org/accessibility/atk/at-spi/at-spi_on_d-bus - -The mailing list used for general questions is: - - https://lists.linuxfoundation.org/mailman/listinfo/accessibility-atspi - -For bug reports, feature requests, patches or enhancements please use: - - https://gitlab.gnome.org/GNOME/at-spi2-core/ - -A git repository with the latest development code is available at: - - https://gitlab.gnome.org/GNOME/at-spi2-core/ - -More information ----------------- - -The project was started with a D-Bus performance review -the results of which are available on the GNOME wiki. Keep in -mind that the D-Bus AT-SPI design documents on this page -have not been kept up to date. - - https://wiki.gnome.org/Accessibility/Documentation/GNOME2/ATSPI2-Investigation - -Other sources of relevant information about AT-SPI and Accessibility -include: - - https://wiki.gnome.org/Accessibility - https://community.kde.org/Accessibility - - -Contents of this package ------------------------- - -This package includes the protocol definitions for the new D-Bus -at-spi. - -Also included is the daemon necessary for forwarding device events -and registering accessible applicaitions. - - -Directory structure -------------------- - -The directories within this package are arranged as follows: - - xml - - This directory contains XML documents describing - the D-Bus protocol in the format used for D-Bus introspection. - - idl - - The D-Bus specification in an idl-like format. This is likely not - parseable by any existing tools, is not entirely up-to-date, and may - by removed in a future release. - - registryd - - The registry daemon code. The registry daemon - keeps a register of accessible applications and presents - this to clients (ATs). - It is also responsible for delivering device events. - - dbind - - Library to ease making D-Bus method calls, contains - marshalling code to convert function arguments - and a provided D-Bus signature into a D-Bus message. - - Used by libatspi. - - atspi - - C library for use by ATs. Wraps the various D-Bus calls, provides - an interface for listening to events, and caches some information about - accessible objects. Also contains some functions used by at-spi2-atk. - - bus - - A server that sits on the session bus and provides an interface - allowing applications to find the accessibility bus daemon, launching - it as needed. The accessibility bus is separate from the session bus - because it may in fact span user sessions if a user, for instance, - runs an application that escalates to run as root. The accessibility - bus is thus tied to the X session rather than the D-Bus session. - - doc - - Contains infrastructure for creating libatspi documentation. - - test - - Contains files that may be useful for testing AT-SPI. - - m4 - - Some macros used for building the module. - - po - - Infrastructure used for translation. diff --git a/README.md b/README.md new file mode 100644 index 00000000..6085fa83 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Assistive Technology Service Provider Interface (AT-SPI) + +This repository contains the [DBus][DBus] interface definitions for AT-SPI, the Assistive +Technology Service Provider Interface — the core of an accessibility stack for free +software systems. It also contains the basic daemons of the accessibility stack. + +The version control repository and bug tracker are at https://gitlab.gnome.org/GNOME/at-spi2-core/ + +The code in this repository is not intended for application programmers. To write +accessible applications, look into [ATK][ATK] or your programming language's bindings for +the `xml` DBus interfaces mentioned below. + +While this module started within the [GNOME][GNOME] project's umbrella, it is not used +only in GNOME. Other sources of relevant information about AT-SPI and Accessibility +include: + +* [GNOME Accessibility wiki][gnome-a11y-wiki] +* [KDE Accessibility wiki][kde-a11y-wiki] +* [Accessibility documentation for GNOME users][docs-users] + + +## Summary of this repository's contents + +* `xml` - DBus interfaces for accessibility, described in DBus's XML introspection format. + Ideally, your programming language's implementation of DBus makes use of these files to + generate callable bindings. + +* `bus` - Launcher for the session's accessibility bus; see its [README.md](bus/README.md) + for details. + +* `registryd` - Daemon that keeps track of accessible applications in the user's session, + and lets them talk to each other and to assistive technologies (ATs) like screen + readers. + +* `atspi` - Hand-written binding for the `xml` DBus interface above, for use from C with + [GObject][GObject]. This is not normally what you would use; use a language-specific + binding instead. This module is for use mainly by [`at-spi2-atk`][at-spi2-atk]. + +* `dbind` - DBus utilities for use by `atspi` above. `atspi` was written before the more + modern C bindings like [GDBusConnection][GDBus] were available, so there is a lot of + hand-written IPC here. + +* Documentation for the Gitlab [Continuous Integration pipeline](devel-docs/gitlab-ci.md). + +## Historical note + +Versions 1.x of AT-SPI were based on [CORBA][CORBA] for inter-process communication (IPC), +using GNOME's ORBit implementation thereof. During the GNOME 2 and 3 release series, +CORBA was phased out in favor of [DBus][DBus], a more modern IPC mechanism. + +The original CORBA interfaces for AT-SPI were based on Java's implementation of +accessibility. Later, these CORBA interfaces were translated to DBus. This is why the +interfaces sometimes have a 1990s feeling to them. + +The project was started with a D-Bus performance review, the results of which are available +on the GNOME wiki. Keep in mind that the D-Bus AT-SPI design documents on this page have +not been kept up to date. + + https://wiki.gnome.org/Accessibility/Documentation/GNOME2/ATSPI2-Investigation + + +[CORBA]: https://en.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture +[DBus]: https://www.freedesktop.org/wiki/Software/dbus/ +[GObject]: https://docs.gtk.org/gobject/ +[at-spi2-atk]: https://gitlab.gnome.org/GNOME/at-spi2-atk +[GDBus]: https://docs.gtk.org/gio/class.DBusConnection.html +[ATK]: https://gitlab.gnome.org/GNOME/atk/ +[GNOME]: https://www.gnome.org +[docs-users]: https://help.gnome.org/users/gnome-help/stable/a11y.html +[gnome-a11y-wiki]: https://wiki.gnome.org/Accessibility +[kde-a11y-wiki]: https://community.kde.org/Accessibility diff --git a/at-spi2-core.doap b/at-spi2-core.doap index 85d64dfe..7c1cde3f 100644 --- a/at-spi2-core.doap +++ b/at-spi2-core.doap @@ -29,4 +29,11 @@ wrapper around the DBus interfaces.</description> <gnome:userid>mgorse</gnome:userid> </foaf:Person> </maintainer> + <maintainer> + <foaf:Person> + <foaf:name>Federico Mena Quintero</foaf:name> + <foaf:mbox rdf:resource="mailto:federico@gnome.org" /> + <gnome:userid>federico</gnome:userid> + </foaf:Person> + </maintainer> </Project> diff --git a/atspi/atspi-device-legacy.c b/atspi/atspi-device-legacy.c index eb677685..bfb63d4b 100644 --- a/atspi/atspi-device-legacy.c +++ b/atspi/atspi-device-legacy.c @@ -212,8 +212,8 @@ atspi_device_legacy_unmap_modifier (AtspiDevice *device, gint keycode) AtspiLegacyKeyModifier *entry = l->data; if (entry->keycode == keycode) { - g_free (entry); priv->modifiers = g_slist_remove (priv->modifiers, entry); + g_free (entry); return; } } diff --git a/atspi/atspi-device-listener.c b/atspi/atspi-device-listener.c index 69f77d1d..9776ebdd 100644 --- a/atspi/atspi-device-listener.c +++ b/atspi/atspi-device-listener.c @@ -53,7 +53,7 @@ device_event_handler_new (AtspiDeviceListenerCB callback, } static gboolean -device_remove_datum (const AtspiDeviceEvent *event, void *user_data) +device_remove_datum (AtspiDeviceEvent *event, void *user_data) { AtspiDeviceListenerSimpleCB cb = user_data; return cb (event); diff --git a/atspi/atspi-device-x11.c b/atspi/atspi-device-x11.c index 4f88e609..eafdba62 100644 --- a/atspi/atspi-device-x11.c +++ b/atspi/atspi-device-x11.c @@ -516,8 +516,8 @@ atspi_device_x11_unmap_modifier (AtspiDevice *device, gint keycode) AtspiX11KeyModifier *entry = l->data; if (entry->keycode == keycode) { - g_free (entry); priv->modifiers = g_slist_remove (priv->modifiers, entry); + g_free (entry); return; } } diff --git a/atspi/atspi-event-listener.c b/atspi/atspi-event-listener.c index 5455b589..5cdc8064 100644 --- a/atspi/atspi-event-listener.c +++ b/atspi/atspi-event-listener.c @@ -1000,6 +1000,13 @@ _atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data) } category++; } + else + { + // TODO: Error + // Note that the single caller of this function, process_deferred_message(), ignores the return value. + // We should probably free the message if we aren't going to process it after all. + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } dbus_message_iter_get_basic (&iter, &detail); dbus_message_iter_next (&iter); dbus_message_iter_get_basic (&iter, &detail1); @@ -1087,6 +1094,8 @@ _atspi_dbus_handle_event (DBusConnection *bus, DBusMessage *message, void *data) break; } + g_assert (e.source != NULL); + dbus_message_iter_next (&iter); if (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_ARRAY) { diff --git a/atspi/atspi-misc.c b/atspi/atspi-misc.c index 7af46e9f..d896afbd 100644 --- a/atspi/atspi-misc.c +++ b/atspi/atspi-misc.c @@ -877,43 +877,6 @@ atspi_dbus_filter (DBusConnection *bus, DBusMessage *message, void *data) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } -/* - * Returns a 'canonicalized' value for DISPLAY, - * with the screen number stripped off if present. - * - * TODO: Avoid having duplicate functions for this here and in at-spi2-atk - */ -static gchar * -spi_display_name (void) -{ - char *canonical_display_name = NULL; - const gchar *display_env = g_getenv ("AT_SPI_DISPLAY"); - - if (!display_env) - { - display_env = g_getenv ("DISPLAY"); - if (!display_env || !display_env[0]) - return NULL; - else - { - gchar *display_p, *screen_p; - canonical_display_name = g_strdup (display_env); - display_p = g_utf8_strrchr (canonical_display_name, -1, ':'); - screen_p = g_utf8_strrchr (canonical_display_name, -1, '.'); - if (screen_p && display_p && (screen_p > display_p)) - { - *screen_p = '\0'; - } - } - } - else - { - canonical_display_name = g_strdup (display_env); - } - - return canonical_display_name; -} - /** * atspi_init: * @@ -1499,6 +1462,43 @@ _atspi_error_quark (void) * Gets the IOR from the XDisplay. */ #ifdef HAVE_X11 +/* + * Returns a 'canonicalized' value for DISPLAY, + * with the screen number stripped off if present. + * + * TODO: Avoid having duplicate functions for this here and in at-spi2-atk + */ +static gchar * +spi_display_name (void) +{ + char *canonical_display_name = NULL; + const gchar *display_env = g_getenv ("AT_SPI_DISPLAY"); + + if (!display_env) + { + display_env = g_getenv ("DISPLAY"); + if (!display_env || !display_env[0]) + return NULL; + else + { + gchar *display_p, *screen_p; + canonical_display_name = g_strdup (display_env); + display_p = g_utf8_strrchr (canonical_display_name, -1, ':'); + screen_p = g_utf8_strrchr (canonical_display_name, -1, '.'); + if (screen_p && display_p && (screen_p > display_p)) + { + *screen_p = '\0'; + } + } + } + else + { + canonical_display_name = g_strdup (display_env); + } + + return canonical_display_name; +} + static char * get_accessibility_bus_address_x11 (void) { diff --git a/bus/README b/bus/README deleted file mode 100644 index 40b9ad6b..00000000 --- a/bus/README +++ /dev/null @@ -1,10 +0,0 @@ -The a11y bus is accessed via two mechanisms: - -1) The DBus session bus, service "org.a11y.Bus", method "GetAddress") -2) The X11 root window property AT_SPI_BUS - -If the "toolkit-accessibility" variable is set, the bus is launched -immediately (and will be accessible immediately via the X11 property). -Otherwise, it will be spawned dynamically. - - diff --git a/bus/README.md b/bus/README.md new file mode 100644 index 00000000..eb14e9f4 --- /dev/null +++ b/bus/README.md @@ -0,0 +1,98 @@ +# Launcher for the accessibility bus + +The communications mechanism for accessibility does not run through the user's session +DBus; it runs in a separate bus just for accessibility purposes. The accessibility +interfaces for DBus are very chatty; using a separate bus prevents the main session bus +from getting too much traffic. + +Throughout this document we will distinguish between the **session bus** and the +**accessibility bus**. + +## Who launches the accessibility bus? + +This source directory `bus` contains a little daemon, `at-spi-bus-launcher`, which +launches the **accessibility bus** and manages its lifetime according to the user's +session. + +The **accessibility bus** is just a separate instance of `dbus-daemon` or equivalent, like +`dbus-broker`. That bus allows communication using the accessibility interfaces defined +in the `xml` directory in this repository. It also has the accessibility registry — +`registryd` in this repository, which claims the name `org.a11y.atspi.Registry` in that +bus. + +## When does the accessibility bus get launched? + +When a normal application starts up, it will want to find the **accesibility bus**, and +then contact the accessibility registry in that bus (`registryd` in this repository) to +inform the world that they are up and running. Finding the accessibility bus can then be +done on demand for normal applications, via the `GetAddress` method described below. + +However, a screen reader is special: it needs to start up automatically during login, and +immediatelly tell the accessibility registry (... via the **accessibility bus**) that it +is running. If you need a screen reader to use your computer, you cannot easily launch it +by hand if there is no screen reader already running! + +That is, if a screen reader is turned on — and we assume it will start up turned on for +future sessions — we need to launch the **accessibility bus** unconditionally, not on +demand, at session startup. This is why `at-spi-dbus-bus.desktop`, described below, is an +[XDG autostart][xdg-autostart] file which runs `at-spi-bus-launcher --launch-immediately`, +but only if a certain GSettings key is turned on. + +In summary, `at-spi-bus-launcher` will launch the **accessibility bus** under two situations: + +* On demand via the `GetAddress` method; see below. + +* Shortly after `at-spi-bus-launcher` starts up, if the gsettings key + `org.gnome.desktop.interface toolkit-accessibility` is set to true, due to the + `at-spi-dbus-bus.desktop` XDG autostart file. + +* The gsettings key `org.gnome.desktop.a11y.applications screen-reader-enabled` is set to true. + +## Contents of this `bus` directory + +This `bus` source directory has a configuration file for the `dbus-daemon` which will run +as the **accessibility bus**, and a helper daemon called `at-spi-bus-launcher`, which actually +starts that bus and makes its address visible to the user's session. The files are as follows: + +* `accessibility.conf.in` - template for the configuration for the accessibility bus, + which gets installed in `$(datadir)/defaults/at-spi2/accessibility.conf`. + +* `at-spi-bus-launcher.c` - See [`at-spi-bus-launcher`](#at-spi-bus-launcher) below. + +* `at-spi-dbus-bus.service.in` - template for a systemd user service to start `at-spi-bus-launcher`. + +* `org.a11y.Bus.service.in` - template for a DBus user service to start `at-spi-bus-launcher`. + +* `at-spi-dbus-bus.desktop.in` - template for a XDG autostart file to start + `at-spi-bus-launcher` at session startup, only if the `org.gnome.desktop.interface + toolkit-accessibility` GSettings key is turned on. + +* `00-at-spi` - script to set the `AT_SPI_BUS` property on the X root window, for + Wayland-based systems where XWayland is started on demand. That X window property is an + alternative way of finding the address of the **accessibility bus**. + +## at-spi-bus-launcher + +This is a tiny daemon which registers a service in the normal **session bus**, and which +can then be used to query the address of the actual **accessibility bus**. The daemon +claims ownership of the `org.a11y.Bus` name in the **session bus**, and exports a +single object, `/org/a11y/bus`, with two interfaces: + +* `org.a11y.Bus` - has a single `GetAddress` method, which returns the address of the + actual **accessibility bus**. Accessibility clients must use this address when creating + their initial DBus connection. + +* `org.a11y.Status` - has properties to query whether the **accessibility bus** is enabled + and whether a screen reader is running. + +`at-spi-bus-launcher` starts the separate `dbus-daemon` (or `dbus-broker` equivalent) for +the **accessibility bus** on demand. The following actions can cause it to launch: + +* Calling the `GetAddress` method. The daemon gets launched and queried for its address; + the method returns that. This is the normal way to start the accessibility bus. + +* If `at-spi-bus-launcher` was run with the `--launch-immediately` option, the + accessibility bus launches as soon as `at-spi-bus-launcher` is able to claim ownership + of the `org.a11y.Bus` name in the session bus. This is intended for session startup. + +[xdg-autostart]: https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html diff --git a/bus/at-spi-bus-launcher.c b/bus/at-spi-bus-launcher.c index d7c66900..d86e7d36 100644 --- a/bus/at-spi-bus-launcher.c +++ b/bus/at-spi-bus-launcher.c @@ -42,6 +42,7 @@ #ifdef DBUS_BROKER #include <systemd/sd-login.h> #endif +#include <sys/stat.h> typedef enum { A11Y_BUS_STATE_IDLE = 0, @@ -64,7 +65,8 @@ typedef struct { A11yBusState state; /* -1 == error, 0 == pending, > 0 == running */ - int a11y_bus_pid; + GPid a11y_bus_pid; + char *socket_name; char *a11y_bus_address; #ifdef HAVE_X11 gboolean x11_prop_set; @@ -84,10 +86,10 @@ static const gchar introspection_xml[] = " <arg type='s' name='address' direction='out'/>" " </method>" " </interface>" - "<interface name='org.a11y.Status'>" - "<property name='IsEnabled' type='b' access='readwrite'/>" - "<property name='ScreenReaderEnabled' type='b' access='readwrite'/>" - "</interface>" + " <interface name='org.a11y.Status'>" + " <property name='IsEnabled' type='b' access='readwrite'/>" + " <property name='ScreenReaderEnabled' type='b' access='readwrite'/>" + " </interface>" "</node>"; static GDBusNodeInfo *introspection_data = NULL; @@ -248,19 +250,44 @@ name_appeared_handler (GDBusConnection *connection, * Read all data from a file descriptor to a C string buffer. */ static gboolean -unix_read_all_fd_to_string (int fd, - char *buf, - ssize_t max_bytes) +unix_read_all_fd_to_string (int fd, + char *buf, + ssize_t max_bytes, + char **error_msg) { - ssize_t bytes_read; + g_assert (max_bytes > 1); + *error_msg = NULL; + + max_bytes -= 1; /* allow space for nul terminator */ - while (max_bytes > 1 && (bytes_read = read (fd, buf, MIN (4096, max_bytes - 1)))) + while (max_bytes > 1) { - if (bytes_read < 0) - return FALSE; - buf += bytes_read; - max_bytes -= bytes_read; + ssize_t bytes_read; + + again: + bytes_read = read (fd, buf, max_bytes); + + if (bytes_read == 0) + { + break; + } + else if (bytes_read > 0) + { + buf += bytes_read; + max_bytes -= bytes_read; + } + else if (errno == EINTR) + { + goto again; + } + else + { + int err_save = errno; + *error_msg = g_strdup_printf ("Failed to read data from accessibility bus: %s", g_strerror (err_save)); + return FALSE; + } } + *buf = '\0'; return TRUE; } @@ -284,53 +311,69 @@ on_bus_exited (GPid pid, app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status)); } g_main_loop_quit (app->loop); -} +} #ifdef DBUS_DAEMON -static void -setup_bus_child_daemon (gpointer data) +static gboolean +ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path) { - A11yBusLauncher *app = data; - (void) app; + char *address_param; - close (app->pipefd[0]); - dup2 (app->pipefd[1], 3); - close (app->pipefd[1]); + if (app->socket_name) + { + gchar *escaped_address = g_dbus_address_escape_value (app->socket_name); + address_param = g_strconcat ("--address=unix:path=", escaped_address, NULL); + g_free (escaped_address); + } + else + { + address_param = NULL; + } - /* On Linux, tell the bus process to exit if this process goes away */ -#ifdef __linux__ - prctl (PR_SET_PDEATHSIG, 15); -#endif -} + if (pipe (app->pipefd) < 0) + g_error ("Failed to create pipe: %s", strerror (errno)); -static gboolean -ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path) -{ - char *argv[] = { DBUS_DAEMON, config_path, "--nofork", "--print-address", "3", NULL }; + char *print_address_fd_param = g_strdup_printf ("%d", app->pipefd[1]); + + char *argv[] = { DBUS_DAEMON, config_path, "--nofork", "--print-address", print_address_fd_param, address_param, NULL }; + gint source_fds[1] = { app->pipefd[1] }; + gint target_fds[1] = { app->pipefd[1] }; + G_STATIC_ASSERT (G_N_ELEMENTS (source_fds) == G_N_ELEMENTS (target_fds)); GPid pid; char addr_buf[2048]; GError *error = NULL; - - if (pipe (app->pipefd) < 0) - g_error ("Failed to create pipe: %s", strerror (errno)); + char *error_from_read; g_clear_pointer (&app->a11y_launch_error_message, g_free); - if (!g_spawn_async (NULL, - argv, - NULL, - G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, - setup_bus_child_daemon, - app, - &pid, - &error)) + if (!g_spawn_async_with_pipes_and_fds (NULL, + (const gchar * const *) argv, + NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_LEAVE_DESCRIPTORS_OPEN, + NULL, /* child_setup */ + app, + -1, /* stdin_fd */ + -1, /* stdout_fd */ + -1, /* stdout_fd */ + source_fds, + target_fds, + G_N_ELEMENTS (source_fds), /* n_fds in source_fds and target_fds */ + &pid, + NULL, /* stdin_pipe_out */ + NULL, /* stdout_pipe_out */ + NULL, /* stderr_pipe_out */ + &error)) { app->a11y_bus_pid = -1; app->a11y_launch_error_message = g_strdup (error->message); g_clear_error (&error); + g_free (address_param); + g_free (print_address_fd_param); goto error; } + g_free (address_param); + g_free (print_address_fd_param); close (app->pipefd[1]); app->pipefd[1] = -1; @@ -339,10 +382,12 @@ ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path) app->state = A11Y_BUS_STATE_READING_ADDRESS; app->a11y_bus_pid = pid; g_debug ("Launched a11y bus, child is %ld", (long) pid); - if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf))) + error_from_read = NULL; + if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf), &error_from_read)) { - app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno)); + app->a11y_launch_error_message = error_from_read; kill (app->a11y_bus_pid, SIGTERM); + g_spawn_close_pid (app->a11y_bus_pid); app->a11y_bus_pid = -1; goto error; } @@ -386,9 +431,6 @@ setup_bus_child_broker (gpointer data) pid_str = g_strdup_printf("%u", getpid()); g_setenv("LISTEN_PID", pid_str, TRUE); g_free(pid_str); - - /* Tell the bus process to exit if this process goes away */ - prctl (PR_SET_PDEATHSIG, SIGTERM); } static gboolean @@ -396,11 +438,14 @@ ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path) { char *argv[] = { DBUS_BROKER, config_path, "--scope", "user", NULL }; char *unit; - struct sockaddr_un addr = { .sun_family = AF_UNIX }; + struct sockaddr_un addr = { .sun_family = AF_UNIX, '\0' }; socklen_t addr_len = sizeof(addr); GPid pid; GError *error = NULL; + if (app->socket_name) + strcpy (addr.sun_path, app->socket_name); + /* This detects whether we are running under systemd. We only try to * use dbus-broker if we are running under systemd because D-Bus * service activation won't work otherwise. @@ -418,10 +463,11 @@ ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path) if ((app->listenfd = socket (PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) g_error ("Failed to create listening socket: %s", strerror (errno)); - if (bind (app->listenfd, (struct sockaddr *)&addr, sizeof(sa_family_t)) < 0) + if (bind (app->listenfd, (struct sockaddr *)&addr, addr_len) < 0) g_error ("Failed to bind listening socket: %s", strerror (errno)); - if (getsockname (app->listenfd, (struct sockaddr *)&addr, &addr_len) < 0) + if (!app->socket_name && + getsockname (app->listenfd, (struct sockaddr *)&addr, &addr_len) < 0) g_error ("Failed to get socket name for listening socket: %s", strerror(errno)); if (listen (app->listenfd, 1024) < 0) @@ -452,7 +498,10 @@ ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path) g_debug ("Launched a11y bus, child is %ld", (long) pid); app->state = A11Y_BUS_STATE_RUNNING; - app->a11y_bus_address = g_strconcat("unix:abstract=", addr.sun_path + 1, NULL); + if (app->socket_name) + app->a11y_bus_address = g_strconcat("unix:path=", addr.sun_path, NULL); + else + app->a11y_bus_address = g_strconcat("unix:abstract=", addr.sun_path + 1, NULL); g_debug ("a11y bus address: %s", app->a11y_bus_address); return TRUE; @@ -476,6 +525,7 @@ ensure_a11y_bus (A11yBusLauncher *app) { char *config_path = NULL; gboolean success = FALSE; + const gchar *xdg_runtime_dir; if (app->a11y_bus_pid != 0) return FALSE; @@ -485,6 +535,25 @@ ensure_a11y_bus (A11yBusLauncher *app) else config_path = "--config-file="DATADIR"/defaults/at-spi2/accessibility.conf"; + xdg_runtime_dir = g_get_user_runtime_dir (); + if (xdg_runtime_dir) + { + const gchar *display = g_getenv ("DISPLAY"); + gchar *at_spi_dir = g_strconcat (xdg_runtime_dir, "/at-spi", NULL); + gchar *p; + mkdir (at_spi_dir, 0700); + app->socket_name = g_strconcat (at_spi_dir, "/bus", display, NULL); + g_free (at_spi_dir); + p = strchr (app->socket_name, ':'); + if (p) + *p = '_'; + if (strlen (app->socket_name) >= 100) + { + g_free (app->socket_name); + app->socket_name = NULL; + } + } + #ifdef WANT_DBUS_BROKER success = ensure_a11y_bus_broker (app, config_path); if (!success) @@ -816,6 +885,11 @@ get_schema (const gchar *name) { #if GLIB_CHECK_VERSION (2, 32, 0) GSettingsSchemaSource *source = g_settings_schema_source_get_default (); + if (!source) + { + g_error ("Cannot get the default GSettingsSchemaSource - is the gsettings-desktop-schemas package installed?"); + } + GSettingsSchema *schema = g_settings_schema_source_lookup (source, name, FALSE); if (schema == NULL) @@ -856,7 +930,7 @@ main (int argc, gboolean screen_reader_set = FALSE; gint i; - _global_app = g_slice_new0 (A11yBusLauncher); + _global_app = g_new0 (A11yBusLauncher, 1); _global_app->loop = g_main_loop_new (NULL, FALSE); for (i = 1; i < argc; i++) @@ -917,7 +991,11 @@ main (int argc, g_main_loop_run (_global_app->loop); if (_global_app->a11y_bus_pid > 0) - kill (_global_app->a11y_bus_pid, SIGTERM); + { + kill (_global_app->a11y_bus_pid, SIGTERM); + g_spawn_close_pid (_global_app->a11y_bus_pid); + _global_app->a11y_bus_pid = -1; + } /* Clear the X property if our bus is gone; in the case where e.g. * GDM is launching a login on an X server it was using before, diff --git a/dbind/dbind-any.c b/dbind/dbind-any.c index efdba738..512c1b34 100644 --- a/dbind/dbind-any.c +++ b/dbind/dbind-any.c @@ -696,14 +696,8 @@ dbind_any_demarshal_va (DBusMessageIter *iter, { const char *p = *arg_types; - /* Pass in args */ + /* Just consume the in args without doing anything to them */ for (;*p != '\0' && *p != '=';) { - int intarg; - void *ptrarg; - double doublearg; - dbus_int64_t int64arg; - void *arg = NULL; - switch (*p) { case DBUS_TYPE_BYTE: case DBUS_TYPE_BOOLEAN: @@ -711,14 +705,14 @@ dbind_any_demarshal_va (DBusMessageIter *iter, case DBUS_TYPE_UINT16: case DBUS_TYPE_INT32: case DBUS_TYPE_UINT32: - intarg = va_arg (args, int); + va_arg (args, int); break; case DBUS_TYPE_INT64: case DBUS_TYPE_UINT64: - int64arg = va_arg (args, dbus_int64_t); + va_arg (args, dbus_int64_t); break; case DBUS_TYPE_DOUBLE: - doublearg = va_arg (args, double); + va_arg (args, double); break; /* ptr types */ case DBUS_TYPE_STRING: @@ -726,18 +720,18 @@ dbind_any_demarshal_va (DBusMessageIter *iter, case DBUS_TYPE_SIGNATURE: case DBUS_TYPE_ARRAY: case DBUS_TYPE_DICT_ENTRY: - ptrarg = va_arg (args, void *); + va_arg (args, void *); break; case DBUS_STRUCT_BEGIN_CHAR: - ptrarg = va_arg (args, void *); + va_arg (args, void *); break; case DBUS_DICT_ENTRY_BEGIN_CHAR: - ptrarg = va_arg (args, void *); + va_arg (args, void *); break; case DBUS_TYPE_VARIANT: fprintf (stderr, "No variant support yet - very toolkit specific\n"); - ptrarg = va_arg (args, void *); + va_arg (args, void *); break; default: fprintf (stderr, "Unknown / invalid arg type %c\n", *p); diff --git a/dbind/dbtest.c b/dbind/dbtest.c index b338c036..511839c3 100644 --- a/dbind/dbtest.c +++ b/dbind/dbtest.c @@ -40,7 +40,10 @@ void demarshal (DBusMessage *msg, const char *type, void *ptr) DBusMessageIter iter; if (!dbus_message_iter_init (msg, &iter)) + { fprintf (stderr, "no data in msg\n"); + g_assert_not_reached (); + } else dbind_any_demarshal (&iter, &type, &ptr); } @@ -293,8 +296,8 @@ void test_struct_with_array () demarshal (msg, TYPEOF_ARRAYSTRUCT, &a2); q = &g_array_index (a2, ArrayStruct, 0); - g_assert (p[0].pad1 == 2); - g_assert (g_array_index (p[1].vals, dbus_uint32_t, 1) == 1000000000); + g_assert (q[0].pad1 == 2); + g_assert (g_array_index (q[1].vals, dbus_uint32_t, 1) == 1000000000); printf ("struct with array ok\n"); diff --git a/devel-docs/gitlab-ci.md b/devel-docs/gitlab-ci.md new file mode 100644 index 00000000..d7d675e7 --- /dev/null +++ b/devel-docs/gitlab-ci.md @@ -0,0 +1,188 @@ +# Gitlab Continuous Integration (CI) for at-spi2-core + +Summary: make the robots set up an environment for running the test +suite, run it, and report back to us. + +If you have questions about the CI, mail federico@gnome.org, or [file +an issue](https://gitlab.gnome.org/GNOME/at-spi2-core/-/issues) and +mention `@federico` in it. + +Table of contents: + +[[_TOC_]] + +# Quick overview + +By having a [`.gitlab-ci.yml`](../.gitlab-ci.yml) file in the toplevel +directory of a project, Gitlab knows that it must run a continuous +integration pipeline when certain events occur, for example, when +someone creates a merge request, or pushes to a branch. + +What's a pipeline? It is an automated version of the following. +Running the test suite for at-spi2-core involves some repetitive +steps: + +* Create a pristine and minimal environment for testing, without all the random + gunk from one's development system. Gitlab CI uses Linux containers, + with pre-built operating system images in the [Open Container + Initiative][OCI] format — this is what Docker, Podman, etc. all use. + +* Install the build-time dependencies (gcc, meson, libfoo-devel, + etc.), the test-time dependencies (dbus-daemon, etc.) in that + pristine environment, as well as special tools (lcov, libasan, + clang-tools). + +* Run the build and install it, and run the test suite. + +* Run variations of the build and test suite with other tools — for + example, using static analysis during compilation, or with address + sanitizer (asan), or with a code coverage tool. Gitlab can collect + the analysis results of each of these tools and present them as part + of the merge request that is being evaluated. It also lets + developers obtain those useful results without dealing with a lot of + fiddly tools on their own computers. + +Additionally, on each pipeline run we'd like to do extra repetitive +work like building the reference documentation, and publishing it on a +web page. + +The `.gitlab-ci.yml` file defines the CI pipeline, the jobs it will +run (build/test, coverage, asan, static-scan, etc.), and the locations +where each job's artifacts will be stored. + +What's an artifact or a job? Read on! + +# A little glossary + +**Pipeline** - A collection of **jobs**, which can be run in parallel +or sequentially. For example, a pair of "build" and "test" jobs would +need to run sequentially, but maybe a "render documentation" job can +run in parallel with them. Similarly, "build" jobs for various +distributions or configurations could be run in parallel. + +**Job** - Think of it as running a shell script within a container. +It can have input from other previous jobs: if you have separate +"build" and "test" jobs, then the "build" job will want to keep around +its compiled artifacts so that the "test" job can use them. It can +provide output artifacts that can be stored for human perusal, or for +use by other jobs. + +**Artifact** - Something produced from a job. If your job compiles +binaries, those binaries could be artifacts if you decide to keep them +around for use later. A documentation job can produce HTML artifacts +from the rendered documentation. A code coverage job will produce a +coverage report artifact. + +**Runner** - An operating system setup for running jobs. +Gitlab.gnome.org provides runners for Linux, BSD, Windows, and MacOS. +For example. the Linux runners let you use any OCI image, so you can +test on openSUSE, Fedora, a custom distro, etc. You don't normally +need to be concerned with runners; Gitlab assigns the shared runners +automatically to your pipeline. + +**Container** - You can think of it as a chroot with extra isolation, +or a lightweight virtual machine. Basically, the Linux kernel can +isolate groups of processes in control groups (cgroups). Each cgroup +can have a different view of the file system, as if you had a +different chroot for each cgroup. Cgroups can be isolated to be in +their own PID namespace, so running "ps" in the container will not +show all the processes in the system, but only those inside the +container's cgroup. File system overlays allow you to have read-only +images for the operating system (the OCI images we talked about above) +plus a read-write overlay that is kept around only during the lifetime +of a container, or persistently if one wants. For Gitlab CI one does +not need to deal with containers directly, but keep in mind that your +jobs will run inside a container, which is more limited than e.g. a +shell session on a graphical, development machine. + +# The CI pipeline for at-spi2-core + +The `.gitlab-ci.yml` file is a more-or-less declarative description +the CI pipeline, with some `script` sections which are imperative +commands to actually *do stuff*. + +Jobs are run in `stages`, and the names of the stages are declared +near the beginning of the YAML file. The stage names are arbitrary; +the ones here follow some informal GNOME conventions. + +Jobs are declared at the toplevel of the YAML file, and they are +distinguished from other declarations by having a container `image` +declared for them, as well as a `script` to execute. + +Many jobs need exactly the same kind of setup (same container images, +mostly same package dependencies), so they use `extends:` to use a +declared template with all that stuff instead of redeclaring it each +time. In our configuration, the `.only-default` template has the +`PROJECT_DEPS`, with the dependencies that most jobs need. The +`.build-setup` template is for the analysis jobs, and it lets them +declare `EXTRA_DEPS` as an environment variable with the names of +extra dependencies: for example, the coverage job puts `lcov` in +`EXTRA_DEPS`. The commands in `before_script` blocks use these +environment variables to install the package dependencies, for example +`zypper install -y ${PROJECT_DEPS}` for an openSUSE job. + +The `build` stage has these jobs: + +* `opensuse-x86_64` - Extends the `.build-default` rule, + builds/installs the code, and runs the tests. Generally this is the + job that one cares about during regular development. + +The `analysis` stage has these jobs: + +* `static-scan` - Runs static analysis during compilation, which + performs interprocedural analysis to detect things like double + `free()` or uninitialized struct fields across functions. + +* `asan-build` - Builds and runs with Address Sanitizer (libasan). + +* `coverage` - Instruments the build to get code coverage information, + and runs the test suite to see how much of the code it manages to + exercise. This is to see which code paths may be untested + automatically, and to decide which ones would require manual + testing, or refactoring to allow automated testing. + +As of 2021/Dec/15 there are some commented-out jobs to generate +documentation and publish it; this needs to be made to work. + +# General advice and future work + +A failed run of a CI pipeline should trouble you; it either means that +some test broke, or that something is not completely deterministic. +Fix it at once. + +Try not to accept merge requests that fail the CI, as this will make +`git bisect` hard in the future. There are tools like Marge-bot to +enforce this; ask federico@gnome.org about it. Read ["The Not Rocket +Science Rule Of Software +Engineering"](https://graydon.livejournal.com/186550.html), which can +be summarized as "automatically maintain a repository of code that +always passes all the tests" for inspiration. Marge-bot is an +implementation of that, and can be used with Gitlab. + +If your software can be configured to build with substantial changes, +the CI pipeline should have jobs that test each of those +configurations. For example, at-spi-bus-launcher operates differently +depending on whether dbus-daemon or dbus-broker are being used. As of +2021/Dec/15 the CI only tests dbus-daemon; there should be a test for +dbus-broker, too. + +Although the YAML syntax for `.gitlab-ci.yml` is a bit magic, the +scripts and configuration are quite amenable to refactoring. Do it +often! + +Minimizing the amount of time that CI takes to run is a good goal. It +reduces energy consumption in the build farm, and allows you to have a +faster feedback loop. Instead of installing package dependencies on +each job, we can move to prebuilt container images. + +# References + +Full documentation for Gitlab CI: https://docs.gitlab.com/ee/ci/ + +Introduction to Gitlab CI: https://docs.gitlab.com/ee/ci/quick_start/index.html + +at-spi2-core's CI pipeline is mostly [cut-and-pasted from +libgweather](https://gitlab.gnome.org/GNOME/libgweather/-/blob/main/.gitlab-ci.yml). +Thanks to Emmanuele Bassi for his advice on how to use it. + +[OCI]: https://opencontainers.org/ diff --git a/meson.build b/meson.build index b5104c8c..fa6ca53e 100644 --- a/meson.build +++ b/meson.build @@ -47,6 +47,7 @@ libdbus_req_version = '>= 1.5' glib_req_version = '>= 2.62.0' gobject_req_version = '>= 2.0.0' gio_req_version = '>= 2.28.0' +gir_req_version = '>= 0.6.7' libdbus_dep = dependency('dbus-1', version: libdbus_req_version) glib_dep = dependency('glib-2.0', version: glib_req_version) @@ -62,6 +63,8 @@ endif x11_deps = [] x11_option = get_option('x11') +# ensure x11_dep is defined for use elsewhere, such as bus/meson.build +x11_dep = dependency('', required: false) if x11_option != 'no' x11_dep = dependency('x11', required: false) @@ -104,7 +107,7 @@ have_gir = false introspection_option = get_option('introspection') if introspection_option != 'no' - gir_dep = dependency('gobject-introspection-1.0', version: '>= 0.6.7', required: false) + gir_dep = dependency('gobject-introspection-1.0', version: gir_req_version, required: false) if gir_dep.found() have_gir = true @@ -31,6 +31,7 @@ hi hr hu id +is it ja kk diff --git a/po/is.po b/po/is.po new file mode 100644 index 00000000..61246a77 --- /dev/null +++ b/po/is.po @@ -0,0 +1,26 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Sveinn í Felli <sv1@fellsnet.is>, 2021. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/at-spi2-core/issues\n" +"POT-Creation-Date: 2021-12-08 22:05+0000\n" +"PO-Revision-Date: 2021-12-15 23:05+0000\n" +"Last-Translator: Sveinn í Felli <sv1@fellsnet.is>\n" +"Language-Team: Icelandic <translation-team-is@lists.sourceforge.org>\n" +"Language: is\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 19.12.3\n" + +#: atspi/atspi-component.c:332 atspi/atspi-misc.c:1105 atspi/atspi-value.c:111 +msgid "The application no longer exists" +msgstr "Forritið er ekki lengur til" + +#: atspi/atspi-misc.c:1888 +msgid "Attempted synchronous call where prohibited" +msgstr "Reyndi samhæft kall þegar slíkt er bannað" diff --git a/registryd/deviceeventcontroller-x11.c b/registryd/deviceeventcontroller-x11.c index 55239f4f..65560480 100644 --- a/registryd/deviceeventcontroller-x11.c +++ b/registryd/deviceeventcontroller-x11.c @@ -34,11 +34,6 @@ #include <stdio.h> #include <sys/time.h> -#include <X11/Xlib.h> -#include <X11/Xutil.h> -#include <X11/extensions/XTest.h> -#include <X11/XKBlib.h> - #define XK_MISCELLANY #define XK_LATIN1 #include <X11/keysymdef.h> @@ -54,6 +49,7 @@ #include "display.h" #include "event-source.h" +#include "deviceeventcontroller-x11.h" #include "deviceeventcontroller.h" #include "reentrant-list.h" @@ -89,31 +85,26 @@ static XModifierKeymap* xmkeymap = NULL; static int (*x_default_error_handler) (Display *display, XErrorEvent *error_event); -typedef struct { - Display *xevie_display; - unsigned int last_press_keycode; - unsigned int last_release_keycode; - struct timeval last_press_time; - struct timeval last_release_time; - int have_xkb; - int xkb_major_extension_opcode; - int xkb_base_event_code; - int xkb_base_error_code; - unsigned int xkb_latch_mask; - unsigned int pending_xkb_mod_relatch_mask; - XkbDescPtr xkb_desc; - KeyCode reserved_keycode; - KeySym reserved_keysym; - guint reserved_reset_timeout; -} DEControllerPrivateData; - static void spi_controller_register_with_devices (SpiDEController *controller); static gboolean spi_device_event_controller_forward_key_event (SpiDEController *controller, const XEvent *event); - static SpiDEController *saved_controller; +/* Normally this function would be provided by the macro call in deviceeventcontroller.c: + * G_DEFINE_TYPE_WITH_CODE (..., G_ADD_PRIVATE (SpiDEController)) + * + * However, that machinery creates a static function for + * _get_instance_private, so it is only visible in that file. Here + * we'll re-define it by hand, using the same name as that generated + * function in case we can later merge the implementations together. + */ +static SpiDEControllerPrivate * +spi_device_event_controller_get_instance_private (SpiDEController *controller) +{ + return g_type_instance_get_private ((GTypeInstance *) controller, SPI_DEVICE_EVENT_CONTROLLER_TYPE); +} + static unsigned int keysym_mod_mask (KeySym keysym, KeyCode keycode) { @@ -174,7 +165,7 @@ keysym_mod_mask (KeySym keysym, KeyCode keycode) } static gboolean -replace_map_keysym (DEControllerPrivateData *priv, KeyCode keycode, KeySym keysym) +replace_map_keysym (SpiDEControllerPrivate *priv, KeyCode keycode, KeySym keysym) { #ifdef HAVE_XKB Display *dpy = spi_get_display (); @@ -214,7 +205,7 @@ replace_map_keysym (DEControllerPrivateData *priv, KeyCode keycode, KeySym keysy static gboolean spi_dec_reset_reserved (gpointer data) { - DEControllerPrivateData *priv = data; + SpiDEControllerPrivate *priv = data; replace_map_keysym (priv, priv->reserved_keycode, priv->reserved_keysym); priv->reserved_reset_timeout = 0; return FALSE; @@ -233,7 +224,7 @@ spi_dec_x11_get_keycode (SpiDEController *controller, keycode = XKeysymToKeycode (spi_get_display (), (KeySym) keysym); if (!keycode && fix) { - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); /* if there's no keycode available, fix it */ if (replace_map_keysym (priv, priv->reserved_keycode, keysym)) { @@ -258,7 +249,7 @@ spi_dec_x11_get_keycode (SpiDEController *controller, static void spi_dec_set_unlatch_pending (SpiDEController *controller, unsigned mask) { - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); #ifdef SPI_XKB_DEBUG if (priv->xkb_latch_mask) fprintf (stderr, "unlatch pending! %x\n", priv->xkb_latch_mask); @@ -392,11 +383,15 @@ spi_dec_x11_mouse_check (SpiDEController *controller, Window root_return, child_return; Display *display = spi_get_display (); - if (display != NULL) - XQueryPointer(display, DefaultRootWindow (display), - &root_return, &child_return, - x, y, - &win_x_return, &win_y_return, &mask_return); + if (display == NULL) + { + return 0; + } + + XQueryPointer(display, DefaultRootWindow (display), + &root_return, &child_return, + x, y, + &win_x_return, &win_y_return, &mask_return); /* * Since many clients grab the pointer, and X goes an automatic * pointer grab on mouse-down, we often must detect mouse button events @@ -471,7 +466,7 @@ spi_dec_init_mouse_listener (SpiDEController *dec) static void spi_dec_clear_unlatch_pending (SpiDEController *controller) { - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); priv->xkb_latch_mask = 0; } @@ -563,11 +558,9 @@ spi_device_event_controller_forward_mouse_event (SpiDEController *controller, static void global_filter_fn (XEvent *xevent, void *data) { - SpiDEController *controller; - DEControllerPrivateData *priv; + SpiDEController *controller = SPI_DEVICE_EVENT_CONTROLLER (data); + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); Display *display = spi_get_display (); - controller = SPI_DEVICE_EVENT_CONTROLLER (data); - priv = controller->priv; if (xevent->type == MappingNotify) xmkeymap = NULL; @@ -705,10 +698,9 @@ _spi_controller_device_error_handler (Display *display, XErrorEvent *error) static void spi_controller_register_with_devices (SpiDEController *controller) { - DEControllerPrivateData *priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); int event_base, error_base, major_version, minor_version; - priv = controller->priv; if (XTestQueryExtension (spi_get_display(), &event_base, &error_base, &major_version, &minor_version)) { XTestGrabControl (spi_get_display (), True); @@ -973,7 +965,7 @@ static unsigned int xkb_get_slowkeys_delay (SpiDEController *controller) { unsigned int retval = 0; - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); #ifdef HAVE_XKB #ifdef XKB_HAS_GET_SLOW_KEYS_DELAY retval = XkbGetSlowKeysDelay (spi_get_display (), @@ -1001,7 +993,7 @@ static unsigned int xkb_get_bouncekeys_delay (SpiDEController *controller) { unsigned int retval = 0; - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); #ifdef HAVE_XKB #ifdef XKB_HAS_GET_BOUNCE_KEYS_DELAY retval = XkbGetBounceKeysDelay (spi_get_display (), @@ -1027,7 +1019,7 @@ xkb_get_bouncekeys_delay (SpiDEController *controller) static gboolean spi_dec_x11_synth_keycode_press (SpiDEController *controller, - unsigned int keycode) + unsigned int keycode) { unsigned int time = CurrentTime; unsigned int bounce_delay; @@ -1035,7 +1027,7 @@ spi_dec_x11_synth_keycode_press (SpiDEController *controller, unsigned int elapsed_msec; struct timeval tv; #endif - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); spi_x_error_trap (); if (keycode == priv->last_release_keycode) @@ -1081,7 +1073,7 @@ spi_dec_x11_synth_keycode_release (SpiDEController *controller, unsigned int elapsed_msec; struct timeval tv; #endif - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); spi_x_error_trap (); if (keycode == priv->last_press_keycode) @@ -1119,7 +1111,7 @@ spi_dec_x11_synth_keycode_release (SpiDEController *controller, static gboolean spi_dec_x11_lock_modifiers (SpiDEController *controller, unsigned modifiers) { - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); if (priv->have_xkb) { return XkbLockModifiers (spi_get_display (), XkbUseCoreKbd, @@ -1138,7 +1130,7 @@ spi_dec_x11_lock_modifiers (SpiDEController *controller, unsigned modifiers) static gboolean spi_dec_x11_unlock_modifiers (SpiDEController *controller, unsigned modifiers) { - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); if (priv->have_xkb) { return XkbLockModifiers (spi_get_display (), XkbUseCoreKbd, @@ -1238,7 +1230,7 @@ spi_dec_x11_synth_keystring (SpiDEController *controller, guint synth_type, gint static void spi_dec_x11_init (SpiDEController *controller) { - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); spi_events_init (spi_get_display ()); @@ -1254,7 +1246,7 @@ spi_dec_x11_init (SpiDEController *controller) static void spi_dec_x11_finalize (SpiDEController *controller) { - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); /* disconnect any special listeners, get rid of outstanding keygrabs */ XUngrabKey (spi_get_display (), AnyKey, AnyModifier, DefaultRootWindow (spi_get_display ())); @@ -1268,7 +1260,7 @@ static gboolean spi_device_event_controller_forward_key_event (SpiDEController *controller, const XEvent *event) { - DEControllerPrivateData *priv = controller->priv; + SpiDEControllerPrivate *priv = spi_device_event_controller_get_instance_private (controller); Accessibility_DeviceEvent key_event; gboolean ret; @@ -1411,8 +1403,6 @@ spi_dec_x11_generate_mouse_event (SpiDEController *controller, void spi_dec_setup_x11 (SpiDEControllerClass *klass) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); - klass->plat.get_keycode = spi_dec_x11_get_keycode; klass->plat.mouse_check = spi_dec_x11_mouse_check; klass->plat.synth_keycode_press = spi_dec_x11_synth_keycode_press; @@ -1427,6 +1417,4 @@ spi_dec_setup_x11 (SpiDEControllerClass *klass) klass->plat.init = spi_dec_x11_init; klass->plat.finalize = spi_dec_x11_finalize; - - g_type_class_add_private (object_class, sizeof (DEControllerPrivateData)); } diff --git a/registryd/deviceeventcontroller-x11.h b/registryd/deviceeventcontroller-x11.h new file mode 100644 index 00000000..62e29843 --- /dev/null +++ b/registryd/deviceeventcontroller-x11.h @@ -0,0 +1,28 @@ +#ifndef _DEVICEEVENTCONTROLLER_X11_H_ +#define _DEVICEEVENTCONTROLLER_X11_H_ + +#include <glib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/XTest.h> +#include <X11/XKBlib.h> + +typedef struct { + Display *xevie_display; + unsigned int last_press_keycode; + unsigned int last_release_keycode; + struct timeval last_press_time; + struct timeval last_release_time; + int have_xkb; + int xkb_major_extension_opcode; + int xkb_base_event_code; + int xkb_base_error_code; + unsigned int xkb_latch_mask; + unsigned int pending_xkb_mod_relatch_mask; + XkbDescPtr xkb_desc; + KeyCode reserved_keycode; + KeySym reserved_keysym; + guint reserved_reset_timeout; +} SpiDEControllerPrivate; + +#endif /* _DEVICEEVENTCONTROLLER_X11_H_ */ diff --git a/registryd/deviceeventcontroller.c b/registryd/deviceeventcontroller.c index 4111e8f3..0e54357f 100644 --- a/registryd/deviceeventcontroller.c +++ b/registryd/deviceeventcontroller.c @@ -43,16 +43,16 @@ #include "de-marshaller.h" #include "keymasks.h" +#include "deviceeventcontroller.h" +#include "reentrant-list.h" +#include "introspection.h" + #ifdef HAVE_X11 +#include "deviceeventcontroller-x11.h" #include "display.h" #include "event-source.h" #endif -#include "deviceeventcontroller.h" -#include "reentrant-list.h" - -#include "introspection.h" - #define CHECK_RELEASE_DELAY 20 #define BIT(c, x) (c[x/8]&(1<<(x%8))) static SpiDEController *saved_controller; @@ -60,6 +60,16 @@ static SpiDEController *saved_controller; /* Our parent Gtk object type */ #define PARENT_TYPE G_TYPE_OBJECT +#ifndef HAVE_X11 +/* If we are using X11, SpiDEControllerPrivate is defined in deviceeventcontroller-x11.h. + * Otherwise, there is no private data and so we use a dummy struct. + * This is so that G_ADD_PRIVATE() will have a type to work with. + */ +typedef struct { + int _dummy; +} SpiDEControllerPrivate; +#endif + /* A pointer to our parent object class */ static int spi_error_code = 0; struct _SpiPoint { @@ -97,7 +107,8 @@ static gboolean eventtype_seq_contains_event (dbus_uint32_t types, static gboolean spi_dec_poll_mouse_moving (gpointer data); static gboolean spi_dec_poll_mouse_idle (gpointer data); -G_DEFINE_TYPE(SpiDEController, spi_device_event_controller, G_TYPE_OBJECT) +G_DEFINE_TYPE_WITH_CODE(SpiDEController, spi_device_event_controller, G_TYPE_OBJECT, + G_ADD_PRIVATE (SpiDEController)) static gint spi_dec_plat_get_keycode (SpiDEController *controller, @@ -111,7 +122,13 @@ spi_dec_plat_get_keycode (SpiDEController *controller, if (klass->plat.get_keycode) return klass->plat.get_keycode (controller, keysym, key_str, fix, modmask); else - return keysym; + { + if (modmask) + { + *modmask = 0; + } + return keysym; + } } static guint @@ -123,7 +140,13 @@ spi_dec_plat_mouse_check (SpiDEController *controller, if (klass->plat.mouse_check) return klass->plat.mouse_check (controller, x, y, moved); else - return 0; + { + if (moved) + { + *moved = FALSE; + } + return 0; + } } static gboolean @@ -873,8 +896,8 @@ reset_hung_process (DBusPendingCall *pending, void *data) { if (!strcmp (l->data, dest)) { - g_free (l->data); hung_processes = g_slist_remove (hung_processes, l->data); + g_free (l->data); break; } } @@ -898,8 +921,8 @@ reset_hung_process_from_ping (DBusPendingCall *pending, void *data) { if (!strcmp (l->data, data)) { - g_free (l->data); hung_processes = g_slist_remove (hung_processes, l->data); + g_free (l->data); break; } } @@ -1863,9 +1886,7 @@ spi_device_event_controller_class_init (SpiDEControllerClass *klass) #ifdef HAVE_X11 if (g_getenv ("DISPLAY") != NULL && g_getenv ("WAYLAND_DISPLAY") == NULL) spi_dec_setup_x11 (klass); - else #endif - g_type_class_add_private (object_class, sizeof (long)); /* dummy */ } static void @@ -1874,10 +1895,6 @@ spi_device_event_controller_init (SpiDEController *device_event_controller) SpiDEControllerClass *klass; klass = SPI_DEVICE_EVENT_CONTROLLER_GET_CLASS (device_event_controller); - /* TODO: shouldn't be gpointer below */ - device_event_controller->priv = G_TYPE_INSTANCE_GET_PRIVATE (device_event_controller, - SPI_DEVICE_EVENT_CONTROLLER_TYPE, - gpointer); device_event_controller->message_queue = g_queue_new (); saved_controller = device_event_controller; diff --git a/registryd/deviceeventcontroller.h b/registryd/deviceeventcontroller.h index 94c01cfa..46ea1697 100644 --- a/registryd/deviceeventcontroller.h +++ b/registryd/deviceeventcontroller.h @@ -52,7 +52,6 @@ struct _SpiDEController { GList *keygrabs_list; GQueue *message_queue; guint message_queue_idle; - gpointer priv; }; typedef enum { diff --git a/registryd/registry.c b/registryd/registry.c index 8d35f8ee..e22fbcbc 100644 --- a/registryd/registry.c +++ b/registryd/registry.c @@ -248,8 +248,8 @@ remove_events (SpiRegistry *registry, const char *bus_name, const char *event) g_strfreev (evdata->data); g_free (evdata->bus_name); g_slist_free_full (evdata->properties, g_free); - g_free (evdata); registry->events = g_list_remove (registry->events, evdata); + g_free (evdata); } else { @@ -1056,7 +1056,7 @@ emit_event (DBusConnection *bus, const char *path) { DBusMessage *sig; - DBusMessageIter iter, iter_variant; + DBusMessageIter iter, iter_variant, iter_array; sig = dbus_message_new_signal(SPI_DBUS_PATH_ROOT, klass, major); @@ -1071,9 +1071,9 @@ emit_event (DBusConnection *bus, append_reference (&iter_variant, name, path); dbus_message_iter_close_container (&iter, &iter_variant); - append_reference (&iter, - dbus_bus_get_unique_name (bus), - SPI_DBUS_PATH_ROOT); + dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "{sv}", + &iter_array); + dbus_message_iter_close_container (&iter, &iter_array); dbus_connection_send(bus, sig, NULL); dbus_message_unref(sig); diff --git a/test/memory.c b/test/memory.c index 2f53dd7f..54f06524 100644 --- a/test/memory.c +++ b/test/memory.c @@ -47,7 +47,7 @@ end (void *data) static gboolean kill_child (void *data) { - kill (child_pid, SIGTERM); + g_assert_no_errno (kill (child_pid, SIGTERM)); return FALSE; } @@ -56,6 +56,7 @@ on_event (AtspiEvent *event, void *data) { if (atspi_accessible_get_role (event->source, NULL) == ATSPI_ROLE_DESKTOP_FRAME) { + printf ("memory: event: %s\n", event->type); if (strstr (event->type, "add")) { AtspiAccessible *desktop = atspi_get_desktop (0); @@ -83,8 +84,16 @@ main() listener = atspi_event_listener_new (on_event, NULL, NULL); atspi_event_listener_register (listener, "object:children-changed", NULL); child_pid = fork (); - if (!child_pid) - execlp ("test/test-application", "test/test-application", NULL); + if (child_pid == 0) + { + g_assert_no_errno (execlp ("test/test-application", "test/test-application", NULL)); + } + else if (child_pid == -1) + { + const char *error = g_strerror (errno); + g_error ("could not fork test-application child: %s", error); + } + printf ("memory: child pid: %d\n", (int) child_pid); atspi_event_main (); return 0; } diff --git a/xml/Cache.xml b/xml/Cache.xml index ce06ba47..89546163 100644 --- a/xml/Cache.xml +++ b/xml/Cache.xml @@ -3,12 +3,12 @@ <interface name="org.a11y.atspi.Cache"> <method name="GetItems"> - <arg direction="out" name="nodes" type="a((so)(so)iiassusau)"/> + <arg direction="out" name="nodes" type="a((so)(so)(so)iiassusau)"/> <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiAccessibleCacheArray"/> </method> <signal name="AddAccessible"> - <arg direction="in" name="nodeAdded" type="((so)(so)iiassusau)"/> + <arg direction="in" name="nodeAdded" type="((so)(so)(so)iiassusau)"/> <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiAccessibleCacheItem"/> </signal> |