diff --git a/.clang-format b/.clang-format
new file mode 100644
index 000000000..16796b688
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,225 @@
+---
+Language:        Cpp
+# BasedOnStyle:  Google
+AccessModifierOffset: -1
+AlignAfterOpenBracket: Align
+AlignArrayOfStructures: None
+AlignConsecutiveMacros: None
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
+AlignEscapedNewlines: Left
+AlignOperands:   Align
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+  - __capability
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      false
+  AfterControlStatement: Never
+  AfterEnum:       false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  AfterExternBlock: false
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit:     80
+CommentPragmas:  '^ IWYU pragma:'
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: true
+DisableFormat:   false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: NextLine
+BasedOnStyle:    ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: true
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IfMacros:
+  - KJ_IF_MAYBE
+IncludeBlocks:   Regroup
+IncludeCategories:
+  - Regex:           '^<ext/.*\.h>'
+    Priority:        2
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '^<.*\.h>'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '^<.*'
+    Priority:        2
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '.*'
+    Priority:        3
+    SortPriority:    0
+    CaseSensitive:   false
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: true
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequires:  false
+IndentWidth:     2
+IndentWrappedFunctionNames: false
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Never
+ObjCBlockIndentWidth: 2
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Left
+PPIndentWidth:   -1
+RawStringFormats:
+  - Language:        Cpp
+    Delimiters:
+      - cc
+      - CC
+      - cpp
+      - Cpp
+      - CPP
+      - 'c++'
+      - 'C++'
+    CanonicalDelimiter: ''
+    BasedOnStyle:    google
+  - Language:        TextProto
+    Delimiters:
+      - pb
+      - PB
+      - proto
+      - PROTO
+    EnclosingFunctions:
+      - EqualsProto
+      - EquivToProto
+      - PARSE_PARTIAL_TEXT_PROTO
+      - PARSE_TEST_PROTO
+      - PARSE_TEXT_PROTO
+      - ParseTextOrDie
+      - ParseTextProtoOrDie
+      - ParseTestProto
+      - ParsePartialTestProto
+    CanonicalDelimiter: pb
+    BasedOnStyle:    google
+ReferenceAlignment: Pointer
+ReflowComments:  true
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes:    CaseSensitive
+SortJavaStaticImport: Before
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeParensOptions:
+  AfterControlStatements: true
+  AfterForeachMacros: true
+  AfterFunctionDefinitionName: false
+  AfterFunctionDeclarationName: false
+  AfterIfMacros:   true
+  AfterOverloadedOperator: false
+  BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles:  Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+  Minimum:         1
+  Maximum:         -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard:        Auto
+StatementAttributeLikeMacros:
+  - Q_EMIT
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        8
+UseCRLF:         false
+UseTab:          Never
+WhitespaceSensitiveMacros:
+  - STRINGIZE
+  - PP_STRINGIZE
+  - BOOST_PP_STRINGIZE
+  - NS_SWIFT_NAME
+  - CF_SWIFT_NAME
+...
diff --git a/.github/workflows/bindings.yml b/.github/workflows/bindings.yml
new file mode 100644
index 000000000..8a217615c
--- /dev/null
+++ b/.github/workflows/bindings.yml
@@ -0,0 +1,100 @@
+on:
+  release:
+    types:
+      - published
+name: Publish bindings
+jobs:
+  build-binaries:
+    if: startsWith(github.ref_name, 'accesskit_c-v')
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        include:
+          - os: macOS-latest
+            target: aarch64-apple-darwin
+            path: macos-arm64
+          - os: macOS-latest
+            target: x86_64-apple-darwin
+            path: macos-x86_64
+          - os: windows-2019
+            target: aarch64-pc-windows-msvc
+            path: win-arm64-msvc
+          - os: windows-2019
+            target: i686-pc-windows-msvc
+            path: win-x86-msvc
+          - os: windows-2019
+            target: x86_64-pc-windows-msvc
+            path: win-x86_64-msvc
+          - os: ubuntu-latest
+            target: i686-pc-windows-gnu
+            use-cross: true
+            path: win-x86-mingw
+          - os: ubuntu-latest
+            target: x86_64-pc-windows-gnu
+            use-cross: true
+            path: win-x86_64-mingw
+          - os: ubuntu-latest
+            target: i686-unknown-linux-gnu
+            use-cross: true
+            path: linux-x86
+          - os: ubuntu-latest
+            target: x86_64-unknown-linux-gnu
+            path: linux-x86_64
+    name: cargo build
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: install stable toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          target: ${{ matrix.target }}
+          profile: minimal
+          override: true
+
+      - name: cargo build
+        uses: actions-rs/cargo@v1
+        with:
+          command: build
+          use-cross: ${{ matrix.use-cross || false }}
+          args: --package accesskit_c --release --target ${{ matrix.target }}
+
+      - name: Upload binaries
+        uses: actions/upload-artifact@v3
+        with:
+          name: ${{ matrix.path }}
+          path: |
+            target/${{ matrix.target }}/release/*.a
+            target/${{ matrix.target }}/release/*.dll
+            target/${{ matrix.target }}/release/*.dylib
+            target/${{ matrix.target }}/release/*.lib
+            target/${{ matrix.target }}/release/*.pdb
+            target/${{ matrix.target }}/release/*.so
+
+  generate-headers:
+    if: startsWith(github.ref_name, 'accesskit_c-v')
+    uses: AccessKit/accesskit/.github/workflows/generate-headers.yml@main
+
+  publish:
+    needs: [build-binaries, generate-headers]
+    runs-on: ubuntu-latest
+    name: Publish
+    steps:
+      - uses: actions/checkout@v3
+
+      - uses: actions/download-artifact@v3
+        with:
+          path: accesskit_c/lib
+
+      - run: mv accesskit_c/lib/headers accesskit_c/include
+      - run: cp -r bindings/c/examples accesskit_c/
+      - run: cp bindings/c/*.md accesskit_c/
+      - run: cp LICENSE* accesskit_c/
+      - run: mv accesskit_c ${{ github.ref_name }}
+      - run: zip -r ${{ github.ref_name }}.zip ${{ github.ref_name }}
+
+      - uses: AButler/upload-release-assets@v2.0
+        with:
+          files: ${{ github.ref_name }}.zip
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+          release-tag: ${{ github.ref_name }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 23c123175..1c5a9c79d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,9 +7,9 @@ on:
 name: CI
 
 jobs:
-  rustfmt:
+  fmt:
     runs-on: ubuntu-latest
-    name: cargo fmt
+    name: Check formatting
     steps:
       - uses: actions/checkout@v2
 
@@ -27,6 +27,12 @@ jobs:
           command: fmt
           args: --all -- --check
 
+      - name: clang-format
+        uses: jidicula/clang-format-action@v4.11.0
+        with:
+          clang-format-version: 15
+          check-path: bindings/c
+
   test:
     runs-on: ${{ matrix.os }}
     strategy:
@@ -57,3 +63,6 @@ jobs:
         uses: actions-rs/cargo@v1
         with:
           command: test
+
+  generate-headers:
+    uses: AccessKit/accesskit/.github/workflows/generate-headers.yml@main
diff --git a/.github/workflows/generate-headers.yml b/.github/workflows/generate-headers.yml
new file mode 100644
index 000000000..63c3ba69f
--- /dev/null
+++ b/.github/workflows/generate-headers.yml
@@ -0,0 +1,27 @@
+on:
+  workflow_call:
+
+jobs:
+  generate-headers:
+    runs-on: ubuntu-latest
+    name: Generate headers
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: install nightly toolchain
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: nightly
+        profile: minimal
+        override: true
+
+    - uses: dtolnay/install@master
+      with:
+        crate: cbindgen
+    - run: make -C bindings/c include/accesskit.h
+
+    - name: Upload header file
+      uses: actions/upload-artifact@v3
+      with:
+        name: headers
+        path: bindings/c/include/accesskit.h
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
index 0389da4a5..f65f93747 100644
--- a/.github/workflows/release-please.yml
+++ b/.github/workflows/release-please.yml
@@ -10,3 +10,4 @@ jobs:
       - uses: GoogleCloudPlatform/release-please-action@v3
         with:
           command: manifest
+          token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 5bce0a278..8794fd73b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,4 +18,3 @@ target
 
 #KDE
 .directory
-
diff --git a/Cargo.lock b/Cargo.lock
index cba338b8f..47bf9e4d3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -27,6 +27,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "accesskit_c"
+version = "0.1.0"
+dependencies = [
+ "accesskit",
+ "accesskit_macos",
+ "accesskit_unix",
+ "accesskit_windows",
+ "paste",
+]
+
 [[package]]
 name = "accesskit_consumer"
 version = "0.14.1"
@@ -168,6 +179,18 @@ dependencies = [
  "slab",
 ]
 
+[[package]]
+name = "async-fs"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
+dependencies = [
+ "async-lock",
+ "autocfg",
+ "blocking",
+ "futures-lite",
+]
+
 [[package]]
 name = "async-io"
 version = "1.12.0"
@@ -190,12 +213,11 @@ dependencies = [
 
 [[package]]
 name = "async-lock"
-version = "2.6.0"
+version = "2.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
+checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
 dependencies = [
  "event-listener",
- "futures-lite",
 ]
 
 [[package]]
@@ -217,15 +239,21 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
 
 [[package]]
 name = "async-trait"
-version = "0.1.64"
+version = "0.1.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
+checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "atomic-waker"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599"
+
 [[package]]
 name = "atspi"
 version = "0.10.1"
@@ -267,9 +295,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "block-buffer"
-version = "0.10.3"
+version = "0.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
 dependencies = [
  "generic-array",
 ]
@@ -293,6 +321,20 @@ dependencies = [
  "objc2-encode",
 ]
 
+[[package]]
+name = "blocking"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8"
+dependencies = [
+ "async-channel",
+ "async-lock",
+ "async-task",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+]
+
 [[package]]
 name = "bumpalo"
 version = "3.12.0"
@@ -301,9 +343,9 @@ checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
 
 [[package]]
 name = "bytemuck"
-version = "1.13.0"
+version = "1.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
+checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
 
 [[package]]
 name = "byteorder"
@@ -415,9 +457,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.14"
+version = "0.8.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
 dependencies = [
  "cfg-if",
 ]
@@ -432,12 +474,6 @@ dependencies = [
  "typenum",
 ]
 
-[[package]]
-name = "cty"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
-
 [[package]]
 name = "derivative"
 version = "2.2.0"
@@ -502,9 +538,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
 
 [[package]]
 name = "dyn-clone"
-version = "1.0.10"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60"
+checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
 
 [[package]]
 name = "enumflags2"
@@ -529,15 +565,36 @@ dependencies = [
 
 [[package]]
 name = "enumn"
-version = "0.1.6"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e88bcb3a067a6555d577aba299e75eff9942da276e6506fc6274327daa026132"
+checksum = "1940ea32e14d489b401074558be4567f35ca9507c4628b4b3fd6fe6eb2ca7b88"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "errno"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
 [[package]]
 name = "event-listener"
 version = "2.5.3"
@@ -580,15 +637,15 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
 
 [[package]]
 name = "futures-core"
-version = "0.3.26"
+version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
+checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd"
 
 [[package]]
 name = "futures-io"
-version = "0.3.26"
+version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
+checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91"
 
 [[package]]
 name = "futures-lite"
@@ -607,25 +664,27 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.26"
+version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
+checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
 
 [[package]]
 name = "futures-task"
-version = "0.3.26"
+version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
+checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879"
 
 [[package]]
 name = "futures-util"
-version = "0.3.26"
+version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
+checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
 dependencies = [
  "futures-core",
+ "futures-io",
  "futures-sink",
  "futures-task",
+ "memchr",
  "pin-project-lite",
  "pin-utils",
  "slab",
@@ -658,6 +717,12 @@ version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
 [[package]]
 name = "hex"
 version = "0.4.3"
@@ -686,11 +751,22 @@ dependencies = [
  "web-sys",
 ]
 
+[[package]]
+name = "io-lifetimes"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76e86b86ae312accbf05ade23ce76b625e0e47a255712b7414037385a1c05380"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.45.0",
+]
+
 [[package]]
 name = "itoa"
-version = "1.0.5"
+version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
 
 [[package]]
 name = "jni-sys"
@@ -700,9 +776,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
 
 [[package]]
 name = "jobserver"
-version = "0.1.25"
+version = "0.1.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
+checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
 dependencies = [
  "libc",
 ]
@@ -724,9 +800,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.139"
+version = "0.2.140"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
 
 [[package]]
 name = "libloading"
@@ -738,6 +814,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "linux-raw-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+
 [[package]]
 name = "log"
 version = "0.4.17"
@@ -755,9 +837,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 
 [[package]]
 name = "memmap2"
-version = "0.5.9"
+version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2af2c65375e552a67fe3829ca63e8a7c27a378a62824594f43b2851d682b5ec2"
+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
 dependencies = [
  "libc",
 ]
@@ -771,6 +853,15 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "minimal-lexical"
 version = "0.2.1"
@@ -836,7 +927,7 @@ dependencies = [
  "bitflags",
  "cfg-if",
  "libc",
- "memoffset",
+ "memoffset 0.6.5",
 ]
 
 [[package]]
@@ -849,43 +940,47 @@ dependencies = [
  "bitflags",
  "cfg-if",
  "libc",
- "memoffset",
- "pin-utils",
+ "memoffset 0.6.5",
 ]
 
 [[package]]
-name = "nom"
-version = "7.1.3"
+name = "nix"
+version = "0.26.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
 dependencies = [
- "memchr",
- "minimal-lexical",
+ "bitflags",
+ "cfg-if",
+ "libc",
+ "memoffset 0.7.1",
+ "pin-utils",
+ "static_assertions",
 ]
 
 [[package]]
-name = "nom8"
-version = "0.2.0"
+name = "nom"
+version = "7.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
 dependencies = [
  "memchr",
+ "minimal-lexical",
 ]
 
 [[package]]
 name = "num_enum"
-version = "0.5.10"
+version = "0.5.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e0072973714303aa6e3631c7e8e777970cf4bdd25dc4932e41031027b8bcc4e"
+checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
 dependencies = [
  "num_enum_derive",
 ]
 
 [[package]]
 name = "num_enum_derive"
-version = "0.5.10"
+version = "0.5.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0629cbd6b897944899b1f10496d9c4a7ac5878d45fd61bc22e9e79bfbbc29597"
+checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -901,9 +996,9 @@ checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7"
 
 [[package]]
 name = "objc2"
-version = "0.3.0-beta.3"
+version = "0.3.0-beta.3.patch-leaks.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe31e5425d3d0b89a15982c024392815da40689aceb34bad364d58732bcfd649"
+checksum = "e7d9bb2ee6b71d02b1b3554ed600d267ee9a2796acc9fa43fb7748e13fe072dd"
 dependencies = [
  "block2",
  "objc-sys",
@@ -927,9 +1022,9 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
 
 [[package]]
 name = "orbclient"
-version = "0.3.42"
+version = "0.3.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba683f1641c11041c59d5d93689187abcab3c1349dc6d9d70c550c9f9360802f"
+checksum = "974465c5e83cf9df05c1e4137b271d29035c902e39e5ad4c1939837e22160af8"
 dependencies = [
  "cfg-if",
  "redox_syscall 0.2.16",
@@ -964,9 +1059,9 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
 
 [[package]]
 name = "paste"
-version = "1.0.11"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
+checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
 
 [[package]]
 name = "percent-encoding"
@@ -1006,16 +1101,18 @@ dependencies = [
 
 [[package]]
 name = "polling"
-version = "2.5.2"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6"
+checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa"
 dependencies = [
  "autocfg",
+ "bitflags",
  "cfg-if",
+ "concurrent-queue",
  "libc",
  "log",
- "wepoll-ffi",
- "windows-sys 0.42.0",
+ "pin-project-lite",
+ "windows-sys 0.45.0",
 ]
 
 [[package]]
@@ -1026,9 +1123,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
 [[package]]
 name = "proc-macro-crate"
-version = "1.3.0"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
 dependencies = [
  "once_cell",
  "toml_edit",
@@ -1036,18 +1133,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.51"
+version = "1.0.52"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
+checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.23"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
 dependencies = [
  "proc-macro2",
 ]
@@ -1084,12 +1181,9 @@ dependencies = [
 
 [[package]]
 name = "raw-window-handle"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a"
-dependencies = [
- "cty",
-]
+checksum = "4f851a03551ceefd30132e447f07f96cb7011d6b658374f3aed847333adb5559"
 
 [[package]]
 name = "redox_syscall"
@@ -1102,9 +1196,9 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.3.4"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb02a9aee8e8c7ad8d86890f1e16b49e0bbbffc9961ff3788c31d57c98bcbf03"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
 dependencies = [
  "bitflags",
 ]
@@ -1138,25 +1232,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
 
 [[package]]
-name = "remove_dir_all"
-version = "0.5.3"
+name = "rustix"
+version = "0.36.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
 dependencies = [
- "winapi",
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.45.0",
 ]
 
 [[package]]
 name = "ryu"
-version = "1.0.12"
+version = "1.0.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
 
 [[package]]
 name = "schemars"
-version = "0.8.11"
+version = "0.8.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307"
+checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
 dependencies = [
  "dyn-clone",
  "schemars_derive",
@@ -1166,9 +1265,9 @@ dependencies = [
 
 [[package]]
 name = "schemars_derive"
-version = "0.8.11"
+version = "0.8.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9"
+checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1203,18 +1302,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.152"
+version = "1.0.156"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.152"
+version = "1.0.156"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
+checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1234,9 +1333,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.93"
+version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
+checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
 dependencies = [
  "itoa",
  "ryu",
@@ -1245,9 +1344,9 @@ dependencies = [
 
 [[package]]
 name = "serde_repr"
-version = "0.1.10"
+version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e"
+checksum = "395627de918015623b32e7669714206363a7fc00382bf477e72c1f7533e8eafc"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1310,9 +1409,9 @@ dependencies = [
 
 [[package]]
 name = "socket2"
-version = "0.4.7"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
 dependencies = [
  "libc",
  "winapi",
@@ -1332,9 +1431,9 @@ checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1"
 
 [[package]]
 name = "syn"
-version = "1.0.107"
+version = "1.0.109"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1343,32 +1442,31 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.3.0"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
 dependencies = [
  "cfg-if",
  "fastrand",
- "libc",
  "redox_syscall 0.2.16",
- "remove_dir_all",
- "winapi",
+ "rustix",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.38"
+version = "1.0.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
+checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.38"
+version = "1.0.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
+checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1402,19 +1500,19 @@ dependencies = [
 
 [[package]]
 name = "toml_datetime"
-version = "0.5.1"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
+checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
 
 [[package]]
 name = "toml_edit"
-version = "0.18.1"
+version = "0.19.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
+checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274"
 dependencies = [
  "indexmap",
- "nom8",
  "toml_datetime",
+ "winnow",
 ]
 
 [[package]]
@@ -1473,9 +1571,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.6"
+version = "1.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
 
 [[package]]
 name = "vec_map"
@@ -1638,15 +1736,6 @@ dependencies = [
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "wepoll-ffi"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
-dependencies = [
- "cc",
-]
-
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -1728,9 +1817,9 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.42.1"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
 dependencies = [
  "windows_aarch64_gnullvm",
  "windows_aarch64_msvc",
@@ -1743,51 +1832,51 @@ dependencies = [
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.42.1"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.42.1"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.42.1"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.42.1"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.42.1"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.42.1"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.42.1"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
 
 [[package]]
 name = "winit"
-version = "0.28.1"
+version = "0.28.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c4755d4ba0e3d30fc7beef2095e246b1e6a6fad0717608bcb87a2df4b003bcf"
+checksum = "4f504e8c117b9015f618774f8d58cd4781f5a479bc41079c064f974cbb253874"
 dependencies = [
  "android-activity",
  "bitflags",
@@ -1805,7 +1894,7 @@ dependencies = [
  "orbclient",
  "percent-encoding",
  "raw-window-handle",
- "redox_syscall 0.3.4",
+ "redox_syscall 0.3.5",
  "sctk-adwaita",
  "smithay-client-toolkit",
  "wasm-bindgen",
@@ -1818,6 +1907,15 @@ dependencies = [
  "x11-dl",
 ]
 
+[[package]]
+name = "winnow"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "x11-dl"
 version = "2.21.0"
@@ -1846,12 +1944,13 @@ checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
 
 [[package]]
 name = "zbus"
-version = "3.10.0"
+version = "3.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f770930448dd412a4a7131dd968a8e6df0064db4d7916fbbd2d6c3f26b566938"
+checksum = "20aae5dd5b051971cd2f49f9f3b860e57b2b495ba5ba254eaec42d34ede57e97"
 dependencies = [
  "async-broadcast",
  "async-executor",
+ "async-fs",
  "async-io",
  "async-lock",
  "async-recursion",
@@ -1866,7 +1965,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "hex",
- "nix 0.25.1",
+ "nix 0.26.2",
  "once_cell",
  "ordered-stream",
  "rand",
@@ -1884,15 +1983,16 @@ dependencies = [
 
 [[package]]
 name = "zbus_macros"
-version = "3.10.0"
+version = "3.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4832059b438689017db7340580ebabba07f114eab91bf990c6e55052408b40d8"
+checksum = "9264b3a1bcf5503d4e0348b6e7efe1da58d4f92a913c15ed9e63b52de85faaa1"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
  "regex",
  "syn",
+ "zvariant_utils",
 ]
 
 [[package]]
@@ -1908,9 +2008,9 @@ dependencies = [
 
 [[package]]
 name = "zvariant"
-version = "3.11.0"
+version = "3.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "903169c05b9ab948ee93fefc9127d08930df4ce031d46c980784274439803e51"
+checksum = "46fe4914a985446d6fd287019b5fceccce38303d71407d9e6e711d44954a05d8"
 dependencies = [
  "byteorder",
  "enumflags2",
@@ -1922,12 +2022,24 @@ dependencies = [
 
 [[package]]
 name = "zvariant_derive"
-version = "3.11.0"
+version = "3.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cce76636e8fab7911be67211cf378c252b115ee7f2bae14b18b84821b39260b5"
+checksum = "34c20260af4b28b3275d6676c7e2a6be0d4332e8e0aba4616d34007fd84e462a"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
  "syn",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b22993dbc4d128a17a3b6c92f1c63872dd67198537ee728d8b5d7c40640a8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 35433b258..bdec0091f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,6 +6,7 @@ members = [
     "platforms/unix",
     "platforms/windows",
     "platforms/winit",
+    "bindings/c",
 ]
 default-members = [
     "common",
@@ -15,3 +16,4 @@ default-members = [
 
 [profile.release]
 lto = true
+panic = "abort"
diff --git a/bindings/c/.gitignore b/bindings/c/.gitignore
new file mode 100644
index 000000000..d4a94ec6c
--- /dev/null
+++ b/bindings/c/.gitignore
@@ -0,0 +1,2 @@
+
+include
\ No newline at end of file
diff --git a/bindings/c/Cargo.toml b/bindings/c/Cargo.toml
new file mode 100644
index 000000000..cd6643022
--- /dev/null
+++ b/bindings/c/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "accesskit_c"
+version = "0.1.0"
+authors = ["Arnold Loubriat <datatriny@gmail.com>"]
+license = "MIT/Apache-2.0"
+publish = false
+edition = "2021"
+
+[lib]
+name = "accesskit"
+crate-type = ["cdylib", "staticlib"]
+
+[features]
+cbindgen = []
+
+[dependencies]
+accesskit = { version = "0.10.1", path = "../../common" }
+paste = "1.0"
+
+[target.'cfg(target_os = "windows")'.dependencies]
+accesskit_windows = { version = "0.13.2", path = "../../platforms/windows" }
+
+[target.'cfg(target_os = "macos")'.dependencies]
+accesskit_macos = { version = "0.6.2", path = "../../platforms/macos" }
+
+[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
+accesskit_unix = { version = "0.3.2", path = "../../platforms/unix" }
diff --git a/bindings/c/Makefile b/bindings/c/Makefile
new file mode 100644
index 000000000..d7d601474
--- /dev/null
+++ b/bindings/c/Makefile
@@ -0,0 +1,36 @@
+SHELL = /usr/bin/env bash
+.SHELLFLAGS = -o pipefail -c
+
+CFLAGS := -Werror -Wall -Wextra -Wpedantic -g -I src/
+
+PROFILE := release
+DESTDIR=/usr/local
+
+ifeq ($(CC), clang)
+	CFLAGS += -fsanitize=address -fsanitize=undefined
+	LDFLAGS += -fsanitize=address
+endif
+
+ifeq ($(PROFILE), release)
+	CFLAGS += -O3
+	CARGOFLAGS += --release
+endif
+
+include:
+	mkdir -p $@
+
+include/accesskit.h: src/*.rs cbindgen.toml include
+	rustup run nightly cbindgen | clang-format '--assume-filename=*.c' > $@
+
+../../target/$(PROFILE)/libaccesskit.a: src/*.rs Cargo.toml
+	cargo build $(CARGOFLAGS)
+
+install: ../../target/$(PROFILE)/libaccesskit.a
+	mkdir -p $(DESTDIR)/lib
+	install target/$(PROFILE)/libaccesskit.a $(DESTDIR)/lib/libaccesskit.a
+	mkdir -p $(DESTDIR)/include
+	install include/accesskit.h $(DESTDIR)/include/
+
+clean:
+	rm -rf include
+	rm -rf ../../target
diff --git a/bindings/c/README.md b/bindings/c/README.md
new file mode 100644
index 000000000..3f5a9658a
--- /dev/null
+++ b/bindings/c/README.md
@@ -0,0 +1,18 @@
+# AccessKit C bindings
+
+These are the bindings to use AccessKit from other languages through FFI such as in C.
+
+## Prerequisites
+
+- [Rust](https://rustup.rs/)
+- A nightly Rust toolchain: `rustup install nightly`
+- [cbindgen](https://github.com/eqrion/cbindgen): `cargo install cbindgen`
+- [clang-format](https://releases.llvm.org/14.0.0/tools/clang/docs/ClangFormat.html), version 14 or higher
+
+## Building
+
+Inside the `bindings/c` directory, some of the available make recipes include:
+
+- `make include/accesskit.h`: generate the header files
+- `make ../../target/release/libaccesskit.a`: build static and dynamic libraries on Linux
+- `make clean`: remove generated artifacts
diff --git a/bindings/c/cbindgen.toml b/bindings/c/cbindgen.toml
new file mode 100644
index 000000000..5cd6ab00c
--- /dev/null
+++ b/bindings/c/cbindgen.toml
@@ -0,0 +1,92 @@
+language = "C"
+header = """/**
+ * Copyright 2023 The AccessKit Authors. All rights reserved.
+ * Licensed under the Apache License, Version 2.0 (found in
+ * the LICENSE-APACHE file) or the MIT license (found in
+ * the LICENSE-MIT file), at your option.
+ */"""
+
+include_guard = "ACCESSKIT_H"
+cpp_compat = true
+after_includes = """#ifdef _WIN32
+#include <windows.h>
+#endif
+#ifdef __APPLE__
+#include <Foundation/NSObjCRuntime.h>
+#include <CoreGraphics/CGGeometry.h>
+#endif"""
+
+usize_is_size_t = true
+
+[export]
+include = ["Size", "tree_update", "Vec2"]
+prefix = "accesskit_"
+renaming_overrides_prefixing = true
+
+[defines]
+"target_os = linux" = "__linux__"
+"target_os = dragonfly" = "__DragonFly__"
+"target_os = freebsd" = "__FreeBSD__"
+"target_os = netbsd" = "__NetBSD__"
+"target_os = openbsd" = "__OpenBSD__"
+"target_os = macos" = "__APPLE__"
+"target_os = windows" = "_WIN32"
+
+[export.rename]
+"Action" = "accesskit_action"
+"ActionHandlerCallback" = "accesskit_action_handler_callback"
+"Affine" = "accesskit_affine"
+"AriaCurrent" = "accesskit_aria_current"
+"CheckedState" = "accesskit_checked_state"
+"DefaultActionVerb" = "accesskit_default_action_verb"
+"DescriptionFrom" = "accesskit_description_from"
+"HWND" = "HWND"
+"HasPopup" = "accesskit_has_popup"
+"Invalid" = "accesskit_invalid"
+"LPARAM" = "LPARAM"
+"LRESULT" = "LRESULT"
+"ListStyle" = "accesskit_list_style"
+"Live" = "accesskit_live"
+"NSArray" = "NSArray"
+"NSObject" = "NSObject"
+"NSPoint" = "CGPoint"
+"NameFrom" = "accesskit_name_from"
+"Orientation" = "accesskit_orientation"
+"Point" = "accesskit_point"
+"Rect" = "accesskit_rect"
+"Role" = "accesskit_role"
+"Size" = "accesskit_size"
+"SortDirection" = "accesskit_sort_direction"
+"TextAlign" = "accesskit_text_align"
+"TextDecoration" = "accesskit_text_decoration"
+"TextDirection" = "accesskit_text_direction"
+"Vec2" = "accesskit_vec2"
+"VerticalOffset" = "accesskit_vertical_offset"
+"WPARAM" = "WPARAM"
+"opt_AriaCurrent" = "accesskit_opt_aria_current"
+"opt_CheckedState" = "accesskit_opt_checked_state"
+"opt_DefaultActionVerb" = "accesskit_opt_default_action_verb"
+"opt_DescriptionFrom" = "accesskit_opt_description_from"
+"opt_HasPopup" = "accesskit_opt_has_popup"
+"opt_Invalid" = "accesskit_opt_invalid"
+"opt_ListStyle" = "accesskit_opt_list_style"
+"opt_Live" = "accesskit_opt_live"
+"opt_NameFrom" = "accesskit_opt_name_from"
+"opt_Orientation" = "accesskit_opt_orientation"
+"opt_SortDirection" = "accesskit_opt_sort_direction"
+"opt_TextAlign" = "accesskit_opt_text_align"
+"opt_TextDecoration" = "accesskit_opt_text_decoration"
+"opt_TextDirection" = "accesskit_opt_text_direction"
+"opt_VerticalOffset" = "accesskit_opt_vertical_offset"
+
+[enum]
+rename_variants = "ScreamingSnakeCase"
+prefix_with_name = true
+
+[parse]
+parse_deps = true
+include = ["accesskit"]
+
+[parse.expand]
+crates = ["accesskit_c"]
+features = ["cbindgen"]
diff --git a/bindings/c/examples/windows/.gitignore b/bindings/c/examples/windows/.gitignore
new file mode 100644
index 000000000..9c570db25
--- /dev/null
+++ b/bindings/c/examples/windows/.gitignore
@@ -0,0 +1,2 @@
+*.exe
+*.obj
\ No newline at end of file
diff --git a/bindings/c/examples/windows/build.bat b/bindings/c/examples/windows/build.bat
new file mode 100644
index 000000000..0d2e4dfa2
--- /dev/null
+++ b/bindings/c/examples/windows/build.bat
@@ -0,0 +1,4 @@
+@echo off
+cl /nologo /W3 /DUNICODE /D_UNICODE /I..\..\include /c hello_world.c
+Rem This script assumes you are building this example from the downloaded package on a 64bit machine.
+link /NOLOGO /SUBSYSTEM:CONSOLE hello_world.obj ..\..\lib\win-x86_64-msvc\accesskit.lib advapi32.lib bcrypt.lib kernel32.lib ole32.lib oleaut32.lib shell32.lib uiautomationcore.lib user32.lib userenv.lib winspool.lib ws2_32.lib
diff --git a/bindings/c/examples/windows/hello_world.c b/bindings/c/examples/windows/hello_world.c
new file mode 100644
index 000000000..25e4e1b42
--- /dev/null
+++ b/bindings/c/examples/windows/hello_world.c
@@ -0,0 +1,346 @@
+#include <stdio.h>
+#include <windows.h>
+
+#include "accesskit.h"
+
+const WCHAR CLASS_NAME[] = L"AccessKitTest";
+
+const WCHAR WINDOW_TITLE[] = L"Hello world";
+
+static accesskit_node_id WINDOW_ID;
+static accesskit_node_id BUTTON_1_ID;
+static accesskit_node_id BUTTON_2_ID;
+static accesskit_node_id ANNOUNCEMENT_ID;
+static accesskit_node_id INITIAL_FOCUS;
+
+const accesskit_rect BUTTON_1_RECT = {20.0, 20.0, 100.0, 60.0};
+
+const accesskit_rect BUTTON_2_RECT = {20.0, 60.0, 100.0, 100.0};
+
+const uint32_t SET_FOCUS_MSG = WM_USER;
+const uint32_t DO_DEFAULT_ACTION_MSG = WM_USER + 1;
+
+const bool node_id_cmp(const accesskit_node_id *id1,
+                       const accesskit_node_id *id2) {
+  return memcmp(id1, id2, sizeof(accesskit_node_id)) == 0;
+}
+
+accesskit_node_id *node_id_dup(const accesskit_node_id *src) {
+  accesskit_node_id *result = malloc(sizeof(accesskit_node_id));
+  memcpy(result, src, sizeof(accesskit_node_id));
+  return result;
+}
+
+accesskit_node *build_button(accesskit_node_id id, const char *name,
+                             accesskit_node_class_set *classes) {
+  accesskit_rect rect;
+  if (node_id_cmp(&id, &BUTTON_1_ID)) {
+    rect = BUTTON_1_RECT;
+  } else {
+    rect = BUTTON_2_RECT;
+  }
+
+  accesskit_node_builder *builder =
+      accesskit_node_builder_new(ACCESSKIT_ROLE_BUTTON);
+  accesskit_node_builder_set_bounds(builder, rect);
+  accesskit_node_builder_set_name(builder, name);
+  accesskit_node_builder_add_action(builder, ACCESSKIT_ACTION_FOCUS);
+  accesskit_node_builder_set_default_action_verb(
+      builder, ACCESSKIT_DEFAULT_ACTION_VERB_CLICK);
+  return accesskit_node_builder_build(builder, classes);
+}
+
+accesskit_node *build_announcement(const char *text,
+                                   accesskit_node_class_set *classes) {
+  accesskit_node_builder *builder =
+      accesskit_node_builder_new(ACCESSKIT_ROLE_STATIC_TEXT);
+  accesskit_node_builder_set_name(builder, text);
+  accesskit_node_builder_set_live(builder, ACCESSKIT_LIVE_POLITE);
+  return accesskit_node_builder_build(builder, classes);
+}
+
+struct window_state {
+  accesskit_windows_uia_init_marker *uia_init_marker;
+  accesskit_windows_adapter *adapter;
+  accesskit_node_id focus;
+  bool is_window_focused;
+  const char *announcement;
+  accesskit_node_class_set *node_classes;
+};
+
+void window_state_free(struct window_state *state) {
+  if (state->uia_init_marker != NULL) {
+    accesskit_windows_uia_init_marker_free(state->uia_init_marker);
+  }
+  if (state->adapter != NULL) {
+    accesskit_windows_adapter_free(state->adapter);
+  }
+  accesskit_node_class_set_free(state->node_classes);
+  free(state);
+}
+
+accesskit_opt_node_id window_state_focus(struct window_state *state) {
+  accesskit_opt_node_id result;
+  result.has_value = state->is_window_focused;
+  if (result.has_value) {
+    result.value = state->focus;
+  }
+  return result;
+}
+
+accesskit_node *window_state_build_root(struct window_state *state) {
+  accesskit_node_builder *builder =
+      accesskit_node_builder_new(ACCESSKIT_ROLE_WINDOW);
+  accesskit_node_builder_push_child(builder, BUTTON_1_ID);
+  accesskit_node_builder_push_child(builder, BUTTON_2_ID);
+  if (state->announcement != NULL) {
+    accesskit_node_builder_push_child(builder, ANNOUNCEMENT_ID);
+  }
+  return accesskit_node_builder_build(builder, state->node_classes);
+}
+
+accesskit_tree_update *window_state_build_initial_tree(
+    struct window_state *state) {
+  accesskit_node *root = window_state_build_root(state);
+  accesskit_node *button_1 =
+      build_button(BUTTON_1_ID, "Button 1", state->node_classes);
+  accesskit_node *button_2 =
+      build_button(BUTTON_2_ID, "Button 2", state->node_classes);
+  accesskit_tree_update *result =
+      accesskit_tree_update_new((state->announcement != NULL) ? 4 : 3);
+  result->tree.has_value = true;
+  result->tree.value = accesskit_tree_new(WINDOW_ID);
+  result->focus = window_state_focus(state);
+  result->ids[0] = WINDOW_ID;
+  result->nodes[0] = root;
+  result->ids[1] = BUTTON_1_ID;
+  result->nodes[1] = button_1;
+  result->ids[2] = BUTTON_2_ID;
+  result->nodes[2] = button_2;
+  if (state->announcement != NULL) {
+    result->ids[3] = ANNOUNCEMENT_ID;
+    result->nodes[3] =
+        build_announcement(state->announcement, state->node_classes);
+  }
+  return result;
+}
+
+void do_action(const accesskit_action_request *request, void *userdata) {
+  HWND window = userdata;
+  if (request->action == ACCESSKIT_ACTION_FOCUS) {
+    LPARAM lparam = (LPARAM)node_id_dup(&request->target);
+    PostMessage((HWND)window, SET_FOCUS_MSG, 0, lparam);
+  } else if (request->action == ACCESSKIT_ACTION_DEFAULT) {
+    LPARAM lparam = (LPARAM)node_id_dup(&request->target);
+    PostMessage((HWND)window, DO_DEFAULT_ACTION_MSG, 0, lparam);
+  }
+}
+
+accesskit_windows_adapter *window_state_get_or_init_accesskit_adapter(
+    struct window_state *state, HWND window) {
+  if (state->adapter != NULL) {
+    return state->adapter;
+  } else {
+    accesskit_tree_update *initial_tree =
+        window_state_build_initial_tree(state);
+    accesskit_action_handler *action_handler =
+        accesskit_action_handler_new(do_action, (void *)window);
+    state->adapter = accesskit_windows_adapter_new(
+        window, initial_tree, action_handler, state->uia_init_marker);
+    state->uia_init_marker = NULL;
+    return state->adapter;
+  }
+}
+
+void window_state_press_button(struct window_state *state,
+                               accesskit_node_id id) {
+  const char *text;
+  if (node_id_cmp(&id, &BUTTON_1_ID)) {
+    text = "You pressed button 1";
+  } else {
+    text = "You pressed button 2";
+  }
+  state->announcement = text;
+  if (state->adapter != NULL) {
+    accesskit_node *announcement =
+        build_announcement(text, state->node_classes);
+    accesskit_node *root = window_state_build_root(state);
+    accesskit_tree_update *update = accesskit_tree_update_new(2);
+    update->ids[0] = ANNOUNCEMENT_ID;
+    update->nodes[0] = announcement;
+    update->ids[1] = WINDOW_ID;
+    update->nodes[1] = root;
+    update->focus = window_state_focus(state);
+    accesskit_windows_queued_events *events =
+        accesskit_windows_adapter_update(state->adapter, update);
+    accesskit_windows_queued_events_raise(events);
+  }
+}
+
+struct window_state *get_window_state(HWND window) {
+  return (struct window_state *)(GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void update_focus(HWND window, bool is_window_focused) {
+  struct window_state *state = get_window_state(window);
+  state->is_window_focused = is_window_focused;
+  accesskit_opt_node_id focus = window_state_focus(state);
+  if (state->adapter != NULL) {
+    accesskit_tree_update *update = accesskit_tree_update_new(0);
+    update->focus = focus;
+    accesskit_windows_queued_events *events =
+        accesskit_windows_adapter_update(state->adapter, update);
+    accesskit_windows_queued_events_raise(events);
+  }
+}
+
+struct window_create_params {
+  accesskit_node_id initial_focus;
+};
+
+LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
+  if (msg == WM_NCCREATE) {
+    CREATESTRUCT *create_struct = (CREATESTRUCT *)lParam;
+    struct window_create_params *create_params =
+        (struct window_create_params *)create_struct->lpCreateParams;
+    struct window_state *state = malloc(sizeof(struct window_state));
+    state->uia_init_marker = accesskit_windows_uia_init_marker_new();
+    state->adapter = NULL;
+    state->focus = create_params->initial_focus;
+    state->is_window_focused = false;
+    state->announcement = NULL;
+    state->node_classes = accesskit_node_class_set_new();
+    SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)state);
+    return DefWindowProc(hwnd, msg, wParam, lParam);
+  } else if (msg == WM_PAINT) {
+    ValidateRect(hwnd, NULL);
+  } else if (msg == WM_DESTROY) {
+    LONG_PTR ptr = SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
+    if (ptr != 0) {
+      struct window_state *state = (struct window_state *)ptr;
+      window_state_free(state);
+    }
+    PostQuitMessage(0);
+  } else if (msg == WM_GETOBJECT) {
+    struct window_state *window_state = get_window_state(hwnd);
+    if (window_state == NULL) {
+      // We need to be prepared to gracefully handle WM_GETOBJECT
+      // while the window is being destroyed; this can happen if
+      // the thread is using a COM STA.
+      return DefWindowProc(hwnd, msg, wParam, lParam);
+    }
+    accesskit_windows_adapter *adapter =
+        window_state_get_or_init_accesskit_adapter(window_state, hwnd);
+    accesskit_opt_lresult result =
+        accesskit_windows_adapter_handle_wm_getobject(adapter, wParam, lParam);
+    if (result.has_value) {
+      return result.value;
+    } else {
+      return DefWindowProc(hwnd, msg, wParam, lParam);
+    }
+  } else if (msg == WM_SETFOCUS || msg == WM_EXITMENULOOP ||
+             msg == WM_EXITSIZEMOVE) {
+    update_focus(hwnd, true);
+  } else if (msg == WM_KILLFOCUS || msg == WM_ENTERMENULOOP ||
+             msg == WM_ENTERSIZEMOVE) {
+    update_focus(hwnd, false);
+  } else if (msg == WM_KEYDOWN) {
+    if (wParam == VK_TAB) {
+      struct window_state *state = get_window_state(hwnd);
+      if (node_id_cmp(&state->focus, &BUTTON_1_ID)) {
+        state->focus = BUTTON_2_ID;
+      } else {
+        state->focus = BUTTON_1_ID;
+      }
+      update_focus(hwnd, true);
+    } else if (wParam == VK_SPACE) {
+      struct window_state *window_state = get_window_state(hwnd);
+      accesskit_node_id id = window_state->focus;
+      window_state_press_button(window_state, id);
+    } else {
+      return DefWindowProc(hwnd, msg, wParam, lParam);
+    }
+  } else if (msg == SET_FOCUS_MSG) {
+    accesskit_node_id *id = (accesskit_node_id *)lParam;
+    if (node_id_cmp(id, &BUTTON_1_ID) || node_id_cmp(id, &BUTTON_2_ID)) {
+      struct window_state *state = get_window_state(hwnd);
+      state->focus = *id;
+      bool is_window_focused = state->is_window_focused;
+      update_focus(hwnd, is_window_focused);
+    }
+    free(id);
+  } else if (msg == DO_DEFAULT_ACTION_MSG) {
+    accesskit_node_id *id = (accesskit_node_id *)lParam;
+    if (node_id_cmp(id, &BUTTON_1_ID) || node_id_cmp(id, &BUTTON_2_ID)) {
+      struct window_state *window_state = get_window_state(hwnd);
+      window_state_press_button(window_state, *id);
+    }
+    free(id);
+  } else {
+    return DefWindowProc(hwnd, msg, wParam, lParam);
+  }
+  return 0;
+}
+
+HWND create_window(const WCHAR *title, accesskit_node_id initial_focus) {
+  struct window_create_params create_params;
+  create_params.initial_focus = initial_focus;
+
+  return CreateWindowEx(WS_EX_CLIENTEDGE, CLASS_NAME, title,
+                        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
+                        CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
+                        GetModuleHandle(NULL), &create_params);
+}
+
+int main() {
+  printf("This example has no visible GUI, and a keyboard interface:\n");
+  printf("- [Tab] switches focus between two logical buttons.\n");
+  printf(
+      "- [Space] 'presses' the button, adding static text in a live region "
+      "announcing that it was pressed.\n");
+  printf(
+      "Enable Narrator with [Win]+[Ctrl]+[Enter] (or [Win]+[Enter] on older "
+      "versions of Windows).\n");
+
+  WNDCLASSEX wc;
+  HWND hwnd;
+  MSG Msg;
+
+  wc.cbSize = sizeof(WNDCLASSEX);
+  wc.style = CS_HREDRAW | CS_VREDRAW;
+  wc.lpfnWndProc = WndProc;
+  wc.cbClsExtra = 0;
+  wc.cbWndExtra = 0;
+  wc.hInstance = GetModuleHandle(NULL);
+  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
+  wc.lpszMenuName = NULL;
+  wc.lpszClassName = CLASS_NAME;
+  wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
+
+  if (!RegisterClassEx(&wc)) {
+    return 0;
+  }
+
+  WINDOW_ID = accesskit_node_id_new(1).value;
+  BUTTON_1_ID = accesskit_node_id_new(2).value;
+  BUTTON_2_ID = accesskit_node_id_new(3).value;
+  ANNOUNCEMENT_ID = accesskit_node_id_new(4).value;
+  INITIAL_FOCUS = BUTTON_1_ID;
+
+  hwnd = create_window(WINDOW_TITLE, INITIAL_FOCUS);
+
+  if (hwnd == NULL) {
+    return 0;
+  }
+
+  ShowWindow(hwnd, SW_SHOW);
+  UpdateWindow(hwnd);
+
+  while (GetMessage(&Msg, NULL, 0, 0) > 0) {
+    TranslateMessage(&Msg);
+    DispatchMessage(&Msg);
+  }
+  return Msg.wParam;
+}
diff --git a/bindings/c/src/common.rs b/bindings/c/src/common.rs
new file mode 100644
index 000000000..af0ab25fe
--- /dev/null
+++ b/bindings/c/src/common.rs
@@ -0,0 +1,1090 @@
+// Copyright 2023 The AccessKit Authors. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (found in
+// the LICENSE-APACHE file) or the MIT license (found in
+// the LICENSE-MIT file), at your option.
+
+use crate::{box_from_ptr, mut_from_ptr, opt_struct, ref_from_ptr, BoxCastPtr, CastPtr};
+use accesskit::*;
+use paste::paste;
+use std::{
+    ffi::{CStr, CString},
+    mem,
+    num::NonZeroU128,
+    os::raw::{c_char, c_void},
+    ptr, slice,
+};
+
+pub struct node_class_set {
+    _private: [u8; 0],
+}
+
+impl CastPtr for node_class_set {
+    type RustType = NodeClassSet;
+}
+
+impl BoxCastPtr for node_class_set {}
+
+impl node_class_set {
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_class_set_new() -> *mut node_class_set {
+        let set = NodeClassSet::new();
+        BoxCastPtr::to_mut_ptr(set)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_class_set_free(set: *mut node_class_set) {
+        drop(box_from_ptr(set));
+    }
+}
+
+pub struct node {
+    _private: [u8; 0],
+}
+
+impl CastPtr for node {
+    type RustType = Node;
+}
+
+impl BoxCastPtr for node {}
+
+impl node {
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_free(node: *mut node) {
+        drop(box_from_ptr(node));
+    }
+}
+
+pub struct node_builder {
+    _private: [u8; 0],
+}
+
+impl CastPtr for node_builder {
+    type RustType = NodeBuilder;
+}
+
+impl BoxCastPtr for node_builder {}
+
+macro_rules! clearer {
+    ($clearer:ident) => {
+        paste! {
+            impl node_builder {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $clearer>](builder: *mut node_builder) {
+                    let builder = mut_from_ptr(builder);
+                    builder.$clearer()
+                }
+            }
+        }
+    };
+}
+
+macro_rules! flag_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        paste! {
+            impl node {
+                $(#[no_mangle]
+                pub extern "C" fn [<accesskit_node_ $getter>](node: *const node) -> bool {
+                    let node = ref_from_ptr(node);
+                    node.$getter()
+                })*
+            }
+            $(impl node_builder {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $getter>](builder: *const node_builder) -> bool {
+                    let builder = ref_from_ptr(builder);
+                    builder.$getter()
+                }
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $setter>](builder: *mut node_builder) {
+                    let builder = mut_from_ptr(builder);
+                    builder.$setter()
+                }
+            }
+            clearer! { $clearer })*
+        }
+    }
+}
+
+macro_rules! array_setter {
+    ($setter:ident, $ffi_type:ty, $rust_type:ty) => {
+        paste! {
+            impl node_builder {
+                /// Caller is responsible for freeing `values`.
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $setter>](builder: *mut node_builder, length: usize, values: *const $ffi_type) {
+                    let builder = mut_from_ptr(builder);
+                    let values = unsafe {
+                        slice::from_raw_parts(values, length)
+                            .iter()
+                            .cloned()
+                            .map(From::from)
+                            .collect::<Vec<$rust_type>>()
+                    };
+                    builder.$setter(values);
+                }
+            }
+        }
+    }
+}
+
+macro_rules! property_getters {
+    ($getter:ident, *const $getter_result:tt) => {
+        paste! {
+            impl node {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_ $getter>](node: *const node) -> *const $getter_result {
+                    let node = ref_from_ptr(node);
+                    match node.$getter() {
+                        Some(value) => value as *const _,
+                        None => ptr::null(),
+                    }
+                }
+            }
+            impl node_builder {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $getter>](builder: *const node_builder) -> *const $getter_result {
+                    let builder = ref_from_ptr(builder);
+                    match builder.$getter() {
+                        Some(value) => value as *const _,
+                        None => ptr::null(),
+                    }
+                }
+            }
+        }
+    };
+    ($getter:ident, *mut $getter_result:tt) => {
+        paste! {
+            impl node {
+                /// Caller is responsible for freeing the returned value.
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_ $getter>](node: *const node) -> *mut $getter_result {
+                    let node = ref_from_ptr(node);
+                    BoxCastPtr::to_mut_ptr(node.$getter().into())
+                }
+            }
+            impl node_builder {
+                /// Caller is responsible for freeing the returned value.
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $getter>](builder: *const node_builder) -> *const $getter_result {
+                    let builder = ref_from_ptr(builder);
+                    BoxCastPtr::to_mut_ptr(builder.$getter().into())
+                }
+            }
+        }
+    };
+    ($getter:ident, $getter_result:tt) => {
+        paste! {
+            impl node {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_ $getter>](node: *const node) -> $getter_result {
+                    let node = ref_from_ptr(node);
+                    node.$getter().into()
+                }
+            }
+            impl node_builder {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $getter>](builder: *const node_builder) -> $getter_result {
+                    let builder = ref_from_ptr(builder);
+                    builder.$getter().into()
+                }
+            }
+        }
+    }
+}
+
+macro_rules! simple_property_methods {
+    ($getter:ident, $getter_result:tt, $setter:ident, $setter_param:tt, $clearer:ident) => {
+        paste! {
+            property_getters! { $getter, $getter_result }
+            impl node_builder {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $setter>](builder: *mut node_builder, value: $setter_param) {
+                    let builder = mut_from_ptr(builder);
+                    builder.$setter(value.into());
+                }
+            }
+            clearer! { $clearer }
+        }
+    };
+    ($getter:ident, *const $getter_result:tt, $setter:ident, $setter_param:tt, $clearer:ident) => {
+        paste! {
+            property_getters! { $getter, *const $getter_result }
+            impl node_builder {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $setter>](builder: *mut node_builder, value: $setter_param) {
+                    let builder = mut_from_ptr(builder);
+                    builder.$setter(Box::new(value));
+                }
+            }
+            clearer! { $clearer }
+        }
+    };
+    ($getter:ident, $getter_result:tt, $setter:ident, *const $setter_param:tt, $clearer:ident) => {
+        property_getters! { $getter, $getter_result }
+        array_setter! { $setter, $setter_param, $setter_param }
+        clearer! { $clearer }
+    }
+}
+
+macro_rules! slice_struct {
+    ($struct_name:ident, $rust_type:ty, $ffi_type:ty) => {
+        #[repr(C)]
+        pub struct $struct_name {
+            pub length: usize,
+            pub values: *const $ffi_type,
+        }
+        impl From<&[$rust_type]> for $struct_name {
+            fn from(values: &[$rust_type]) -> Self {
+                Self {
+                    length: values.len(),
+                    values: values.as_ptr() as *const _,
+                }
+            }
+        }
+        impl From<$struct_name> for Vec<$rust_type> {
+            fn from(values: $struct_name) -> Self {
+                unsafe {
+                    slice::from_raw_parts(values.values as *mut $rust_type, values.length).to_vec()
+                }
+            }
+        }
+    };
+}
+
+macro_rules! array_struct {
+    ($struct_name:ident, $rust_type:ty, $ffi_type:ty) => {
+        #[repr(C)]
+        pub struct $struct_name {
+            pub length: usize,
+            pub values: *mut $ffi_type,
+        }
+        impl CastPtr for $struct_name {
+            type RustType = $struct_name;
+        }
+        impl BoxCastPtr for $struct_name {}
+        paste! {
+            impl $struct_name {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_ $struct_name _free>](value: *mut $struct_name) {
+                    let array = box_from_ptr(value);
+                    unsafe { Vec::from_raw_parts(array.values, array.length, array.length) };
+                    drop(array);
+                }
+            }
+        }
+        impl From<&[$rust_type]> for $struct_name {
+            fn from(values: &[$rust_type]) -> Self {
+                let length = values.len();
+                let mut ffi_values = values.iter().map(From::from).collect::<Vec<$ffi_type>>();
+                let array = Self {
+                    length,
+                    values: ffi_values.as_mut_ptr(),
+                };
+                mem::forget(ffi_values);
+                array
+            }
+        }
+    };
+}
+
+macro_rules! vec_property_methods {
+    ($(($item_type:ty, $getter:ident, *mut $getter_result:ty, $setter:ident, $setter_param:ty, $pusher:ident, $clearer:ident)),+) => {
+        paste! {
+            $(property_getters! { $getter, *mut $getter_result }
+            array_setter! { $setter, $setter_param, $item_type }
+            impl node_builder {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $pusher>](builder: *mut node_builder, item: $setter_param) {
+                    let builder = mut_from_ptr(builder);
+                    builder.$pusher(item.into());
+                }
+            }
+            clearer! { $clearer })*
+        }
+    };
+    ($(($item_type:ty, $getter:ident, $getter_result:ty, $setter:ident, $setter_param:ty, $pusher:ident, $clearer:ident)),+) => {
+        paste! {
+            $(property_getters! { $getter, $getter_result }
+            array_setter! { $setter, $setter_param, $item_type }
+            impl node_builder {
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $pusher>](builder: *mut node_builder, item: $setter_param) {
+                    let builder = mut_from_ptr(builder);
+                    builder.$pusher(item.into());
+                }
+            }
+            clearer! { $clearer })*
+        }
+    }
+}
+
+/// Call `accesskit_node_id_new` to create this struct.
+///
+/// If you have to manually populate this, ensure it is not filled up only with zeroes.
+#[derive(Clone, Copy)]
+#[repr(C)]
+pub struct node_id([u8; 16]);
+
+impl From<NodeId> for node_id {
+    fn from(id: NodeId) -> Self {
+        Self(id.0.get().to_le_bytes())
+    }
+}
+
+impl From<node_id> for NodeId {
+    fn from(id: node_id) -> Self {
+        Self(unsafe { NonZeroU128::new_unchecked(u128::from_le_bytes(id.0)) })
+    }
+}
+
+slice_struct! { node_ids, NodeId, node_id }
+
+macro_rules! node_id_vec_property_methods {
+    ($(($getter:ident, $setter:ident, $pusher:ident, $clearer:ident)),+) => {
+        $(vec_property_methods! {
+            (NodeId, $getter, node_ids, $setter, node_id, $pusher, $clearer)
+        })*
+    }
+}
+
+macro_rules! node_id_property_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        opt_struct! { opt_node_id, node_id }
+        $(simple_property_methods! {
+            $getter, opt_node_id, $setter, node_id, $clearer
+        })*
+    }
+}
+
+macro_rules! string_property_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        paste! {
+            impl node {
+                /// Caller must call `accesskit_string_free` with the return value.
+                $(#[no_mangle]
+                pub extern "C" fn [<accesskit_node_ $getter>](node: *const node) -> *mut c_char {
+                    let node = ref_from_ptr(node);
+                    match node.$getter() {
+                        Some(value) => CString::new(value).unwrap().into_raw(),
+                        None => ptr::null_mut()
+                    }
+                })*
+            }
+            $(impl node_builder {
+                /// Caller must call `accesskit_string_free` with the return value.
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $getter>](builder: *const node_builder) -> *mut c_char {
+                    let builder = ref_from_ptr(builder);
+                    match builder.$getter() {
+                        Some(value) => CString::new(value).unwrap().into_raw(),
+                        None => ptr::null_mut()
+                    }
+                }
+                /// Caller is responsible for freeing the memory pointed by `value`.
+                #[no_mangle]
+                pub extern "C" fn [<accesskit_node_builder_ $setter>](builder: *mut node_builder, value: *const c_char) {
+                    let builder = mut_from_ptr(builder);
+                    let value = unsafe { CStr::from_ptr(value) };
+                    builder.$setter(value.to_string_lossy());
+                }
+            }
+            clearer! { $clearer })*
+        }
+    }
+}
+
+macro_rules! f64_property_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        opt_struct! { opt_double, f64 }
+        $(simple_property_methods! {
+            $getter, opt_double, $setter, f64, $clearer
+        })*
+    }
+}
+
+macro_rules! usize_property_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        opt_struct! { opt_index, usize }
+        $(simple_property_methods! {
+            $getter, opt_index, $setter, usize, $clearer
+        })*
+    }
+}
+
+macro_rules! color_property_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        opt_struct! { opt_color, u32 }
+        $(simple_property_methods! {
+            $getter, opt_color, $setter, u32, $clearer
+        })*
+    }
+}
+
+macro_rules! text_decoration_property_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        opt_struct! { opt_text_decoration, TextDecoration }
+        $(simple_property_methods! {
+            $getter, opt_text_decoration, $setter, TextDecoration, $clearer
+        })*
+    }
+}
+
+macro_rules! opt_slice_struct {
+    ($struct_name:ident, $rust_type:ty, $ffi_type:ty) => {
+        #[repr(C)]
+        pub struct $struct_name {
+            pub has_value: bool,
+            pub length: usize,
+            pub values: *const $ffi_type,
+        }
+        impl From<Option<&[$rust_type]>> for $struct_name {
+            fn from(value: Option<&[$rust_type]>) -> $struct_name {
+                match value {
+                    Some(value) => $struct_name {
+                        has_value: true,
+                        length: value.len(),
+                        values: value.as_ptr() as *const $ffi_type,
+                    },
+                    None => $struct_name::default(),
+                }
+            }
+        }
+        impl Default for $struct_name {
+            fn default() -> $struct_name {
+                $struct_name {
+                    has_value: false,
+                    length: 0,
+                    values: ptr::null(),
+                }
+            }
+        }
+    };
+}
+
+macro_rules! length_slice_property_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        slice_struct! { lengths, u8, u8 }
+        $(simple_property_methods! {
+            $getter, lengths, $setter, *const u8, $clearer
+        })*
+    }
+}
+
+macro_rules! coord_slice_property_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        opt_slice_struct! { opt_coords, f32, f32 }
+        $(simple_property_methods! {
+            $getter, opt_coords, $setter, *const f32, $clearer
+        })*
+    }
+}
+
+macro_rules! bool_property_methods {
+    ($(($getter:ident, $setter:ident, $clearer:ident)),+) => {
+        opt_struct! { opt_bool, bool }
+        $(simple_property_methods! {
+            $getter, opt_bool, $setter, bool, $clearer
+        })*
+    }
+}
+
+macro_rules! unique_enum_property_methods {
+    ($(($prop_type:ty, $getter:ident, $setter:ident, $clearer:ident)),+) => {
+        $(paste! {
+            opt_struct! { [<opt_ $prop_type >], $prop_type }
+            simple_property_methods! {
+                $getter, [<opt_ $prop_type >], $setter, $prop_type, $clearer
+            }
+        })*
+    }
+}
+
+property_getters! { role, Role }
+impl node_builder {
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_builder_set_role(builder: *mut node_builder, value: Role) {
+        let builder = mut_from_ptr(builder);
+        builder.set_role(value);
+    }
+}
+
+impl node {
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_supports_action(node: *const node, action: Action) -> bool {
+        let node = ref_from_ptr(node);
+        node.supports_action(action)
+    }
+}
+
+impl node_builder {
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_builder_supports_action(
+        builder: *const node_builder,
+        action: Action,
+    ) -> bool {
+        let builder = ref_from_ptr(builder);
+        builder.supports_action(action)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_builder_add_action(
+        builder: *mut node_builder,
+        action: Action,
+    ) {
+        let builder = mut_from_ptr(builder);
+        builder.add_action(action);
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_builder_remove_action(
+        builder: *mut node_builder,
+        action: Action,
+    ) {
+        let builder = mut_from_ptr(builder);
+        builder.remove_action(action);
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_builder_clear_actions(builder: *mut node_builder) {
+        let builder = mut_from_ptr(builder);
+        builder.clear_actions();
+    }
+}
+
+flag_methods! {
+    (is_autofill_available, set_autofill_available, clear_autofill_available),
+    (is_default, set_default, clear_default),
+    (is_editable, set_editable, clear_editable),
+    (is_hovered, set_hovered, clear_hovered),
+    (is_hidden, set_hidden, clear_hidden),
+    (is_linked, set_linked, clear_linked),
+    (is_multiline, set_multiline, clear_multiline),
+    (is_multiselectable, set_multiselectable, clear_multiselectable),
+    (is_protected, set_protected, clear_protected),
+    (is_required, set_required, clear_required),
+    (is_visited, set_visited, clear_visited),
+    (is_busy, set_busy, clear_busy),
+    (is_live_atomic, set_live_atomic, clear_live_atomic),
+    (is_modal, set_modal, clear_modal),
+    (is_scrollable, set_scrollable, clear_scrollable),
+    (is_selected_from_focus, set_selected_from_focus, clear_selected_from_focus),
+    (is_touch_pass_through, set_touch_pass_through, clear_touch_pass_through),
+    (is_read_only, set_read_only, clear_read_only),
+    (is_disabled, set_disabled, clear_disabled),
+    (is_bold, set_bold, clear_bold),
+    (is_italic, set_italic, clear_italic),
+    (canvas_has_fallback, set_canvas_has_fallback, clear_canvas_has_fallback),
+    (clips_children, set_clips_children, clear_clips_children),
+    (is_line_breaking_object, set_is_line_breaking_object, clear_is_line_breaking_object),
+    (is_page_breaking_object, set_is_page_breaking_object, clear_is_page_breaking_object),
+    (is_spelling_error, set_is_spelling_error, clear_is_spelling_error),
+    (is_grammar_error, set_is_grammar_error, clear_is_grammar_error),
+    (is_search_match, set_is_search_match, clear_is_search_match),
+    (is_suggestion, set_is_suggestion, clear_is_suggestion),
+    (is_nonatomic_text_field_root, set_is_nonatomic_text_field_root, clear_is_nonatomic_text_field_root)
+}
+
+node_id_vec_property_methods! {
+    (children, set_children, push_child, clear_children),
+    (indirect_children, set_indirect_children, push_indirect_child, clear_indirect_children),
+    (controls, set_controls, push_controlled, clear_controls),
+    (details, set_details, push_detail, clear_details),
+    (described_by, set_described_by, push_described_by, clear_described_by),
+    (flow_to, set_flow_to, push_flow_to, clear_flow_to),
+    (labelled_by, set_labelled_by, push_labelled_by, clear_labelled_by),
+    (radio_group, set_radio_group, push_to_radio_group, clear_radio_group)
+}
+
+node_id_property_methods! {
+    (active_descendant, set_active_descendant, clear_active_descendant),
+    (error_message, set_error_message, clear_error_message),
+    (in_page_link_target, set_in_page_link_target, clear_in_page_link_target),
+    (member_of, set_member_of, clear_member_of),
+    (next_on_line, set_next_on_line, clear_next_on_line),
+    (previous_on_line, set_previous_on_line, clear_previous_on_line),
+    (popup_for, set_popup_for, clear_popup_for),
+    (table_header, set_table_header, clear_table_header),
+    (table_row_header, set_table_row_header, clear_table_row_header),
+    (table_column_header, set_table_column_header, clear_table_column_header),
+    (next_focus, set_next_focus, clear_next_focus),
+    (previous_focus, set_previous_focus, clear_previous_focus)
+}
+
+/// Only call this function with a string that originated from AccessKit.
+#[no_mangle]
+pub extern "C" fn accesskit_string_free(string: *mut c_char) {
+    assert!(!string.is_null());
+    drop(unsafe { CString::from_raw(string) });
+}
+
+string_property_methods! {
+    (name, set_name, clear_name),
+    (description, set_description, clear_description),
+    (value, set_value, clear_value),
+    (access_key, set_access_key, clear_access_key),
+    (auto_complete, set_auto_complete, clear_auto_complete),
+    (checked_state_description, set_checked_state_description, clear_checked_state_description),
+    (class_name, set_class_name, clear_class_name),
+    (css_display, set_css_display, clear_css_display),
+    (font_family, set_font_family, clear_font_family),
+    (html_tag, set_html_tag, clear_html_tag),
+    (inner_html, set_inner_html, clear_inner_html),
+    (input_type, set_input_type, clear_input_type),
+    (key_shortcuts, set_key_shortcuts, clear_key_shortcuts),
+    (language, set_language, clear_language),
+    (live_relevant, set_live_relevant, clear_live_relevant),
+    (placeholder, set_placeholder, clear_placeholder),
+    (aria_role, set_aria_role, clear_aria_role),
+    (role_description, set_role_description, clear_role_description),
+    (tooltip, set_tooltip, clear_tooltip),
+    (url, set_url, clear_url)
+}
+
+f64_property_methods! {
+    (scroll_x, set_scroll_x, clear_scroll_x),
+    (scroll_x_min, set_scroll_x_min, clear_scroll_x_min),
+    (scroll_x_max, set_scroll_x_max, clear_scroll_x_max),
+    (scroll_y, set_scroll_y, clear_scroll_y),
+    (scroll_y_min, set_scroll_y_min, clear_scroll_y_min),
+    (scroll_y_max, set_scroll_y_max, clear_scroll_y_max),
+    (numeric_value, set_numeric_value, clear_numeric_value),
+    (min_numeric_value, set_min_numeric_value, clear_min_numeric_value),
+    (max_numeric_value, set_max_numeric_value, clear_max_numeric_value),
+    (numeric_value_step, set_numeric_value_step, clear_numeric_value_step),
+    (numeric_value_jump, set_numeric_value_jump, clear_numeric_value_jump),
+    (font_size, set_font_size, clear_font_size),
+    (font_weight, set_font_weight, clear_font_weight),
+    (text_indent, set_text_indent, clear_text_indent)
+}
+
+usize_property_methods! {
+    (table_row_count, set_table_row_count, clear_table_row_count),
+    (table_column_count, set_table_column_count, clear_table_column_count),
+    (table_row_index, set_table_row_index, clear_table_row_index),
+    (table_column_index, set_table_column_index, clear_table_column_index),
+    (table_cell_column_index, set_table_cell_column_index, clear_table_cell_column_index),
+    (table_cell_column_span, set_table_cell_column_span, clear_table_cell_column_span),
+    (table_cell_row_index, set_table_cell_row_index, clear_table_cell_row_index),
+    (table_cell_row_span, set_table_cell_row_span, clear_table_cell_row_span),
+    (hierarchical_level, set_hierarchical_level, clear_hierarchical_level),
+    (size_of_set, set_size_of_set, clear_size_of_set),
+    (position_in_set, set_position_in_set, clear_position_in_set)
+}
+
+color_property_methods! {
+    (color_value, set_color_value, clear_color_value),
+    (background_color, set_background_color, clear_background_color),
+    (foreground_color, set_foreground_color, clear_foreground_color)
+}
+
+text_decoration_property_methods! {
+    (overline, set_overline, clear_overline),
+    (strikethrough, set_strikethrough, clear_strikethrough),
+    (underline, set_underline, clear_underline)
+}
+
+length_slice_property_methods! {
+    (character_lengths, set_character_lengths, clear_character_lengths),
+    (word_lengths, set_word_lengths, clear_word_lengths)
+}
+
+coord_slice_property_methods! {
+    (character_positions, set_character_positions, clear_character_positions),
+    (character_widths, set_character_widths, clear_character_widths)
+}
+
+bool_property_methods! {
+    (is_expanded, set_expanded, clear_expanded),
+    (is_selected, set_selected, clear_selected)
+}
+
+unique_enum_property_methods! {
+    (NameFrom, name_from, set_name_from, clear_name_from),
+    (DescriptionFrom, description_from, set_description_from, clear_description_from),
+    (Invalid, invalid, set_invalid, clear_invalid),
+    (CheckedState, checked_state, set_checked_state, clear_checked_state),
+    (Live, live, set_live, clear_live),
+    (DefaultActionVerb, default_action_verb, set_default_action_verb, clear_default_action_verb),
+    (TextDirection, text_direction, set_text_direction, clear_text_direction),
+    (Orientation, orientation, set_orientation, clear_orientation),
+    (SortDirection, sort_direction, set_sort_direction, clear_sort_direction),
+    (AriaCurrent, aria_current, set_aria_current, clear_aria_current),
+    (HasPopup, has_popup, set_has_popup, clear_has_popup),
+    (ListStyle, list_style, set_list_style, clear_list_style),
+    (TextAlign, text_align, set_text_align, clear_text_align),
+    (VerticalOffset, vertical_offset, set_vertical_offset, clear_vertical_offset)
+}
+
+simple_property_methods! {
+    transform, *const Affine, set_transform, Affine, clear_transform
+}
+opt_struct! { opt_rect, Rect }
+simple_property_methods! {
+    bounds, opt_rect, set_bounds, Rect, clear_bounds
+}
+
+#[repr(C)]
+pub struct text_position {
+    pub node: node_id,
+    pub character_index: usize,
+}
+
+impl From<text_position> for TextPosition {
+    fn from(position: text_position) -> Self {
+        Self {
+            node: position.node.into(),
+            character_index: position.character_index,
+        }
+    }
+}
+
+impl From<TextPosition> for text_position {
+    fn from(position: TextPosition) -> Self {
+        Self {
+            node: position.node.into(),
+            character_index: position.character_index,
+        }
+    }
+}
+
+#[repr(C)]
+pub struct text_selection {
+    pub anchor: text_position,
+    pub focus: text_position,
+}
+
+impl From<text_selection> for TextSelection {
+    fn from(selection: text_selection) -> Self {
+        Self {
+            anchor: selection.anchor.into(),
+            focus: selection.focus.into(),
+        }
+    }
+}
+
+impl From<TextSelection> for text_selection {
+    fn from(selection: TextSelection) -> Self {
+        Self {
+            anchor: selection.anchor.into(),
+            focus: selection.focus.into(),
+        }
+    }
+}
+
+impl From<&TextSelection> for text_selection {
+    fn from(selection: &TextSelection) -> Self {
+        Self {
+            anchor: selection.anchor.into(),
+            focus: selection.focus.into(),
+        }
+    }
+}
+
+opt_struct! { opt_text_selection, text_selection }
+property_getters! { text_selection, opt_text_selection }
+impl node_builder {
+    #[no_mangle]
+    pub extern "C" fn accesskit_builder_set_text_selection(
+        builder: *mut node_builder,
+        value: text_selection,
+    ) {
+        let builder = mut_from_ptr(builder);
+        builder.set_text_selection(Box::new(value.into()));
+    }
+}
+clearer! { clear_text_selection }
+
+/// Use `accesskit_custom_action_new` to create this struct. Do not reallocate `description`.
+///
+/// When you get this struct, you are responsible for freeing `description`.
+#[derive(Clone)]
+#[repr(C)]
+pub struct custom_action {
+    pub id: i32,
+    pub description: *mut c_char,
+}
+
+impl custom_action {
+    #[no_mangle]
+    pub extern "C" fn accesskit_custom_action_new(
+        id: i32,
+        description: *const c_char,
+    ) -> custom_action {
+        let description = CString::new(String::from(
+            unsafe { CStr::from_ptr(description) }.to_string_lossy(),
+        ))
+        .unwrap();
+        Self {
+            id,
+            description: description.into_raw(),
+        }
+    }
+}
+
+impl Drop for custom_action {
+    fn drop(&mut self) {
+        accesskit_string_free(self.description);
+    }
+}
+
+impl From<custom_action> for CustomAction {
+    fn from(action: custom_action) -> Self {
+        Self {
+            id: action.id,
+            description: unsafe { CStr::from_ptr(action.description).to_string_lossy().into() },
+        }
+    }
+}
+
+impl From<&custom_action> for CustomAction {
+    fn from(action: &custom_action) -> Self {
+        Self {
+            id: action.id,
+            description: unsafe { CStr::from_ptr(action.description).to_string_lossy().into() },
+        }
+    }
+}
+
+impl From<&CustomAction> for custom_action {
+    fn from(action: &CustomAction) -> Self {
+        Self {
+            id: action.id,
+            description: CString::new(&*action.description).unwrap().into_raw(),
+        }
+    }
+}
+
+array_struct! { custom_actions, CustomAction, custom_action }
+
+vec_property_methods! {
+    (CustomAction, custom_actions, *mut custom_actions, set_custom_actions, custom_action, push_custom_action, clear_custom_actions)
+}
+
+impl node_id {
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_id_new(id: u64) -> opt_node_id {
+        NonZeroU128::new(id as u128).map(NodeId).into()
+    }
+}
+
+impl node_builder {
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_builder_new(role: Role) -> *mut node_builder {
+        let builder = NodeBuilder::new(role);
+        BoxCastPtr::to_mut_ptr(builder)
+    }
+
+    /// Converts an `accesskit_node_builder` to an `accesskit_node`, freeing the memory in the process.
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_builder_build(
+        builder: *mut node_builder,
+        classes: *mut node_class_set,
+    ) -> *mut node {
+        let builder = box_from_ptr(builder);
+        let classes = mut_from_ptr(classes);
+        let node = builder.build(classes);
+        BoxCastPtr::to_mut_ptr(node)
+    }
+
+    /// Only call this function if you have to abort the building of a node.
+    ///
+    /// If you called `accesskit_node_builder_build`, don't call this function.
+    #[no_mangle]
+    pub extern "C" fn accesskit_node_builder_free(builder: *mut node_builder) {
+        drop(box_from_ptr(builder));
+    }
+}
+
+#[repr(C)]
+pub struct tree {
+    pub root: node_id,
+    pub root_scroller: opt_node_id,
+}
+
+impl tree {
+    #[no_mangle]
+    pub extern "C" fn accesskit_tree_new(root: node_id) -> tree {
+        tree {
+            root,
+            root_scroller: opt_node_id::default(),
+        }
+    }
+}
+
+opt_struct! { opt_tree, tree }
+
+impl From<tree> for Tree {
+    fn from(tree: tree) -> Self {
+        Self {
+            root: tree.root.into(),
+            root_scroller: tree.root_scroller.into(),
+        }
+    }
+}
+
+/// Use `accesskit_tree_update_new` to create this struct. Do not reallocate `ids` and `nodes`.
+#[repr(C)]
+pub struct tree_update {
+    pub node_count: usize,
+    pub ids: *mut node_id,
+    pub nodes: *mut *mut node,
+    pub tree: opt_tree,
+    pub focus: opt_node_id,
+}
+
+impl CastPtr for tree_update {
+    type RustType = tree_update;
+}
+
+impl BoxCastPtr for tree_update {}
+
+impl tree_update {
+    #[no_mangle]
+    pub extern "C" fn accesskit_tree_update_new(node_count: usize) -> *mut tree_update {
+        let mut ids = Vec::with_capacity(node_count);
+        let mut nodes = Vec::with_capacity(node_count);
+        let update = tree_update {
+            node_count,
+            ids: ids.as_mut_ptr(),
+            nodes: nodes.as_mut_ptr(),
+            tree: opt_tree::default(),
+            focus: opt_node_id::default(),
+        };
+        mem::forget(ids);
+        mem::forget(nodes);
+        BoxCastPtr::to_mut_ptr(update)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_tree_update_free(update: *mut tree_update) {
+        drop(TreeUpdate::from(box_from_ptr(update)));
+    }
+}
+
+impl From<Box<tree_update>> for TreeUpdate {
+    fn from(update: Box<tree_update>) -> Self {
+        let ids = unsafe {
+            Vec::from_raw_parts(
+                update.ids as *mut NodeId,
+                update.node_count,
+                update.node_count,
+            )
+        };
+        let nodes =
+            unsafe { Vec::from_raw_parts(update.nodes, update.node_count, update.node_count) };
+        Self {
+            nodes: ids
+                .into_iter()
+                .zip(nodes.into_iter().map(box_from_ptr))
+                .map(|(id, node)| (id, *node))
+                .collect::<Vec<(NodeId, Node)>>(),
+            tree: update.tree.into(),
+            focus: update.focus.into(),
+        }
+    }
+}
+
+#[repr(C)]
+pub enum action_data {
+    CustomAction(i32),
+    Value(*mut c_char),
+    NumericValue(f64),
+    ScrollTargetRect(Rect),
+    ScrollToPoint(Point),
+    SetScrollOffset(Point),
+    SetTextSelection(text_selection),
+}
+
+impl Drop for action_data {
+    fn drop(&mut self) {
+        if let Self::Value(value) = *self {
+            accesskit_string_free(value);
+        }
+    }
+}
+
+opt_struct! { opt_action_data, action_data }
+
+impl From<ActionData> for action_data {
+    fn from(data: ActionData) -> Self {
+        match data {
+            ActionData::CustomAction(action) => Self::CustomAction(action),
+            ActionData::Value(value) => Self::Value(CString::new(&*value).unwrap().into_raw()),
+            ActionData::NumericValue(value) => Self::NumericValue(value),
+            ActionData::ScrollTargetRect(rect) => Self::ScrollTargetRect(rect),
+            ActionData::ScrollToPoint(point) => Self::ScrollToPoint(point),
+            ActionData::SetScrollOffset(offset) => Self::SetScrollOffset(offset),
+            ActionData::SetTextSelection(selection) => Self::SetTextSelection(selection.into()),
+        }
+    }
+}
+
+#[repr(C)]
+pub struct action_request {
+    pub action: Action,
+    pub target: node_id,
+    pub data: opt_action_data,
+}
+
+impl From<ActionRequest> for action_request {
+    fn from(request: ActionRequest) -> action_request {
+        Self {
+            action: request.action,
+            target: request.target.into(),
+            data: request.data.into(),
+        }
+    }
+}
+
+pub type ActionHandlerCallback =
+    Option<extern "C" fn(request: *const action_request, userdata: *mut c_void)>;
+
+struct FfiActionHandlerUserdata(*mut c_void);
+
+unsafe impl Send for FfiActionHandlerUserdata {}
+unsafe impl Sync for FfiActionHandlerUserdata {}
+
+pub(crate) struct FfiActionHandler {
+    callback: ActionHandlerCallback,
+    userdata: FfiActionHandlerUserdata,
+}
+
+pub struct action_handler {
+    _private: [u8; 0],
+}
+
+impl CastPtr for action_handler {
+    type RustType = FfiActionHandler;
+}
+
+impl BoxCastPtr for action_handler {}
+
+impl action_handler {
+    #[no_mangle]
+    pub extern "C" fn accesskit_action_handler_new(
+        callback: ActionHandlerCallback,
+        userdata: *mut c_void,
+    ) -> *mut action_handler {
+        let userdata = FfiActionHandlerUserdata(userdata);
+        let handler = FfiActionHandler { callback, userdata };
+        BoxCastPtr::to_mut_ptr(handler)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_action_handler_free(handler: *mut action_handler) {
+        drop(box_from_ptr(handler));
+    }
+}
+
+impl ActionHandler for FfiActionHandler {
+    fn do_action(&self, request: ActionRequest) {
+        if let Some(callback) = self.callback {
+            let request = request.into();
+            callback(&request, self.userdata.0);
+        }
+    }
+}
+
+/// This function can't return a null pointer. Ownership of the returned value will be transfered to the caller.
+#[cfg(any(target_os = "windows", target_os = "macos", feature = "cbindgen"))]
+pub type tree_update_factory = Option<extern "C" fn(*mut c_void) -> *mut tree_update>;
diff --git a/bindings/c/src/geometry.rs b/bindings/c/src/geometry.rs
new file mode 100644
index 000000000..16a238885
--- /dev/null
+++ b/bindings/c/src/geometry.rs
@@ -0,0 +1,185 @@
+// Copyright 2023 The AccessKit Authors. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (found in
+// the LICENSE-APACHE file) or the MIT license (found in
+// the LICENSE-MIT file), at your option.
+
+use accesskit::{Affine, Point, Rect, Size, Vec2};
+use paste::paste;
+
+#[no_mangle]
+pub const extern "C" fn accesskit_affine_identity() -> Affine {
+    Affine::scale(1.0)
+}
+
+#[no_mangle]
+pub const extern "C" fn accesskit_affine_flip_y() -> Affine {
+    Affine::new([1.0, 0., 0., -1.0, 0., 0.])
+}
+
+#[no_mangle]
+pub const extern "C" fn accesskit_affine_flip_x() -> Affine {
+    Affine::new([-1.0, 0., 0., 1.0, 0., 0.])
+}
+
+#[no_mangle]
+pub const extern "C" fn accesskit_affine_scale(s: f64) -> Affine {
+    Affine::scale(s)
+}
+
+#[no_mangle]
+pub const extern "C" fn accesskit_affine_scale_non_uniform(s_x: f64, s_y: f64) -> Affine {
+    Affine::scale_non_uniform(s_x, s_y)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_affine_rotate(th: f64) -> Affine {
+    Affine::rotate(th)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_affine_translate(p: Vec2) -> Affine {
+    Affine::translate(p)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_affine_map_unit_square(rect: Rect) -> Affine {
+    Affine::map_unit_square(rect)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_affine_determinant(affine: Affine) -> f64 {
+    Affine::determinant(affine)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_affine_inverse(affine: Affine) -> Affine {
+    Affine::inverse(affine)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_affine_transform_rect_bbox(affine: Affine, rect: Rect) -> Rect {
+    Affine::transform_rect_bbox(affine, rect)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_affine_is_finite(affine: *const Affine) -> bool {
+    if affine.is_null() {
+        false
+    } else {
+        unsafe { Box::from_raw(affine as *mut Affine).is_finite() }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_affine_is_nan(affine: *const Affine) -> bool {
+    if affine.is_null() {
+        false
+    } else {
+        unsafe { Box::from_raw(affine as *mut Affine).is_nan() }
+    }
+}
+
+#[no_mangle]
+pub const extern "C" fn accesskit_point_to_vec2(point: Point) -> Vec2 {
+    Point::to_vec2(point)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_rect_from_points(p0: Point, p1: Point) -> Rect {
+    Rect::from_points(p0, p1)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_rect_from_origin_size(origin: Point, size: Size) -> Rect {
+    Rect::from_origin_size(origin, size)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_rect_with_origin(rect: Rect, origin: Point) -> Rect {
+    Rect::with_origin(rect, origin)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_rect_with_size(rect: Rect, size: Size) -> Rect {
+    Rect::with_size(rect, size)
+}
+
+macro_rules! rect_getter_methods {
+    ($(($getter:ident, $getter_result:ty, $default_value:expr)),+) => {
+        paste! {
+            $(#[no_mangle]
+            pub extern "C" fn [<accesskit_rect_ $getter>](rect: *const Rect) -> $getter_result {
+                if rect.is_null() {
+                    $default_value
+                } else {
+                    unsafe { Box::from_raw(rect as *mut Rect).$getter() }
+                }
+            })*
+        }
+    }
+}
+
+rect_getter_methods! {
+    (width, f64, 0.),
+    (height, f64, 0.),
+    (min_x, f64, 0.),
+    (max_x, f64, 0.),
+    (min_y, f64, 0.),
+    (max_y, f64, 0.),
+    (origin, Point, Point::ZERO),
+    (size, Size, Size::ZERO),
+    (abs, Rect, Rect::ZERO),
+    (area, f64, 0.),
+    (is_empty, bool, true)
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_rect_contains(rect: *const Rect, point: Point) -> bool {
+    if rect.is_null() {
+        false
+    } else {
+        unsafe { Box::from_raw(rect as *mut Rect).contains(point) }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_rect_union(rect: *const Rect, other: Rect) -> Rect {
+    if rect.is_null() {
+        Rect::ZERO
+    } else {
+        unsafe { Box::from_raw(rect as *mut Rect).union(other) }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_rect_union_pt(rect: *const Rect, pt: Point) -> Rect {
+    if rect.is_null() {
+        Rect::ZERO
+    } else {
+        unsafe { Box::from_raw(rect as *mut Rect).union_pt(pt) }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn accesskit_rect_intersect(rect: *const Rect, other: Rect) -> Rect {
+    if rect.is_null() {
+        Rect::ZERO
+    } else {
+        unsafe { Box::from_raw(rect as *mut Rect).intersect(other) }
+    }
+}
+
+#[no_mangle]
+pub const extern "C" fn accesskit_size_to_vec2(size: Size) -> Vec2 {
+    Size::to_vec2(size)
+}
+
+#[no_mangle]
+pub const extern "C" fn accesskit_vec2_to_point(vec2: Vec2) -> Point {
+    Vec2::to_point(vec2)
+}
+
+#[no_mangle]
+pub const extern "C" fn accesskit_vec2_to_size(vec2: Vec2) -> Size {
+    Vec2::to_size(vec2)
+}
diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs
new file mode 100644
index 000000000..e12f634de
--- /dev/null
+++ b/bindings/c/src/lib.rs
@@ -0,0 +1,177 @@
+// Copyright 2023 The AccessKit Authors. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (found in
+// the LICENSE-APACHE file) or the MIT license (found in
+// the LICENSE-MIT file), at your option.
+
+// Derived from rustls-ffi.
+// Copyright (c) 2021, Jacob Hoffman-Andrews <jsha@letsencrypt.org>
+// Licensed under the Apache License, Version 2.0 (found in
+// the LICENSE-APACHE file), the ISC license (found in
+// the LICENSE-ISC file), or the MIT license (found in
+// the LICENSE-MIT file), at your option.
+
+#![allow(non_camel_case_types)]
+#![allow(clippy::not_unsafe_ptr_arg_deref)]
+
+mod common;
+mod geometry;
+
+#[cfg(any(target_os = "macos", feature = "cbindgen"))]
+mod macos;
+#[cfg(any(
+    target_os = "linux",
+    target_os = "dragonfly",
+    target_os = "freebsd",
+    target_os = "netbsd",
+    target_os = "openbsd",
+    feature = "cbindgen"
+))]
+mod unix;
+#[cfg(any(target_os = "windows", feature = "cbindgen"))]
+mod windows;
+
+pub use common::*;
+pub use geometry::*;
+#[cfg(any(target_os = "macos", feature = "cbindgen"))]
+pub use macos::*;
+#[cfg(any(
+    target_os = "linux",
+    target_os = "dragonfly",
+    target_os = "freebsd",
+    target_os = "netbsd",
+    target_os = "openbsd",
+    feature = "cbindgen"
+))]
+pub use unix::*;
+#[cfg(any(target_os = "windows", feature = "cbindgen"))]
+pub use windows::*;
+
+/// CastPtr represents the relationship between a snake case type (like node_class_set)
+/// and the corresponding Rust type (like NodeClassSet). For each matched pair of types, there
+/// should be an `impl CastPtr for foo_bar { RustType = FooBar }`.
+///
+/// This allows us to avoid using `as` in most places, and ensure that when we cast, we're
+/// preserving const-ness, and casting between the correct types.
+/// Implementing this is required in order to use `ref_from_ptr!` or
+/// `mut_from_ptr!`.
+pub(crate) trait CastPtr {
+    type RustType;
+
+    fn cast_mut_ptr(ptr: *mut Self) -> *mut Self::RustType {
+        ptr as *mut _
+    }
+}
+
+/// CastConstPtr represents a subset of CastPtr, for when we can only treat
+/// something as a const (for instance when dealing with Arc).
+pub(crate) trait CastConstPtr {
+    type RustType;
+
+    fn cast_const_ptr(ptr: *const Self) -> *const Self::RustType {
+        ptr as *const _
+    }
+}
+
+/// Anything that qualifies for CastPtr also automatically qualifies for
+/// CastConstPtr. Splitting out CastPtr vs CastConstPtr allows us to ensure
+/// that Arcs are never cast to a mutable pointer.
+impl<T, R> CastConstPtr for T
+where
+    T: CastPtr<RustType = R>,
+{
+    type RustType = R;
+}
+
+// An implementation of BoxCastPtr means that when we give C code a pointer to the relevant type,
+// it is actually a Box.
+pub(crate) trait BoxCastPtr: CastPtr + Sized {
+    fn to_box(ptr: *mut Self) -> Box<Self::RustType> {
+        assert!(!ptr.is_null());
+        let rs_typed = Self::cast_mut_ptr(ptr);
+        unsafe { Box::from_raw(rs_typed) }
+    }
+
+    fn to_mut_ptr(src: Self::RustType) -> *mut Self {
+        Box::into_raw(Box::new(src)) as *mut _
+    }
+
+    fn set_mut_ptr(dst: *mut *mut Self, src: Self::RustType) {
+        unsafe {
+            *dst = Self::to_mut_ptr(src);
+        }
+    }
+}
+
+/// Turn a raw const pointer into a reference. This is a generic function
+/// rather than part of the CastPtr trait because (a) const pointers can't act
+/// as "self" for trait methods, and (b) we want to rely on type inference
+/// against T (the cast-to type) rather than across F (the from type).
+pub(crate) fn ref_from_ptr<'a, F, T>(from: *const F) -> &'a T
+where
+    F: CastConstPtr<RustType = T>,
+{
+    unsafe { F::cast_const_ptr(from).as_ref() }.unwrap()
+}
+
+/// Turn a raw mut pointer into a mutable reference.
+pub(crate) fn mut_from_ptr<'a, F, T>(from: *mut F) -> &'a mut T
+where
+    F: CastPtr<RustType = T>,
+{
+    unsafe { F::cast_mut_ptr(from).as_mut() }.unwrap()
+}
+
+pub(crate) fn box_from_ptr<F, T>(from: *mut F) -> Box<T>
+where
+    F: BoxCastPtr<RustType = T>,
+{
+    F::to_box(from)
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! opt_struct {
+    ($struct_name:ident, $prop_type:ty) => {
+        /// Represents an optional value.
+        ///
+        /// If `has_value` is false, do not read the `value` field.
+        #[repr(C)]
+        pub struct $struct_name {
+            pub has_value: bool,
+            pub value: std::mem::MaybeUninit<$prop_type>,
+        }
+        impl<T> From<Option<T>> for $struct_name
+        where
+            T: Into<$prop_type>,
+        {
+            fn from(value: Option<T>) -> $struct_name {
+                match value {
+                    None => $struct_name::default(),
+                    Some(value) => $struct_name {
+                        has_value: true,
+                        value: std::mem::MaybeUninit::new(value.into()),
+                    },
+                }
+            }
+        }
+        impl<T> From<$struct_name> for Option<T>
+        where
+            T: From<$prop_type>,
+        {
+            fn from(value: $struct_name) -> Self {
+                match value.has_value {
+                    true => Some(unsafe { T::from(value.value.assume_init()) }),
+                    false => None,
+                }
+            }
+        }
+        impl Default for $struct_name {
+            fn default() -> $struct_name {
+                $struct_name {
+                    has_value: false,
+                    value: std::mem::MaybeUninit::uninit(),
+                }
+            }
+        }
+    };
+}
diff --git a/bindings/c/src/macos.rs b/bindings/c/src/macos.rs
new file mode 100644
index 000000000..a4ce1cf62
--- /dev/null
+++ b/bindings/c/src/macos.rs
@@ -0,0 +1,168 @@
+// Copyright 2023 The AccessKit Authors. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (found in
+// the LICENSE-APACHE file) or the MIT license (found in
+// the LICENSE-MIT file), at your option.
+
+use crate::{
+    action_handler, box_from_ptr, ref_from_ptr, tree_update, tree_update_factory, BoxCastPtr,
+    CastPtr,
+};
+use accesskit_macos::{Adapter, NSObject, NSPoint, QueuedEvents, SubclassingAdapter};
+use std::{os::raw::c_void, ptr};
+
+#[cfg(not(feature = "cbindgen"))]
+pub type NSArray = c_void;
+
+pub struct macos_queued_events {
+    _private: [u8; 0],
+}
+
+impl CastPtr for macos_queued_events {
+    type RustType = QueuedEvents;
+}
+
+impl BoxCastPtr for macos_queued_events {}
+
+impl macos_queued_events {
+    /// Memory is also freed when calling this function.
+    #[no_mangle]
+    pub extern "C" fn accesskit_macos_queued_events_raise(events: *mut macos_queued_events) {
+        let events = box_from_ptr(events);
+        events.raise();
+    }
+}
+
+pub struct macos_adapter {
+    _private: [u8; 0],
+}
+
+impl CastPtr for macos_adapter {
+    type RustType = Adapter;
+}
+
+impl BoxCastPtr for macos_adapter {}
+
+impl macos_adapter {
+    /// This function takes ownership of `initial_state` and `handler`.
+    #[no_mangle]
+    pub unsafe extern "C" fn accesskit_macos_adapter_new(
+        view: *mut c_void,
+        initial_state: *mut tree_update,
+        handler: *mut action_handler,
+    ) -> *mut macos_adapter {
+        let initial_state = box_from_ptr(initial_state);
+        let handler = box_from_ptr(handler);
+        let adapter = Adapter::new(view, initial_state.into(), handler);
+        BoxCastPtr::to_mut_ptr(adapter)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_macos_adapter_free(adapter: *mut macos_adapter) {
+        drop(box_from_ptr(adapter));
+    }
+
+    /// This function takes ownership of `update`.
+    /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. It can be null in case of error.
+    #[no_mangle]
+    pub extern "C" fn accesskit_macos_adapter_update(
+        adapter: *const macos_adapter,
+        update: *mut tree_update,
+    ) -> *mut macos_queued_events {
+        let adapter = ref_from_ptr(adapter);
+        let update = box_from_ptr(update);
+        let events = adapter.update(update.into());
+        BoxCastPtr::to_mut_ptr(events)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_macos_adapter_view_children(
+        adapter: *const macos_adapter,
+    ) -> *mut NSArray {
+        let adapter = ref_from_ptr(adapter);
+        adapter.view_children() as *mut _
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_macos_adapter_focus(
+        adapter: *const macos_adapter,
+    ) -> *mut NSObject {
+        let adapter = ref_from_ptr(adapter);
+        adapter.focus()
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_macos_adapter_hit_test(
+        adapter: *const macos_adapter,
+        point: NSPoint,
+    ) -> *mut NSObject {
+        let adapter = ref_from_ptr(adapter);
+        adapter.hit_test(point)
+    }
+}
+
+pub struct macos_subclassing_adapter {
+    _private: [u8; 0],
+}
+
+impl CastPtr for macos_subclassing_adapter {
+    type RustType = SubclassingAdapter;
+}
+
+impl BoxCastPtr for macos_subclassing_adapter {}
+
+impl macos_subclassing_adapter {
+    /// This function takes ownership of `handler`.
+    #[no_mangle]
+    pub unsafe extern "C" fn accesskit_macos_subclassing_adapter_new(
+        view: *mut c_void,
+        source: tree_update_factory,
+        source_userdata: *mut c_void,
+        handler: *mut action_handler,
+    ) -> *mut macos_subclassing_adapter {
+        let source = source.unwrap();
+        let handler = box_from_ptr(handler);
+        let adapter = SubclassingAdapter::new(
+            view,
+            move || box_from_ptr(source(source_userdata)).into(),
+            handler,
+        );
+        BoxCastPtr::to_mut_ptr(adapter)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_macos_subclassing_adapter_free(
+        adapter: *mut macos_subclassing_adapter,
+    ) {
+        drop(box_from_ptr(adapter));
+    }
+
+    /// This function takes ownership of `update`.
+    /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. It can be null in case of error.
+    #[no_mangle]
+    pub extern "C" fn accesskit_macos_subclassing_adapter_update(
+        adapter: *const macos_subclassing_adapter,
+        update: *mut tree_update,
+    ) -> *mut macos_queued_events {
+        let adapter = ref_from_ptr(adapter);
+        let update = box_from_ptr(update);
+        let events = adapter.update(update.into());
+        BoxCastPtr::to_mut_ptr(events)
+    }
+
+    /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. It can be null in case of error or if the window is not active.
+    #[no_mangle]
+    pub extern "C" fn accesskit_macos_subclassing_adapter_update_if_active(
+        adapter: *const macos_subclassing_adapter,
+        update_factory: tree_update_factory,
+        update_factory_userdata: *mut c_void,
+    ) -> *mut macos_queued_events {
+        let update_factory = update_factory.unwrap();
+        let adapter = ref_from_ptr(adapter);
+        let events = adapter
+            .update_if_active(|| box_from_ptr(update_factory(update_factory_userdata)).into());
+        match events {
+            Some(events) => BoxCastPtr::to_mut_ptr(events),
+            None => ptr::null_mut(),
+        }
+    }
+}
diff --git a/bindings/c/src/unix.rs b/bindings/c/src/unix.rs
new file mode 100644
index 000000000..783f26fbf
--- /dev/null
+++ b/bindings/c/src/unix.rs
@@ -0,0 +1,77 @@
+// Copyright 2023 The AccessKit Authors. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (found in
+// the LICENSE-APACHE file) or the MIT license (found in
+// the LICENSE-MIT file), at your option.
+
+use crate::{action_handler, box_from_ptr, ref_from_ptr, tree_update, BoxCastPtr, CastPtr};
+use accesskit::Rect;
+use accesskit_unix::Adapter;
+use std::{
+    ffi::CStr,
+    os::raw::{c_char, c_void},
+    ptr,
+};
+
+pub struct unix_adapter {
+    _private: [u8; 0],
+}
+
+impl CastPtr for unix_adapter {
+    type RustType = Adapter;
+}
+
+impl BoxCastPtr for unix_adapter {}
+
+impl unix_adapter {
+    /// Caller is responsible for freeing `app_name`, `toolkit_name` and `toolkit_version`.
+    /// This function will take ownership of the pointer returned by `initial_state`, which can't be null.
+    #[no_mangle]
+    pub extern "C" fn accesskit_unix_adapter_new(
+        app_name: *const c_char,
+        toolkit_name: *const c_char,
+        toolkit_version: *const c_char,
+        initial_state: Option<extern "C" fn(*mut c_void) -> *mut tree_update>,
+        initial_state_userdata: *mut c_void,
+        handler: *mut action_handler,
+    ) -> *mut unix_adapter {
+        let app_name = unsafe { CStr::from_ptr(app_name).to_string_lossy().into() };
+        let toolkit_name = unsafe { CStr::from_ptr(toolkit_name).to_string_lossy().into() };
+        let toolkit_version = unsafe { CStr::from_ptr(toolkit_version).to_string_lossy().into() };
+        let initial_state = initial_state.unwrap();
+        let handler = box_from_ptr(handler);
+        let adapter = Adapter::new(
+            app_name,
+            toolkit_name,
+            toolkit_version,
+            move || box_from_ptr(initial_state(initial_state_userdata)).into(),
+            handler,
+        );
+        adapter.map_or_else(ptr::null_mut, BoxCastPtr::to_mut_ptr)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_unix_adapter_free(adapter: *mut unix_adapter) {
+        drop(box_from_ptr(adapter));
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_unix_adapter_set_root_window_bounds(
+        adapter: *const unix_adapter,
+        outer: Rect,
+        inner: Rect,
+    ) {
+        let adapter = ref_from_ptr(adapter);
+        adapter.set_root_window_bounds(outer, inner);
+    }
+
+    /// This function takes ownership of `update`.
+    #[no_mangle]
+    pub extern "C" fn accesskit_unix_adapter_update(
+        adapter: *const unix_adapter,
+        update: *mut tree_update,
+    ) {
+        let adapter = ref_from_ptr(adapter);
+        let update = box_from_ptr(update);
+        adapter.update(update.into());
+    }
+}
diff --git a/bindings/c/src/windows.rs b/bindings/c/src/windows.rs
new file mode 100644
index 000000000..32f6a5109
--- /dev/null
+++ b/bindings/c/src/windows.rs
@@ -0,0 +1,179 @@
+// Copyright 2023 The AccessKit Authors. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (found in
+// the LICENSE-APACHE file) or the MIT license (found in
+// the LICENSE-MIT file), at your option.
+
+use crate::{
+    action_handler, box_from_ptr, opt_struct, ref_from_ptr, tree_update, tree_update_factory,
+    BoxCastPtr, CastPtr,
+};
+use accesskit_windows::*;
+use std::{os::raw::c_void, ptr};
+
+pub struct windows_uia_init_marker {
+    _private: [u8; 0],
+}
+
+impl CastPtr for windows_uia_init_marker {
+    type RustType = UiaInitMarker;
+}
+
+impl BoxCastPtr for windows_uia_init_marker {}
+
+impl windows_uia_init_marker {
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_uia_init_marker_new() -> *mut windows_uia_init_marker {
+        let marker = UiaInitMarker::new();
+        BoxCastPtr::to_mut_ptr(marker)
+    }
+
+    /// You don't need to call this if you use `accesskit_windows_adapter_new`.
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_uia_init_marker_free(marker: *mut windows_uia_init_marker) {
+        drop(box_from_ptr(marker));
+    }
+}
+
+pub struct windows_queued_events {
+    _private: [u8; 0],
+}
+
+impl CastPtr for windows_queued_events {
+    type RustType = QueuedEvents;
+}
+
+impl BoxCastPtr for windows_queued_events {}
+
+impl windows_queued_events {
+    /// Memory is also freed when calling this function.
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_queued_events_raise(events: *mut windows_queued_events) {
+        let events = box_from_ptr(events);
+        events.raise();
+    }
+}
+
+opt_struct! { opt_lresult, LRESULT }
+
+pub struct windows_adapter {
+    _private: [u8; 0],
+}
+
+impl CastPtr for windows_adapter {
+    type RustType = Adapter;
+}
+
+impl BoxCastPtr for windows_adapter {}
+
+impl windows_adapter {
+    /// This function takes ownership of all pointers passed to it.
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_adapter_new(
+        hwnd: HWND,
+        initial_state: *mut tree_update,
+        handler: *mut action_handler,
+        uia_init_marker: *mut windows_uia_init_marker,
+    ) -> *mut windows_adapter {
+        let initial_state = box_from_ptr(initial_state);
+        let handler = box_from_ptr(handler);
+        let uia_init_marker = *box_from_ptr(uia_init_marker);
+        let adapter = Adapter::new(hwnd, initial_state.into(), handler, uia_init_marker);
+        BoxCastPtr::to_mut_ptr(adapter)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_adapter_free(adapter: *mut windows_adapter) {
+        drop(box_from_ptr(adapter));
+    }
+
+    /// This function takes ownership of `update`.
+    /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. It can be null in case of error.
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_adapter_update(
+        adapter: *const windows_adapter,
+        update: *mut tree_update,
+    ) -> *mut windows_queued_events {
+        let adapter = ref_from_ptr(adapter);
+        let update = box_from_ptr(update);
+        let events = adapter.update(update.into());
+        BoxCastPtr::to_mut_ptr(events)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_adapter_handle_wm_getobject(
+        adapter: *mut windows_adapter,
+        wparam: WPARAM,
+        lparam: LPARAM,
+    ) -> opt_lresult {
+        let adapter = ref_from_ptr(adapter);
+        let lresult = adapter.handle_wm_getobject(wparam, lparam);
+        opt_lresult::from(lresult)
+    }
+}
+
+pub struct windows_subclassing_adapter {
+    _private: [u8; 0],
+}
+
+impl CastPtr for windows_subclassing_adapter {
+    type RustType = SubclassingAdapter;
+}
+
+impl BoxCastPtr for windows_subclassing_adapter {}
+
+impl windows_subclassing_adapter {
+    /// This function takes ownership of `handler`.
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_subclassing_adapter_new(
+        hwnd: HWND,
+        source: tree_update_factory,
+        source_userdata: *mut c_void,
+        handler: *mut action_handler,
+    ) -> *mut windows_subclassing_adapter {
+        let source = source.unwrap();
+        let handler = box_from_ptr(handler);
+        let adapter = SubclassingAdapter::new(
+            hwnd,
+            move || box_from_ptr(source(source_userdata)).into(),
+            handler,
+        );
+        BoxCastPtr::to_mut_ptr(adapter)
+    }
+
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_subclassing_adapter_free(
+        adapter: *mut windows_subclassing_adapter,
+    ) {
+        drop(box_from_ptr(adapter));
+    }
+
+    /// This function takes ownership of `update`.
+    /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. It can be null in case of error.
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_subclassing_adapter_update(
+        adapter: *const windows_subclassing_adapter,
+        update: *mut tree_update,
+    ) -> *mut windows_queued_events {
+        let adapter = ref_from_ptr(adapter);
+        let update = box_from_ptr(update);
+        let events = adapter.update(update.into());
+        BoxCastPtr::to_mut_ptr(events)
+    }
+
+    /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. It can be null in case of error or if the window is not active.
+    #[no_mangle]
+    pub extern "C" fn accesskit_windows_subclassing_adapter_update_if_active(
+        adapter: *const windows_subclassing_adapter,
+        update_factory: tree_update_factory,
+        update_factory_userdata: *mut c_void,
+    ) -> *mut windows_queued_events {
+        let update_factory = update_factory.unwrap();
+        let adapter = ref_from_ptr(adapter);
+        let events = adapter
+            .update_if_active(|| box_from_ptr(update_factory(update_factory_userdata)).into());
+        match events {
+            Some(events) => BoxCastPtr::to_mut_ptr(events),
+            None => ptr::null_mut(),
+        }
+    }
+}
diff --git a/common/src/geometry.rs b/common/src/geometry.rs
index 07a09b15a..4e618de8b 100644
--- a/common/src/geometry.rs
+++ b/common/src/geometry.rs
@@ -18,6 +18,7 @@ use std::{
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[repr(C)]
 pub struct Affine([f64; 6]);
 
 impl Affine {
@@ -221,6 +222,7 @@ impl Mul<Affine> for f64 {
 #[derive(Clone, Copy, Default, PartialEq)]
 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[repr(C)]
 pub struct Point {
     /// The x coordinate.
     pub x: f64,
@@ -345,6 +347,7 @@ impl fmt::Debug for Point {
 #[derive(Clone, Copy, Default, PartialEq)]
 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[repr(C)]
 pub struct Rect {
     /// The minimum x coordinate (left edge).
     pub x0: f64,
@@ -578,6 +581,7 @@ impl Rect {
 #[derive(Clone, Copy, Default, PartialEq)]
 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[repr(C)]
 pub struct Size {
     /// The width.
     pub width: f64,
@@ -723,6 +727,7 @@ impl From<Size> for (f64, f64) {
 #[derive(Clone, Copy, Default, Debug, PartialEq)]
 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[repr(C)]
 pub struct Vec2 {
     /// The x-coordinate.
     pub x: f64,
diff --git a/release-please-config.json b/release-please-config.json
index 2a636a3e6..797027299 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -9,6 +9,7 @@
     "platforms/macos": {},
     "platforms/unix": {},
     "platforms/windows": {},
-    "platforms/winit": {}
+    "platforms/winit": {},
+    "bindings/c": {}
   }
 }
