├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── doc ├── ec-flow.svg ├── ec-flow.xml └── ec-user-signals.txt ├── etc ├── dbus │ └── org.surface.dtx.conf ├── dtx │ ├── attach.sh │ ├── detach.sh │ ├── surface-dtx-daemon.conf │ └── surface-dtx-userd.conf ├── systemd │ ├── surface-dtx-daemon.service │ └── surface-dtx-userd.service └── udev │ └── 40-surface_dtx.rules ├── pkg ├── bin │ ├── .gitignore │ └── makebin ├── deb │ ├── .gitignore │ ├── debian │ │ ├── changelog │ │ ├── compat │ │ ├── control │ │ └── rules │ └── makedeb └── fedora │ ├── makerpm │ └── surface-dtx-daemon.spec ├── surface-dtx-daemon ├── Cargo.toml ├── build.rs └── src │ ├── cli.rs │ ├── config.rs │ ├── logic │ ├── core.rs │ ├── mod.rs │ ├── proc.rs │ └── srvc.rs │ ├── main.rs │ ├── service │ ├── arg.rs │ ├── event.rs │ ├── mod.rs │ └── prop.rs │ └── utils │ ├── mod.rs │ ├── scope.rs │ ├── task.rs │ ├── taskq.rs │ └── tracing.rs └── surface-dtx-userd ├── Cargo.toml ├── build.rs └── src ├── cli.rs ├── config.rs ├── logic ├── core.rs ├── mod.rs └── types.rs ├── main.rs └── utils ├── mod.rs ├── notify.rs └── task.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - feature/ci 8 | 9 | tags: 10 | - v[0-9]+.* 11 | - testing-ci.* 12 | 13 | pull_request: 14 | 15 | jobs: 16 | lint: 17 | name: Clippy 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | 23 | - name: Install rust 24 | run: | 25 | rustup update stable && rustup default stable 26 | rustup component add clippy 27 | 28 | - name: Install dependencies 29 | run: sudo apt-get install libdbus-1-dev 30 | 31 | - name: Run clippy 32 | run: cargo clippy --all --all-features -- -Dwarnings 33 | 34 | test: 35 | name: Test 36 | runs-on: ubuntu-latest 37 | 38 | strategy: 39 | matrix: 40 | toolchain: [stable, nightly] 41 | 42 | steps: 43 | - name: Checkout code 44 | uses: actions/checkout@v4 45 | 46 | - name: Install rust 47 | run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} 48 | 49 | - name: Install dependencies 50 | run: sudo apt-get install libdbus-1-dev 51 | 52 | - name: Build 53 | run: cargo build --all 54 | 55 | - name: Test 56 | run: cargo test --all 57 | 58 | build-bin: 59 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') 60 | 61 | name: Build binary package 62 | runs-on: ubuntu-22.04 63 | needs: [lint, test] 64 | 65 | steps: 66 | - name: Checkout code 67 | uses: actions/checkout@v4 68 | 69 | - name: Install rust 70 | run: rustup update stable && rustup default stable 71 | 72 | - name: Install dependencies 73 | run: sudo apt-get install libdbus-1-dev 74 | 75 | - name: Build package 76 | run: ./pkg/bin/makebin 77 | 78 | - name: Prepare release 79 | run: mkdir release && mv pkg/bin/*.tar.xz release 80 | 81 | - name: Upload artifacts 82 | uses: actions/upload-artifact@v4 83 | with: 84 | name: binary-latest 85 | path: release 86 | 87 | build-deb: 88 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') 89 | 90 | name: Build deb package 91 | runs-on: ubuntu-22.04 92 | needs: [lint, test] 93 | 94 | steps: 95 | - name: Checkout code 96 | uses: actions/checkout@v4 97 | 98 | - name: Install rust 99 | run: rustup update stable && rustup default stable 100 | 101 | - name: Install dependencies 102 | run: sudo apt-get install debhelper fakeroot dpkg-sig libdbus-1-dev 103 | 104 | - name: Build package 105 | run: ./pkg/deb/makedeb 106 | 107 | - name: Sign package 108 | env: 109 | GPG_KEY_ID: 56C464BAAC421453 110 | GPG_KEY: ${{ secrets.LINUX_SURFACE_GPG_KEY }} 111 | run: | 112 | # import GPG key 113 | echo "$GPG_KEY" | base64 -d | gpg --import --no-tty --batch --yes 114 | export GPG_TTY=$(tty) 115 | # sign package 116 | cd pkg/deb && dpkg-sig -g "--batch --no-tty" --sign builder -k $GPG_KEY_ID ./*.deb 117 | - name: Prepare release 118 | run: mkdir release && mv pkg/deb/*.deb release 119 | 120 | - name: Upload artifacts 121 | uses: actions/upload-artifact@v4 122 | with: 123 | name: debian-latest 124 | path: release 125 | 126 | build-f40: 127 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') 128 | 129 | name: Build Fedora 40 package 130 | runs-on: ubuntu-latest 131 | needs: [lint, test] 132 | container: 133 | image: registry.fedoraproject.org/fedora:40 134 | options: --security-opt seccomp=unconfined 135 | 136 | steps: 137 | - name: Checkout code 138 | uses: actions/checkout@v4 139 | 140 | - name: Install build dependencies 141 | run: | 142 | dnf distro-sync -y 143 | dnf install -y rpmdevtools rpm-sign 'dnf-command(builddep)' 144 | dnf builddep -y pkg/fedora/surface-dtx-daemon.spec 145 | 146 | - name: Build package 147 | run: | 148 | cd pkg/fedora 149 | # Build the .rpm packages 150 | ./makerpm 151 | 152 | - name: Sign packages 153 | env: 154 | GPG_KEY_ID: 56C464BAAC421453 155 | GPG_KEY: ${{ secrets.LINUX_SURFACE_GPG_KEY }} 156 | run: | 157 | cd pkg/fedora/out/x86_64 158 | 159 | # import GPG key 160 | echo "$GPG_KEY" | base64 -d | gpg --import --no-tty --batch --yes 161 | 162 | # sign package 163 | rpm --resign *.rpm --define "_gpg_name $GPG_KEY_ID" 164 | 165 | - name: Upload artifacts 166 | uses: actions/upload-artifact@v4 167 | with: 168 | name: fedora-40-latest 169 | path: pkg/fedora/out/x86_64 170 | 171 | build-f41: 172 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') 173 | 174 | name: Build Fedora 41 package 175 | runs-on: ubuntu-latest 176 | needs: [lint, test] 177 | container: 178 | image: registry.fedoraproject.org/fedora:41 179 | options: --security-opt seccomp=unconfined 180 | 181 | steps: 182 | - name: Checkout code 183 | uses: actions/checkout@v4 184 | 185 | - name: Install build dependencies 186 | run: | 187 | dnf distro-sync -y 188 | dnf install -y rpmdevtools rpm-sign 'dnf-command(builddep)' 189 | dnf builddep -y pkg/fedora/surface-dtx-daemon.spec 190 | 191 | - name: Build package 192 | run: | 193 | cd pkg/fedora 194 | # Build the .rpm packages 195 | ./makerpm 196 | 197 | - name: Sign packages 198 | env: 199 | GPG_KEY_ID: 56C464BAAC421453 200 | GPG_KEY: ${{ secrets.LINUX_SURFACE_GPG_KEY }} 201 | run: | 202 | cd pkg/fedora/out/x86_64 203 | 204 | # import GPG key 205 | echo "$GPG_KEY" | base64 -d | gpg --import --no-tty --batch --yes 206 | 207 | # sign package 208 | rpm --resign *.rpm --define "_gpg_name $GPG_KEY_ID" 209 | 210 | - name: Upload artifacts 211 | uses: actions/upload-artifact@v4 212 | with: 213 | name: fedora-41-latest 214 | path: pkg/fedora/out/x86_64 215 | 216 | build-f42: 217 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') 218 | 219 | name: Build Fedora 42 package 220 | runs-on: ubuntu-latest 221 | needs: [lint, test] 222 | container: 223 | image: registry.fedoraproject.org/fedora:42 224 | options: --security-opt seccomp=unconfined 225 | 226 | steps: 227 | - name: Checkout code 228 | uses: actions/checkout@v4 229 | 230 | - name: Install build dependencies 231 | run: | 232 | dnf distro-sync -y 233 | dnf install -y rpmdevtools rpm-sign 'dnf-command(builddep)' 234 | dnf builddep -y pkg/fedora/surface-dtx-daemon.spec 235 | 236 | - name: Build package 237 | run: | 238 | cd pkg/fedora 239 | # Build the .rpm packages 240 | ./makerpm 241 | 242 | - name: Sign packages 243 | env: 244 | GPG_KEY_ID: 56C464BAAC421453 245 | GPG_KEY: ${{ secrets.LINUX_SURFACE_GPG_KEY }} 246 | run: | 247 | cd pkg/fedora/out/x86_64 248 | 249 | # import GPG key 250 | echo "$GPG_KEY" | base64 -d | gpg --import --no-tty --batch --yes 251 | 252 | # sign package 253 | rpm --resign *.rpm --define "_gpg_name $GPG_KEY_ID" 254 | 255 | - name: Upload artifacts 256 | uses: actions/upload-artifact@v4 257 | with: 258 | name: fedora-42-latest 259 | path: pkg/fedora/out/x86_64 260 | 261 | release: 262 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') 263 | 264 | name: Publish release 265 | needs: [build-bin, build-deb, build-f40, build-f41, build-f42] 266 | runs-on: ubuntu-latest 267 | 268 | steps: 269 | - name: Download binary artifacts 270 | uses: actions/download-artifact@v4 271 | with: 272 | name: binary-latest 273 | path: binary-latest 274 | 275 | - name: Download Debian artifacts 276 | uses: actions/download-artifact@v4 277 | with: 278 | name: debian-latest 279 | path: debian-latest 280 | 281 | - name: Download Fedora 40 artifacts 282 | uses: actions/download-artifact@v4 283 | with: 284 | name: fedora-40-latest 285 | path: fedora-40-latest 286 | 287 | - name: Download Fedora 41 artifacts 288 | uses: actions/download-artifact@v4 289 | with: 290 | name: fedora-41-latest 291 | path: fedora-41-latest 292 | 293 | - name: Download Fedora 42 artifacts 294 | uses: actions/download-artifact@v4 295 | with: 296 | name: fedora-42-latest 297 | path: fedora-42-latest 298 | 299 | - name: Upload assets 300 | uses: svenstaro/upload-release-action@v2 301 | with: 302 | repo_token: ${{ secrets.GITHUB_TOKEN }} 303 | file: ./*-latest/* 304 | tag: ${{ github.ref }} 305 | overwrite: true 306 | file_glob: true 307 | 308 | repo-deb: 309 | name: Update Debian package repository 310 | needs: [release] 311 | runs-on: ubuntu-latest 312 | container: debian:sid 313 | steps: 314 | - name: Install dependencies 315 | run: | 316 | apt-get update 317 | apt-get install -y git 318 | 319 | - name: Download artifacts 320 | uses: actions/download-artifact@v4 321 | with: 322 | name: debian-latest 323 | path: debian-latest 324 | 325 | - name: Update repository 326 | env: 327 | SURFACEBOT_TOKEN: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} 328 | BRANCH_STAGING: u/staging 329 | GIT_REF: ${{ github.ref }} 330 | run: | 331 | repo="https://surfacebot:${SURFACEBOT_TOKEN}@github.com/linux-surface/repo.git" 332 | 333 | # clone package repository 334 | git clone -b "${BRANCH_STAGING}" "${repo}" repo 335 | 336 | # copy packages 337 | cp debian-latest/* repo/debian/ 338 | cd repo/debian 339 | 340 | # parse git tag from ref 341 | GIT_TAG=$(echo $GIT_REF | sed 's|^refs/tags/||g') 342 | 343 | # convert packages into references 344 | for pkg in $(find . -name '*.deb'); do 345 | echo "surface-dtx-daemon:$GIT_TAG/$(basename $pkg)" > $pkg.blob 346 | rm $pkg 347 | done 348 | 349 | # set git identity 350 | git config --global user.email "surfacebot@users.noreply.github.com" 351 | git config --global user.name "surfacebot" 352 | 353 | # commit and push 354 | update_branch="${BRANCH_STAGING}-$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)" 355 | git switch -c "${update_branch}" 356 | git add . 357 | git commit -m "Update Debian DTX daemon" 358 | git push --set-upstream origin "${update_branch}" 359 | 360 | repo-f40: 361 | name: Update Fedora 40 package repository 362 | needs: [release] 363 | runs-on: ubuntu-latest 364 | container: 365 | image: registry.fedoraproject.org/fedora:40 366 | options: --security-opt seccomp=unconfined 367 | steps: 368 | - name: Install dependencies 369 | run: | 370 | dnf install -y git findutils 371 | 372 | - name: Download artifacts 373 | uses: actions/download-artifact@v4 374 | with: 375 | name: fedora-40-latest 376 | path: fedora-40-latest 377 | 378 | - name: Update repository 379 | env: 380 | SURFACEBOT_TOKEN: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} 381 | BRANCH_STAGING: u/staging 382 | GIT_REF: ${{ github.ref }} 383 | run: | 384 | repo="https://surfacebot:${SURFACEBOT_TOKEN}@github.com/linux-surface/repo.git" 385 | 386 | # clone package repository 387 | git clone -b "${BRANCH_STAGING}" "${repo}" repo 388 | 389 | # copy packages 390 | cp fedora-40-latest/* repo/fedora/f40 391 | cd repo/fedora/f40 392 | 393 | # parse git tag from ref 394 | GIT_TAG=$(echo $GIT_REF | sed 's|^refs/tags/||g') 395 | 396 | # convert packages into references 397 | for pkg in $(find . -name '*.rpm'); do 398 | echo "surface-dtx-daemon:$GIT_TAG/$(basename $pkg)" > $pkg.blob 399 | rm $pkg 400 | done 401 | 402 | # set git identity 403 | git config --global user.email "surfacebot@users.noreply.github.com" 404 | git config --global user.name "surfacebot" 405 | 406 | # commit and push 407 | update_branch="${BRANCH_STAGING}-$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)" 408 | git checkout -b "${update_branch}" 409 | git add . 410 | git commit -m "Update Fedora 40 DTX daemon" 411 | git push --set-upstream origin "${update_branch}" 412 | 413 | repo-f41: 414 | name: Update Fedora 41 package repository 415 | needs: [release] 416 | runs-on: ubuntu-latest 417 | container: 418 | image: registry.fedoraproject.org/fedora:41 419 | options: --security-opt seccomp=unconfined 420 | steps: 421 | - name: Install dependencies 422 | run: | 423 | dnf install -y git findutils 424 | 425 | - name: Download artifacts 426 | uses: actions/download-artifact@v4 427 | with: 428 | name: fedora-41-latest 429 | path: fedora-41-latest 430 | 431 | - name: Update repository 432 | env: 433 | SURFACEBOT_TOKEN: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} 434 | BRANCH_STAGING: u/staging 435 | GIT_REF: ${{ github.ref }} 436 | run: | 437 | repo="https://surfacebot:${SURFACEBOT_TOKEN}@github.com/linux-surface/repo.git" 438 | 439 | # clone package repository 440 | git clone -b "${BRANCH_STAGING}" "${repo}" repo 441 | 442 | # copy packages 443 | cp fedora-41-latest/* repo/fedora/f41 444 | cd repo/fedora/f41 445 | 446 | # parse git tag from ref 447 | GIT_TAG=$(echo $GIT_REF | sed 's|^refs/tags/||g') 448 | 449 | # convert packages into references 450 | for pkg in $(find . -name '*.rpm'); do 451 | echo "surface-dtx-daemon:$GIT_TAG/$(basename $pkg)" > $pkg.blob 452 | rm $pkg 453 | done 454 | 455 | # set git identity 456 | git config --global user.email "surfacebot@users.noreply.github.com" 457 | git config --global user.name "surfacebot" 458 | 459 | # commit and push 460 | update_branch="${BRANCH_STAGING}-$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)" 461 | git checkout -b "${update_branch}" 462 | git add . 463 | git commit -m "Update Fedora 41 DTX daemon" 464 | git push --set-upstream origin "${update_branch}" 465 | 466 | repo-f42: 467 | name: Update Fedora 42 package repository 468 | needs: [release] 469 | runs-on: ubuntu-latest 470 | container: 471 | image: registry.fedoraproject.org/fedora:42 472 | options: --security-opt seccomp=unconfined 473 | steps: 474 | - name: Install dependencies 475 | run: | 476 | dnf install -y git findutils 477 | 478 | - name: Download artifacts 479 | uses: actions/download-artifact@v4 480 | with: 481 | name: fedora-42-latest 482 | path: fedora-42-latest 483 | 484 | - name: Update repository 485 | env: 486 | SURFACEBOT_TOKEN: ${{ secrets.LINUX_SURFACE_BOT_TOKEN }} 487 | BRANCH_STAGING: u/staging 488 | GIT_REF: ${{ github.ref }} 489 | run: | 490 | repo="https://surfacebot:${SURFACEBOT_TOKEN}@github.com/linux-surface/repo.git" 491 | 492 | # clone package repository 493 | git clone -b "${BRANCH_STAGING}" "${repo}" repo 494 | 495 | # copy packages 496 | cp fedora-42-latest/* repo/fedora/f42 497 | cd repo/fedora/f42 498 | 499 | # parse git tag from ref 500 | GIT_TAG=$(echo $GIT_REF | sed 's|^refs/tags/||g') 501 | 502 | # convert packages into references 503 | for pkg in $(find . -name '*.rpm'); do 504 | echo "surface-dtx-daemon:$GIT_TAG/$(basename $pkg)" > $pkg.blob 505 | rm $pkg 506 | done 507 | 508 | # set git identity 509 | git config --global user.email "surfacebot@users.noreply.github.com" 510 | git config --global user.name "surfacebot" 511 | 512 | # commit and push 513 | update_branch="${BRANCH_STAGING}-$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)" 514 | git checkout -b "${update_branch}" 515 | git add . 516 | git commit -m "Update Fedora 42 DTX daemon" 517 | git push --set-upstream origin "${update_branch}" 518 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys 0.59.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 73 | dependencies = [ 74 | "anstyle", 75 | "once_cell", 76 | "windows-sys 0.59.0", 77 | ] 78 | 79 | [[package]] 80 | name = "anyhow" 81 | version = "1.0.98" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 84 | 85 | [[package]] 86 | name = "autocfg" 87 | version = "1.4.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 90 | 91 | [[package]] 92 | name = "backtrace" 93 | version = "0.3.74" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 96 | dependencies = [ 97 | "addr2line", 98 | "cfg-if", 99 | "libc", 100 | "miniz_oxide", 101 | "object", 102 | "rustc-demangle", 103 | "windows-targets", 104 | ] 105 | 106 | [[package]] 107 | name = "bitflags" 108 | version = "2.9.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 111 | 112 | [[package]] 113 | name = "bytes" 114 | version = "1.10.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 117 | 118 | [[package]] 119 | name = "cfg-if" 120 | version = "1.0.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 123 | 124 | [[package]] 125 | name = "cfg_aliases" 126 | version = "0.2.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 129 | 130 | [[package]] 131 | name = "clap" 132 | version = "4.5.37" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" 135 | dependencies = [ 136 | "clap_builder", 137 | ] 138 | 139 | [[package]] 140 | name = "clap_builder" 141 | version = "4.5.37" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" 144 | dependencies = [ 145 | "anstream", 146 | "anstyle", 147 | "clap_lex", 148 | "strsim", 149 | ] 150 | 151 | [[package]] 152 | name = "clap_complete" 153 | version = "4.5.47" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "c06f5378ea264ad4f82bbc826628b5aad714a75abf6ece087e923010eb937fb6" 156 | dependencies = [ 157 | "clap", 158 | ] 159 | 160 | [[package]] 161 | name = "clap_lex" 162 | version = "0.7.4" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 165 | 166 | [[package]] 167 | name = "colorchoice" 168 | version = "1.0.3" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 171 | 172 | [[package]] 173 | name = "dbus" 174 | version = "0.9.7" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" 177 | dependencies = [ 178 | "futures-channel", 179 | "futures-util", 180 | "libc", 181 | "libdbus-sys", 182 | "winapi", 183 | ] 184 | 185 | [[package]] 186 | name = "dbus-crossroads" 187 | version = "0.5.2" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "3a4c83437187544ba5142427746835061b330446ca8902eabd70e4afb8f76de0" 190 | dependencies = [ 191 | "dbus", 192 | ] 193 | 194 | [[package]] 195 | name = "dbus-tokio" 196 | version = "0.7.6" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "007688d459bc677131c063a3a77fb899526e17b7980f390b69644bdbc41fad13" 199 | dependencies = [ 200 | "dbus", 201 | "libc", 202 | "tokio", 203 | ] 204 | 205 | [[package]] 206 | name = "equivalent" 207 | version = "1.0.2" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 210 | 211 | [[package]] 212 | name = "futures" 213 | version = "0.3.31" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 216 | dependencies = [ 217 | "futures-channel", 218 | "futures-core", 219 | "futures-executor", 220 | "futures-io", 221 | "futures-sink", 222 | "futures-task", 223 | "futures-util", 224 | ] 225 | 226 | [[package]] 227 | name = "futures-channel" 228 | version = "0.3.31" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 231 | dependencies = [ 232 | "futures-core", 233 | "futures-sink", 234 | ] 235 | 236 | [[package]] 237 | name = "futures-core" 238 | version = "0.3.31" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 241 | 242 | [[package]] 243 | name = "futures-executor" 244 | version = "0.3.31" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 247 | dependencies = [ 248 | "futures-core", 249 | "futures-task", 250 | "futures-util", 251 | ] 252 | 253 | [[package]] 254 | name = "futures-io" 255 | version = "0.3.31" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 258 | 259 | [[package]] 260 | name = "futures-macro" 261 | version = "0.3.31" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 264 | dependencies = [ 265 | "proc-macro2", 266 | "quote", 267 | "syn", 268 | ] 269 | 270 | [[package]] 271 | name = "futures-sink" 272 | version = "0.3.31" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 275 | 276 | [[package]] 277 | name = "futures-task" 278 | version = "0.3.31" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 281 | 282 | [[package]] 283 | name = "futures-util" 284 | version = "0.3.31" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 287 | dependencies = [ 288 | "futures-channel", 289 | "futures-core", 290 | "futures-io", 291 | "futures-macro", 292 | "futures-sink", 293 | "futures-task", 294 | "memchr", 295 | "pin-project-lite", 296 | "pin-utils", 297 | "slab", 298 | ] 299 | 300 | [[package]] 301 | name = "gimli" 302 | version = "0.31.1" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 305 | 306 | [[package]] 307 | name = "hashbrown" 308 | version = "0.15.2" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 311 | 312 | [[package]] 313 | name = "indexmap" 314 | version = "2.9.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 317 | dependencies = [ 318 | "equivalent", 319 | "hashbrown", 320 | ] 321 | 322 | [[package]] 323 | name = "is_terminal_polyfill" 324 | version = "1.70.1" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 327 | 328 | [[package]] 329 | name = "lazy_static" 330 | version = "1.5.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 333 | 334 | [[package]] 335 | name = "libc" 336 | version = "0.2.172" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 339 | 340 | [[package]] 341 | name = "libdbus-sys" 342 | version = "0.2.5" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" 345 | dependencies = [ 346 | "pkg-config", 347 | ] 348 | 349 | [[package]] 350 | name = "log" 351 | version = "0.4.27" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 354 | 355 | [[package]] 356 | name = "matchers" 357 | version = "0.1.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 360 | dependencies = [ 361 | "regex-automata 0.1.10", 362 | ] 363 | 364 | [[package]] 365 | name = "memchr" 366 | version = "2.7.4" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 369 | 370 | [[package]] 371 | name = "miniz_oxide" 372 | version = "0.8.8" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 375 | dependencies = [ 376 | "adler2", 377 | ] 378 | 379 | [[package]] 380 | name = "mio" 381 | version = "1.0.3" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 384 | dependencies = [ 385 | "libc", 386 | "wasi", 387 | "windows-sys 0.52.0", 388 | ] 389 | 390 | [[package]] 391 | name = "nix" 392 | version = "0.29.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 395 | dependencies = [ 396 | "bitflags", 397 | "cfg-if", 398 | "cfg_aliases", 399 | "libc", 400 | ] 401 | 402 | [[package]] 403 | name = "nu-ansi-term" 404 | version = "0.46.0" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 407 | dependencies = [ 408 | "overload", 409 | "winapi", 410 | ] 411 | 412 | [[package]] 413 | name = "object" 414 | version = "0.36.7" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 417 | dependencies = [ 418 | "memchr", 419 | ] 420 | 421 | [[package]] 422 | name = "once_cell" 423 | version = "1.21.3" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 426 | 427 | [[package]] 428 | name = "overload" 429 | version = "0.1.1" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 432 | 433 | [[package]] 434 | name = "pin-project-lite" 435 | version = "0.2.16" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 438 | 439 | [[package]] 440 | name = "pin-utils" 441 | version = "0.1.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 444 | 445 | [[package]] 446 | name = "pkg-config" 447 | version = "0.3.32" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 450 | 451 | [[package]] 452 | name = "proc-macro2" 453 | version = "1.0.95" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 456 | dependencies = [ 457 | "unicode-ident", 458 | ] 459 | 460 | [[package]] 461 | name = "quote" 462 | version = "1.0.40" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 465 | dependencies = [ 466 | "proc-macro2", 467 | ] 468 | 469 | [[package]] 470 | name = "regex" 471 | version = "1.11.1" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 474 | dependencies = [ 475 | "aho-corasick", 476 | "memchr", 477 | "regex-automata 0.4.9", 478 | "regex-syntax 0.8.5", 479 | ] 480 | 481 | [[package]] 482 | name = "regex-automata" 483 | version = "0.1.10" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 486 | dependencies = [ 487 | "regex-syntax 0.6.29", 488 | ] 489 | 490 | [[package]] 491 | name = "regex-automata" 492 | version = "0.4.9" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 495 | dependencies = [ 496 | "aho-corasick", 497 | "memchr", 498 | "regex-syntax 0.8.5", 499 | ] 500 | 501 | [[package]] 502 | name = "regex-syntax" 503 | version = "0.6.29" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 506 | 507 | [[package]] 508 | name = "regex-syntax" 509 | version = "0.8.5" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 512 | 513 | [[package]] 514 | name = "rustc-demangle" 515 | version = "0.1.24" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 518 | 519 | [[package]] 520 | name = "sdtx" 521 | version = "0.1.6" 522 | source = "git+https://github.com/linux-surface/libsurfacedtx?tag=v0.1.6#940c9a0cade870218ef8fee5f16df1eef8853c44" 523 | dependencies = [ 524 | "futures", 525 | "nix", 526 | "smallvec", 527 | "thiserror", 528 | "tracing", 529 | ] 530 | 531 | [[package]] 532 | name = "sdtx-tokio" 533 | version = "0.1.6" 534 | source = "git+https://github.com/linux-surface/libsurfacedtx?tag=v0.1.6#940c9a0cade870218ef8fee5f16df1eef8853c44" 535 | dependencies = [ 536 | "futures", 537 | "sdtx", 538 | "tokio", 539 | ] 540 | 541 | [[package]] 542 | name = "serde" 543 | version = "1.0.219" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 546 | dependencies = [ 547 | "serde_derive", 548 | ] 549 | 550 | [[package]] 551 | name = "serde_derive" 552 | version = "1.0.219" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 555 | dependencies = [ 556 | "proc-macro2", 557 | "quote", 558 | "syn", 559 | ] 560 | 561 | [[package]] 562 | name = "serde_ignored" 563 | version = "0.1.11" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "566da67d80e92e009728b3731ff0e5360cb181432b8ca73ea30bb1d170700d76" 566 | dependencies = [ 567 | "serde", 568 | ] 569 | 570 | [[package]] 571 | name = "serde_spanned" 572 | version = "0.6.8" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 575 | dependencies = [ 576 | "serde", 577 | ] 578 | 579 | [[package]] 580 | name = "sharded-slab" 581 | version = "0.1.7" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 584 | dependencies = [ 585 | "lazy_static", 586 | ] 587 | 588 | [[package]] 589 | name = "signal-hook-registry" 590 | version = "1.4.4" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "a1ee1aca2bc74ef9589efa7ccaa0f3752751399940356209b3fd80c078149b5e" 593 | dependencies = [ 594 | "libc", 595 | ] 596 | 597 | [[package]] 598 | name = "slab" 599 | version = "0.4.9" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 602 | dependencies = [ 603 | "autocfg", 604 | ] 605 | 606 | [[package]] 607 | name = "smallvec" 608 | version = "1.15.0" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 611 | 612 | [[package]] 613 | name = "socket2" 614 | version = "0.5.9" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" 617 | dependencies = [ 618 | "libc", 619 | "windows-sys 0.52.0", 620 | ] 621 | 622 | [[package]] 623 | name = "strsim" 624 | version = "0.11.1" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 627 | 628 | [[package]] 629 | name = "surface-dtx-daemon" 630 | version = "0.3.9" 631 | dependencies = [ 632 | "anyhow", 633 | "clap", 634 | "clap_complete", 635 | "dbus", 636 | "dbus-crossroads", 637 | "dbus-tokio", 638 | "futures", 639 | "libc", 640 | "nix", 641 | "sdtx", 642 | "sdtx-tokio", 643 | "serde", 644 | "serde_ignored", 645 | "tokio", 646 | "toml", 647 | "tracing", 648 | "tracing-subscriber", 649 | ] 650 | 651 | [[package]] 652 | name = "surface-dtx-userd" 653 | version = "0.3.9" 654 | dependencies = [ 655 | "anyhow", 656 | "clap", 657 | "clap_complete", 658 | "dbus", 659 | "dbus-tokio", 660 | "futures", 661 | "serde", 662 | "serde_ignored", 663 | "tokio", 664 | "toml", 665 | "tracing", 666 | "tracing-subscriber", 667 | ] 668 | 669 | [[package]] 670 | name = "syn" 671 | version = "2.0.100" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 674 | dependencies = [ 675 | "proc-macro2", 676 | "quote", 677 | "unicode-ident", 678 | ] 679 | 680 | [[package]] 681 | name = "thiserror" 682 | version = "2.0.12" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 685 | dependencies = [ 686 | "thiserror-impl", 687 | ] 688 | 689 | [[package]] 690 | name = "thiserror-impl" 691 | version = "2.0.12" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 694 | dependencies = [ 695 | "proc-macro2", 696 | "quote", 697 | "syn", 698 | ] 699 | 700 | [[package]] 701 | name = "thread_local" 702 | version = "1.1.8" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 705 | dependencies = [ 706 | "cfg-if", 707 | "once_cell", 708 | ] 709 | 710 | [[package]] 711 | name = "tokio" 712 | version = "1.44.2" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" 715 | dependencies = [ 716 | "backtrace", 717 | "bytes", 718 | "libc", 719 | "mio", 720 | "pin-project-lite", 721 | "signal-hook-registry", 722 | "socket2", 723 | "tokio-macros", 724 | "windows-sys 0.52.0", 725 | ] 726 | 727 | [[package]] 728 | name = "tokio-macros" 729 | version = "2.5.0" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 732 | dependencies = [ 733 | "proc-macro2", 734 | "quote", 735 | "syn", 736 | ] 737 | 738 | [[package]] 739 | name = "toml" 740 | version = "0.8.20" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" 743 | dependencies = [ 744 | "serde", 745 | "serde_spanned", 746 | "toml_datetime", 747 | "toml_edit", 748 | ] 749 | 750 | [[package]] 751 | name = "toml_datetime" 752 | version = "0.6.8" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 755 | dependencies = [ 756 | "serde", 757 | ] 758 | 759 | [[package]] 760 | name = "toml_edit" 761 | version = "0.22.24" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" 764 | dependencies = [ 765 | "indexmap", 766 | "serde", 767 | "serde_spanned", 768 | "toml_datetime", 769 | "winnow", 770 | ] 771 | 772 | [[package]] 773 | name = "tracing" 774 | version = "0.1.41" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 777 | dependencies = [ 778 | "pin-project-lite", 779 | "tracing-attributes", 780 | "tracing-core", 781 | ] 782 | 783 | [[package]] 784 | name = "tracing-attributes" 785 | version = "0.1.28" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 788 | dependencies = [ 789 | "proc-macro2", 790 | "quote", 791 | "syn", 792 | ] 793 | 794 | [[package]] 795 | name = "tracing-core" 796 | version = "0.1.33" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 799 | dependencies = [ 800 | "once_cell", 801 | "valuable", 802 | ] 803 | 804 | [[package]] 805 | name = "tracing-log" 806 | version = "0.2.0" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 809 | dependencies = [ 810 | "log", 811 | "once_cell", 812 | "tracing-core", 813 | ] 814 | 815 | [[package]] 816 | name = "tracing-subscriber" 817 | version = "0.3.19" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 820 | dependencies = [ 821 | "matchers", 822 | "nu-ansi-term", 823 | "once_cell", 824 | "regex", 825 | "sharded-slab", 826 | "smallvec", 827 | "thread_local", 828 | "tracing", 829 | "tracing-core", 830 | "tracing-log", 831 | ] 832 | 833 | [[package]] 834 | name = "unicode-ident" 835 | version = "1.0.18" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 838 | 839 | [[package]] 840 | name = "utf8parse" 841 | version = "0.2.2" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 844 | 845 | [[package]] 846 | name = "valuable" 847 | version = "0.1.1" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 850 | 851 | [[package]] 852 | name = "wasi" 853 | version = "0.11.0+wasi-snapshot-preview1" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 856 | 857 | [[package]] 858 | name = "winapi" 859 | version = "0.3.9" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 862 | dependencies = [ 863 | "winapi-i686-pc-windows-gnu", 864 | "winapi-x86_64-pc-windows-gnu", 865 | ] 866 | 867 | [[package]] 868 | name = "winapi-i686-pc-windows-gnu" 869 | version = "0.4.0" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 872 | 873 | [[package]] 874 | name = "winapi-x86_64-pc-windows-gnu" 875 | version = "0.4.0" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 878 | 879 | [[package]] 880 | name = "windows-sys" 881 | version = "0.52.0" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 884 | dependencies = [ 885 | "windows-targets", 886 | ] 887 | 888 | [[package]] 889 | name = "windows-sys" 890 | version = "0.59.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 893 | dependencies = [ 894 | "windows-targets", 895 | ] 896 | 897 | [[package]] 898 | name = "windows-targets" 899 | version = "0.52.6" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 902 | dependencies = [ 903 | "windows_aarch64_gnullvm", 904 | "windows_aarch64_msvc", 905 | "windows_i686_gnu", 906 | "windows_i686_gnullvm", 907 | "windows_i686_msvc", 908 | "windows_x86_64_gnu", 909 | "windows_x86_64_gnullvm", 910 | "windows_x86_64_msvc", 911 | ] 912 | 913 | [[package]] 914 | name = "windows_aarch64_gnullvm" 915 | version = "0.52.6" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 918 | 919 | [[package]] 920 | name = "windows_aarch64_msvc" 921 | version = "0.52.6" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 924 | 925 | [[package]] 926 | name = "windows_i686_gnu" 927 | version = "0.52.6" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 930 | 931 | [[package]] 932 | name = "windows_i686_gnullvm" 933 | version = "0.52.6" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 936 | 937 | [[package]] 938 | name = "windows_i686_msvc" 939 | version = "0.52.6" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 942 | 943 | [[package]] 944 | name = "windows_x86_64_gnu" 945 | version = "0.52.6" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 948 | 949 | [[package]] 950 | name = "windows_x86_64_gnullvm" 951 | version = "0.52.6" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 954 | 955 | [[package]] 956 | name = "windows_x86_64_msvc" 957 | version = "0.52.6" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 960 | 961 | [[package]] 962 | name = "winnow" 963 | version = "0.7.6" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" 966 | dependencies = [ 967 | "memchr", 968 | ] 969 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "surface-dtx-daemon", 5 | "surface-dtx-userd", 6 | ] 7 | 8 | [profile.release] 9 | lto = true 10 | codegen-units = 1 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Maximilian Luz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linux DTX Daemon for Surface Books 2 | 3 | ![CI](https://github.com/linux-surface/surface-dtx-daemon/workflows/CI/badge.svg) 4 | 5 | Linux User-Space Detachment System (DTX) Daemon for the Surface ACPI Driver (and Surface Books). 6 | Currently, only the Surface Book 2 and 3 are supported, due to lack of driver-support on the Surface Book 1. 7 | This may change in the future. 8 | 9 | ## About this Package 10 | 11 | This package contains two daemons. 12 | A system daemon (`surface-dtx-daemon`) and a per-user daemon (`surface-dtx-userd`): 13 | 14 | - The system daemon allows proper clipboard detachment on the Surface Book 2 and 3. It allows you to run commands before the clipboard is unlocked, after it has been re-attached, or when the unlocking-process has been aborted (e.g. by pressing the detach-button a second time). 15 | See the configuration section below for details. 16 | Furthermore, this daemon provides a d-bus interface via which you can query the current device mode (i.e. if the device is in tablet-, laptop- or studio-mode). 17 | 18 | - The per-user daemon is responsible for desktop-notifications, i.e. it notifies you when the clipboard can be physically detached (i.e. the latch holding it in place is unlocked), and when the re-attachment process has been completed, i.e. indicating when it is fully usable again after re-attachment. 19 | Running this daemon is completely optional, i.e. if you don't want any notifications, you are free to simply not run it. 20 | 21 | The split into two daemons is required as notifications can only be sent on a per-user basis. 22 | 23 | ## Installation 24 | 25 | If you have a Debian (Ubuntu, ...) based distribution, have a look at the [releases page][releases] for official packages. 26 | Official Arch Linux packages can be found in the AUR (`surface-dtx-daemon`). 27 | After installation, you may want to: 28 | - enable the systemd service for the system daemon using `systemctl enable surface-dtx-daemon.service`. 29 | - enable the systemd service for the per-user daemon using `systemctl enable --user surface-dtx-userd.service`. 30 | 31 | Alternatively, you can build these packages yourself, using the provided `PKGBUILD` (Arch Linux) or `makedeb.sh` script in the respective `pkg` subdirectories. 32 | 33 | ## Configuration 34 | 35 | The main configuration files can be found under 36 | 37 | - `/etc/surface-dtx/surface-dtx-daemon.conf` for the system daemon configuration, and 38 | - `/etc/surface-dtx/surface-dtx-userd.conf` for the per-user daemon configuration. 39 | 40 | Here you can specify the handler-scripts for supported events and other options. 41 | All options are explained in these files, the configuration language is TOML, default attach and detach handler scripts are included. 42 | 43 | Furthermore, a per-user configuration for the user daemon can also be created under `$XDG_CONFIG_HOME/surface-dtx/surface-dtx-userd.conf` (if not set, `$XDG_CONFIG_HOME` defaults to `.config`). 44 | 45 | ## Building a Package from Source 46 | 47 | ### Arch Linux 48 | 49 | Simply install `surface-dtx-daemon` from AUR or have a look at its PKGBUILD. 50 | 51 | ### Debian/Ubuntu 52 | 53 | Use the `makedeb` script provided under `pkg/deb`, i.e. run 54 | ``` 55 | ./pkg/deb/makedeb 56 | ``` 57 | from the root project directory. 58 | You may need to install the `build-essential` and `devscripts` packages beforehand. 59 | The final package will be in the `pkg/deb` directory. 60 | 61 | 62 | [releases]: https://github.com/linux-surface/surface-dtx-daemon/releases 63 | -------------------------------------------------------------------------------- /doc/ec-flow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /doc/ec-user-signals.txt: -------------------------------------------------------------------------------- 1 | CURRENT STATE SIGNAL NEXT STATE EVENTS/EFFECT 2 | 3 | connected-closed request connected-closed-waiting request event 4 | connected-closed cancel connected-closed no event 5 | connected-closed confirm connected-closed no event 6 | connected-closed heartbeat connected-closed no event 7 | 8 | connected-closed-waiting request connected-closed request event 9 | connected-closed-waiting cancel connected-closed request event 10 | connected-closed-waiting confirm connected-opened opened event 11 | connected-closed-waiting heartbeat connected-closed-waiting no event, timeout is reset 12 | 13 | connected-opened request connected-opened-pending request event 14 | connected-opened cancel connected-opened-pending request event 15 | connected-opened confirm connected-opened no event 16 | connected-opened heartbeat connected-opened no event 17 | 18 | connected-opened-pending request connected-opened-pending no event 19 | connected-opened-pending cancel connected-opened-pending no event 20 | connected-opened-pending confirm connected-opened-pending no event 21 | connected-opened-pending heartbeat connected-opened-pending no event 22 | 23 | 24 | disconnected-closed request disconnected-closed-waiting request event 25 | disconnected-closed cancel disconnected-closed no event 26 | disconnected-closed confirm disconnected-closed no event 27 | disconnected-closed heartbeat disconnected-closed no event 28 | 29 | disconnected-closed-waiting request disconnected-closed request event 30 | disconnected-closed-waiting cancel disconnected-closed request event 31 | disconnected-closed-waiting confirm disconnected-opened opened event 32 | disconnected-closed-waiting heartbeat disconnected-closed-waiting no event, timeout is reset 33 | 34 | disconnected-opened request disconnected-opened-pending request event 35 | disconnected-opened cancel disconnected-opened-pending request event 36 | disconnected-opened confirm disconnected-opened no event 37 | disconnected-opened heartbeat disconnected-opened no event 38 | 39 | disconnected-opened-pending request disconnected-opened-pending no event 40 | disconnected-opened-pending cancel disconnected-opened-pending no event 41 | disconnected-opened-pending confirm disconnected-opened-pending no event 42 | disconnected-opened-pending heartbeat disconnected-opened-pending no event 43 | -------------------------------------------------------------------------------- /etc/dbus/org.surface.dtx.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | system 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /etc/dtx/attach.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # surface-dtx detachment handler 3 | -------------------------------------------------------------------------------- /etc/dtx/detach.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # surface-dtx detachment handler 3 | 4 | # unmount all USB devices 5 | for usb_dev in /dev/disk/by-id/usb-* 6 | do 7 | dev=$(readlink -f $usb_dev) 8 | mount -l | grep -q "^$dev\s" && umount "$dev" 9 | done 10 | 11 | # signal commence 12 | exit $EXIT_DETACH_COMMENCE 13 | # The exit signal determines the continuation of the detachment-procedure. A 14 | # value of EXIT_DETACH_COMMENCE (0/success), causes the detachment procedure 15 | # to open the latch, while a value of EXIT_DETACH_ABORT (1, or any other 16 | # non-zero value) will cause the detachment-procedure to be aborted. On an 17 | # abort caused by this script, the detach_abort handler will _not_ be 18 | # executed. It is therefore the the responsibility of this handler-executable 19 | # to ensure the device state is properly reset to the state before its 20 | # execution, if required. 21 | -------------------------------------------------------------------------------- /etc/dtx/surface-dtx-daemon.conf: -------------------------------------------------------------------------------- 1 | # Surface DTX System Daemon Configuration 2 | 3 | 4 | [log] 5 | # Log format options. 6 | 7 | level = "info" 8 | # The level used for logging. 9 | # Valid options are trace, debug, info, warning, error, and critical. 10 | 11 | 12 | [handler] 13 | # Event handler scripts. 14 | # All paths are relative to this file. 15 | 16 | [handler.detach] 17 | exec = "./detach.sh" 18 | # The executable to be executed before unlocking the clipboard. 19 | # If unspecified, no handler will be executed. 20 | 21 | #timeout = 22 | # Timeout for the executable, after which it will be killed. 23 | # Defaults to 60 seconds. 24 | 25 | [handler.detach_abort] 26 | exec = "./attach.sh" 27 | # The executable to be executed after the detach-process has been aborted. 28 | # This script will be executed only after completion of the detach script. 29 | # If unspecified, no handler will be executed. 30 | 31 | #timeout = 32 | # Timeout for the executable, after which it will be killed. 33 | # Defaults to 60 seconds. 34 | 35 | [handler.attach] 36 | exec = "./attach.sh" 37 | # The executable to be executed after the clipboard has been attached. 38 | # Before execution, the delay specified in delay.attach will be waited to 39 | # allow for all devices to be set up correctly. 40 | # If unspecified, no handler will be executed. 41 | 42 | #timeout = 43 | # Timeout for the executable, after which it will be killed. 44 | # Defaults to 60 seconds. 45 | 46 | #delay = 47 | # The delay in seconds to wait before executing the attach handler. 48 | # Defaults to 5 (seconds). 49 | -------------------------------------------------------------------------------- /etc/dtx/surface-dtx-userd.conf: -------------------------------------------------------------------------------- 1 | # Surface DTX User Daemon Configuration 2 | # Use $XDG_CONFIG_HOME/surface-dtx/surface-dtx-userd.conf for per-user configuration. 3 | 4 | 5 | [log] 6 | # Log format options. 7 | 8 | level = "info" 9 | # The level used for logging. 10 | # Valid options are trace, debug, info, warning, error, and critical. 11 | -------------------------------------------------------------------------------- /etc/systemd/surface-dtx-daemon.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Surface Detachment System (DTX) Daemon 3 | Documentation=https://github.com/linux-surface/surface-dtx-daemon 4 | After=dev-surface-dtx.device 5 | Wants=dev-surface-dtx.device 6 | 7 | [Service] 8 | Type=simple 9 | ExecStart=/usr/bin/surface-dtx-daemon --no-log-time 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /etc/systemd/surface-dtx-userd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Surface Detachment System (DTX) User Daemon 3 | Documentation=https://github.com/linux-surface/surface-dtx-daemon 4 | After=basic.target 5 | ConditionUser=!root 6 | 7 | [Service] 8 | Type=simple 9 | ExecStart=/usr/bin/surface-dtx-userd --no-log-time 10 | 11 | [Install] 12 | WantedBy=default.target 13 | -------------------------------------------------------------------------------- /etc/udev/40-surface_dtx.rules: -------------------------------------------------------------------------------- 1 | KERNEL=="surface_dtx", TAG+="systemd" 2 | -------------------------------------------------------------------------------- /pkg/bin/.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | src/ 3 | *.tar 4 | *.tar.xz 5 | -------------------------------------------------------------------------------- /pkg/bin/makebin: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | pkgname="surface-dtx-daemon" 5 | pkgarch="x86_64" 6 | 7 | gitver=$(git describe --tags 2> /dev/null | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || true) 8 | civer=$(echo $TRAVIS_TAG | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g') 9 | 10 | pkgver=${civer:-${gitver:-0.0.0}} 11 | binpkg="$pkgname-$pkgver-$pkgarch.bin.tar.xz" 12 | 13 | branch="HEAD" 14 | basepath="$PWD/pkg/bin" 15 | srcdir="$basepath/src" 16 | pkgdir="$basepath/pkg" 17 | 18 | 19 | pushd() { 20 | command pushd "$@" > /dev/null 21 | } 22 | 23 | popd() { 24 | command popd "$@" > /dev/null 25 | } 26 | 27 | 28 | chkdir() { 29 | if [ ! -d ".git" ] 30 | then 31 | echo "Error: Script must be run from the root directory" 32 | exit 1 33 | fi 34 | } 35 | 36 | prepare() { 37 | archive="$basepath/src.tar" 38 | 39 | git archive --format tar "$branch" > "$archive" 40 | 41 | mkdir -p "$srcdir" 42 | tar xf "$archive" --directory "$srcdir" 43 | } 44 | 45 | build() { 46 | pushd "$srcdir" 47 | env CARGO_TARGET_DIR="$PWD/target" CARGO_INCREMENTAL=0 cargo build --release --locked 48 | strip --strip-all "target/release/surface-dtx-daemon" 49 | strip --strip-all "target/release/surface-dtx-userd" 50 | popd 51 | } 52 | 53 | package() { 54 | pushd "$srcdir" 55 | 56 | # clean package directory 57 | rm -rf "$pkgdir" 58 | mkdir -p "$pkgdir" 59 | 60 | # binary files 61 | install -D -m755 "target/release/surface-dtx-daemon" "$pkgdir/bin/surface-dtx-daemon" 62 | install -D -m755 "target/release/surface-dtx-userd" "$pkgdir/bin/surface-dtx-userd" 63 | 64 | # application files 65 | install -D -m644 "etc/dtx/surface-dtx-daemon.conf" "$pkgdir/surface-dtx/surface-dtx-daemon.conf" 66 | install -D -m644 "etc/dtx/surface-dtx-userd.conf" "$pkgdir/surface-dtx/surface-dtx-userd.conf" 67 | install -D -m755 "etc/dtx/attach.sh" "$pkgdir/surface-dtx/attach.sh" 68 | install -D -m755 "etc/dtx/detach.sh" "$pkgdir/surface-dtx/detach.sh" 69 | 70 | # systemd service files 71 | install -D -m644 "etc/systemd/surface-dtx-daemon.service" "$pkgdir/systemd/surface-dtx-daemon.service" 72 | install -D -m644 "etc/systemd/surface-dtx-userd.service" "$pkgdir/systemd/surface-dtx-userd.service" 73 | 74 | # dbus config file 75 | install -D -m644 "etc/dbus/org.surface.dtx.conf" "$pkgdir/dbus/org.surface.dtx.conf" 76 | 77 | # udev rules 78 | install -D -m644 "etc/udev/40-surface_dtx.rules" "$pkgdir/udev/40-surface_dtx.rules" 79 | 80 | # completion files 81 | install -D -m644 "target/surface-dtx-daemon.bash" "$pkgdir/shell-completions/surface-dtx-daemon.bash" 82 | install -D -m644 "target/surface-dtx-userd.bash" "$pkgdir/shell-completions/surface-dtx-userd.bash" 83 | install -D -m644 "target/_surface-dtx-daemon" "$pkgdir/shell-completions/surface-dtx-daemon.zsh" 84 | install -D -m644 "target/_surface-dtx-userd" "$pkgdir/shell-completions/surface-dtx-userd.zsh" 85 | install -D -m644 "target/surface-dtx-daemon.fish" "$pkgdir/shell-completions/surface-dtx-daemon.fish" 86 | install -D -m644 "target/surface-dtx-userd.fish" "$pkgdir/shell-completions/surface-dtx-userd.fish" 87 | 88 | # license 89 | install -D -m644 "LICENSE" "$pkgdir/LICENSE" 90 | 91 | # zip package 92 | tar -C "$pkgdir" -cJf "$basepath/$binpkg" . 93 | 94 | popd 95 | } 96 | 97 | 98 | chkdir 99 | prepare 100 | build 101 | package 102 | -------------------------------------------------------------------------------- /pkg/deb/.gitignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.tar 3 | *.buildinfo 4 | *.changes 5 | *.deb -------------------------------------------------------------------------------- /pkg/deb/debian/changelog: -------------------------------------------------------------------------------- 1 | surface-dtx-daemon (0.3.9-1) unstable; urgency=medium 2 | 3 | * Update dependencies 4 | 5 | -- Maximilian Luz Sat, 19 Arp 2025 20:42:03 +0200 6 | 7 | surface-dtx-daemon (0.3.8-1) unstable; urgency=medium 8 | 9 | * Update dependencies 10 | 11 | -- Maximilian Luz Sat, 14 Sep 2024 16:00:43 +0200 12 | 13 | surface-dtx-daemon (0.3.7-1) unstable; urgency=medium 14 | 15 | * Update dependencies 16 | 17 | -- Maximilian Luz Thu, 14 Mar 2024 23:34:21 +0200 18 | 19 | surface-dtx-daemon (0.3.6-2) unstable; urgency=medium 20 | 21 | * Bump release for Fedora 39 build 22 | 23 | -- Maximilian Luz Tue, 03 Oct 2023 16:40:38 +0200 24 | 25 | surface-dtx-daemon (0.3.6-1) unstable; urgency=medium 26 | 27 | * Update dependencies 28 | 29 | -- Maximilian Luz Tue, 03 Oct 2023 16:04:19 +0200 30 | 31 | surface-dtx-daemon (0.3.5-1) unstable; urgency=medium 32 | 33 | * Update dependencies 34 | * Remove dependency on unmaintained atty crate 35 | 36 | -- Maximilian Luz Tue, 11 Jul 2023 22:02:40 +0200 37 | 38 | surface-dtx-daemon (0.3.4-1) unstable; urgency=medium 39 | 40 | * Update dependencies 41 | 42 | -- Maximilian Luz Wed, 19 Apr 2023 16:09:13 +0200 43 | 44 | surface-dtx-daemon (0.3.3-2) unstable; urgency=medium 45 | 46 | * Bump release to build for Fedora 37 47 | 48 | -- Maximilian Luz Fri, 14 Oct 2022 21:12:25 +0200 49 | 50 | surface-dtx-daemon (0.3.3-1) unstable; urgency=medium 51 | 52 | * Update dependencies 53 | 54 | -- Maximilian Luz Sat, 08 Oct 2022 12:40:08 +0200 55 | 56 | surface-dtx-daemon (0.3.2-1) unstable; urgency=medium 57 | 58 | * Update dependencies 59 | 60 | -- Maximilian Luz Thu, 28 Apr 2022 02:00:32 +0200 61 | 62 | surface-dtx-daemon (0.3.1-3) unstable; urgency=medium 63 | 64 | * Bump release to build for Fedora 36 65 | 66 | -- Dorian Stoll Wed, 27 Apr 2022 20:05:00 +0200 67 | 68 | surface-dtx-daemon (0.3.1-2) unstable; urgency=medium 69 | 70 | * Bump release to build for Fedora 35 71 | 72 | -- Dorian Stoll Wed, 03 Nov 2021 19:48:00 +0100 73 | 74 | surface-dtx-daemon (0.3.1-1) unstable; urgency=medium 75 | 76 | * Fix typo causing the user-daemon to crash on latch error 77 | * Update dependencies 78 | 79 | -- Maximilian Luz Mon, 23 Aug 2021 02:09:22 +0200 80 | 81 | surface-dtx-daemon (0.3.0-1) unstable; urgency=medium 82 | 83 | * Properly forward hardware-errors, runtime-errors, and other notifications to user 84 | * Support DTX heartbeat commend 85 | * Support handler script timeouts 86 | * Various stability improvements 87 | 88 | -- Maximilian Luz Wed, 07 Apr 2021 03:48:02 +0200 89 | 90 | surface-dtx-daemon (0.2.0-2) unstable; urgency=medium 91 | 92 | * Bump release to build for Fedora 34 93 | 94 | -- Dorian Stoll Fri, 19 Mar 2021 08:06:24 +0100 95 | 96 | surface-dtx-daemon (0.2.0-1) unstable; urgency=medium 97 | 98 | * Update to new DTX kernel interface. 99 | * Update dependencies. 100 | 101 | -- Maximilian Luz Fri, 16 Oct 2020 20:09:24 +0200 102 | 103 | surface-dtx-daemon (0.1.5-2) unstable; urgency=medium 104 | 105 | * Bump release to build for Fedora 33 106 | 107 | -- Dorian Stoll Tue, 29 Sep 2020 18:47:56 +0200 108 | 109 | surface-dtx-daemon (0.1.5-1) unstable; urgency=medium 110 | 111 | * Update dependencies 112 | 113 | -- Maximilian Luz Sat, 04 Jul 2020 03:32:56 +0200 114 | 115 | surface-dtx-daemon (0.1.4-3) unstable; urgency=medium 116 | 117 | * Bump pkgrel 118 | 119 | -- Dorian Stoll Tue, 31 Mar 2020 13:56:21 +0200 120 | 121 | surface-dtx-daemon (0.1.4-2) unstable; urgency=medium 122 | 123 | * Bump pkgrel 124 | 125 | -- Maximilian Luz Tue, 18 Feb 2020 20:36:25 +0100 126 | 127 | surface-dtx-daemon (0.1.4) unstable; urgency=medium 128 | 129 | * Improve packaging strategy 130 | * Update third-party dependencies 131 | 132 | -- Maximilian Luz Mon, 09 Sep 2019 17:56:32 +0200 133 | 134 | surface-dtx-daemon (0.1.3) unstable; urgency=medium 135 | 136 | * Update third-party dependencies. 137 | 138 | -- Maximilian Luz Thu, 05 Sep 2019 02:45:46 +0200 139 | 140 | surface-dtx-daemon (0.1.2) unstable; urgency=medium 141 | 142 | * Add udev rule to ensure device is available. 143 | 144 | -- Maximilian Luz Thu, 09 May 2019 22:54:39 +0000 145 | 146 | surface-dtx-daemon (0.1.1) unstable; urgency=medium 147 | 148 | * Add request function to dbus interface. 149 | * Fix libc/libgcc dependencies. 150 | 151 | -- Maximilian Luz Wed, 08 May 2019 19:34:02 +0000 152 | 153 | surface-dtx-daemon (0.1.0) unstable; urgency=medium 154 | 155 | * Initial release. 156 | 157 | -- Maximilian Luz Sat, 20 Apr 2019 16:50:43 +0000 158 | -------------------------------------------------------------------------------- /pkg/deb/debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /pkg/deb/debian/control: -------------------------------------------------------------------------------- 1 | Source: surface-dtx-daemon 2 | Section: misc 3 | Priority: optional 4 | Maintainer: Maximilian Luz 5 | Build-Depends: build-essential, debhelper (>= 10), cargo, rustc (>= 1.34.0), libdbus-glib-1-dev 6 | 7 | Package: surface-dtx-daemon 8 | Architecture: amd64 9 | Depends: libc6 (>= 2.19), libgcc1 (>= 1:4.9.2), libdbus-1-3 10 | Description: Surface Detachment System (DTX) Daemon 11 | -------------------------------------------------------------------------------- /pkg/deb/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | export DH_VERBOSE = 1 3 | 4 | pkgdir = debian/surface-dtx-daemon 5 | 6 | clean: 7 | dh clean 8 | cargo clean 9 | 10 | build: 11 | dh build 12 | env CARGO_TARGET_DIR="${PWD}/target" CARGO_INCREMENTAL=0 cargo build --release --locked 13 | 14 | override_dh_install: 15 | # binary files 16 | install -D -m755 "target/release/surface-dtx-daemon" "${pkgdir}/usr/bin/surface-dtx-daemon" 17 | install -D -m755 "target/release/surface-dtx-userd" "${pkgdir}/usr/bin/surface-dtx-userd" 18 | 19 | # application files 20 | install -D -m644 "etc/dtx/surface-dtx-daemon.conf" "${pkgdir}/etc/surface-dtx/surface-dtx-daemon.conf" 21 | install -D -m644 "etc/dtx/surface-dtx-userd.conf" "${pkgdir}/etc/surface-dtx/surface-dtx-userd.conf" 22 | install -D -m755 "etc/dtx/attach.sh" "${pkgdir}/etc/surface-dtx/attach.sh" 23 | install -D -m755 "etc/dtx/detach.sh" "${pkgdir}/etc/surface-dtx/detach.sh" 24 | 25 | # systemd service files 26 | install -D -m644 "etc/systemd/surface-dtx-daemon.service" "${pkgdir}/usr/lib/systemd/system/surface-dtx-daemon.service" 27 | install -D -m644 "etc/systemd/surface-dtx-userd.service" "${pkgdir}/usr/lib/systemd/user/surface-dtx-userd.service" 28 | 29 | # dbus config file 30 | install -D -m644 "etc/dbus/org.surface.dtx.conf" "${pkgdir}/etc/dbus-1/system.d/org.surface.dtx.conf" 31 | 32 | # udev rules 33 | install -D -m644 "etc/udev/40-surface_dtx.rules" "${pkgdir}/etc/udev/rules.d/40-surface_dtx.rules" 34 | 35 | # completion files 36 | install -D -m644 "target/surface-dtx-daemon.bash" "${pkgdir}/usr/share/bash-completion/completions/surface-dtx-daemon" 37 | install -D -m644 "target/surface-dtx-userd.bash" "${pkgdir}/usr/share/bash-completion/completions/surface-dtx-userd" 38 | 39 | install -D -m644 "target/_surface-dtx-daemon" "${pkgdir}/usr/share/zsh/vendor-completions/_surface-dtx-daemon" 40 | install -D -m644 "target/_surface-dtx-userd" "${pkgdir}/usr/share/zsh/vendor-completions/_surface-dtx-userd" 41 | 42 | install -D -m644 "target/surface-dtx-daemon.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/surface-dtx-daemon.fish" 43 | install -D -m644 "target/surface-dtx-userd.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/surface-dtx-userd.fish" 44 | 45 | %: 46 | dh $@ -------------------------------------------------------------------------------- /pkg/deb/makedeb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | branch="HEAD" 5 | basepath="pkg/deb" 6 | 7 | 8 | pushd() { 9 | command pushd "$@" > /dev/null 10 | } 11 | 12 | popd() { 13 | command popd "$@" > /dev/null 14 | } 15 | 16 | 17 | chkdir() { 18 | if [ ! -d ".git" ] 19 | then 20 | echo "Error: Script must be run from the root directory" 21 | exit 1 22 | fi 23 | } 24 | 25 | prepare() { 26 | archive="src.tar" 27 | 28 | git archive --format tar "$branch" > "$basepath/$archive" 29 | 30 | mkdir -p "$basepath/src" 31 | tar xf "$basepath/$archive" --directory "$basepath/src" 32 | 33 | cp -r "$basepath/debian" "$basepath/src/" 34 | } 35 | 36 | build() { 37 | pushd "$basepath/src" 38 | dpkg-buildpackage -b -d -us -uc 39 | popd 40 | } 41 | 42 | 43 | clean() { 44 | echo "TODO" 45 | } 46 | 47 | 48 | chkdir 49 | prepare 50 | build 51 | -------------------------------------------------------------------------------- /pkg/fedora/makerpm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Default to using the first specfile in the current directory 4 | SPEC="*.spec" 5 | OPTS="-ba" 6 | 7 | SIGN=0 8 | KEY="" 9 | 10 | BUILD=".build" 11 | RPMS="out" 12 | 13 | usage() { 14 | echo "Usage: $0 [OPTION]..." 15 | echo "Wrapper for rpmbuild that is easier to use." 16 | echo 17 | echo "Options:" 18 | echo " -h This help message" 19 | echo " -f The specfile to build from" 20 | echo " -c Clean the build artifacts" 21 | echo " -s Sign the produced RPM packages" 22 | echo " -k The GPG key to use for signing" 23 | exit 24 | } 25 | 26 | clean() { 27 | rm -rf $BUILD 28 | rm -rf $RPMS 29 | exit 30 | } 31 | 32 | while getopts ":hcsf:k:" args; do 33 | case "$args" in 34 | f) 35 | SPEC=$OPTARG 36 | ;; 37 | s) 38 | SIGN=1 39 | ;; 40 | k) 41 | KEY=$OPTARG 42 | ;; 43 | c) 44 | clean 45 | ;; 46 | h) 47 | usage 48 | ;; 49 | esac 50 | done 51 | shift $((OPTIND-1)) 52 | 53 | if [ ! "$@" = "" ]; then 54 | OPTS="$@" 55 | fi 56 | 57 | # Check if the specfile exists 58 | if [ "$(ls -f $SPEC | wc -l)" = "0" ]; then 59 | echo "ERROR: No specfile found. Specify it with the -s option." 60 | exit -2 61 | fi 62 | 63 | # Check if there are too many specfiles 64 | if [ ! "$(ls -f $SPEC | wc -l)" = "1" ]; then 65 | echo "ERROR: Ambiguous matches for specfile. Please specify a single" \ 66 | "file through the -s option." 67 | exit -7 68 | fi 69 | 70 | # Get the directory of the specfile 71 | SPEC=$(ls -f $SPEC) 72 | DIR=$(readlink -f $(dirname $SPEC)) 73 | 74 | if [ ! -d "$DIR/$BUILD" ]; then 75 | mkdir "$DIR/$BUILD" 76 | fi 77 | 78 | FILES=$(find $DIR -maxdepth 1); 79 | for file in $FILES; do 80 | [ "$file" = "$DIR" ] && continue 81 | [ "$file" = "$DIR/$BUILD" ] && continue 82 | [ "$file" = "$DIR/$RPMS" ] && continue 83 | 84 | cp -r "$file" "$DIR/$BUILD" 85 | done 86 | 87 | spectool \ 88 | --define "_sourcedir $DIR/$BUILD" \ 89 | --define "_builddir $DIR/$BUILD" \ 90 | --define "_srcrpmdir $DIR/$RPMS" \ 91 | --define "_rpmdir $DIR/$RPMS" \ 92 | --define "_specdir $DIR" \ 93 | --get-files --all \ 94 | --directory $DIR/$BUILD $SPEC 95 | 96 | echo 97 | 98 | rpmbuild \ 99 | --define "_sourcedir $DIR/$BUILD" \ 100 | --define "_builddir $DIR/$BUILD" \ 101 | --define "_srcrpmdir $DIR/$RPMS" \ 102 | --define "_rpmdir $DIR/$RPMS" \ 103 | --define "_specdir $DIR" \ 104 | $OPTS $SPEC 105 | 106 | if [ ! "$SIGN" = "1" ]; then 107 | exit 108 | fi 109 | 110 | for file in $(find out/ -name '*.rpm'); do 111 | echo "Signing $file" 112 | if [ "$KEY" = "" ]; then 113 | rpm --resign $file 2>&1 > /dev/null 114 | else 115 | rpm --resign $file --define "_gpg_name $KEY" 2>&1 > /dev/null 116 | fi 117 | done 118 | -------------------------------------------------------------------------------- /pkg/fedora/surface-dtx-daemon.spec: -------------------------------------------------------------------------------- 1 | Name: surface-dtx-daemon 2 | Version: 0.3.9 3 | Release: 1%{?dist} 4 | Summary: Surface Detachment System (DTX) Daemon 5 | 6 | License: MIT 7 | URL: https://github.com/linux-surface/surface-dtx-daemon 8 | 9 | Requires: dbus libgcc 10 | BuildRequires: rust cargo dbus-devel 11 | 12 | %global debug_package %{nil} 13 | 14 | %description 15 | Linux User-Space Detachment System (DTX) Daemon for the Surface ACPI Driver 16 | (and Surface Books). Currently only the Surface Book 2 is supported, due to 17 | lack of driver-support on the Surface Book 1. This may change in the future. 18 | 19 | %prep 20 | 21 | %build 22 | export CARGO_TARGET_DIR="$PWD/target" 23 | export CARGO_INCREMENTAL=0 24 | 25 | cargo build --release --locked 26 | strip --strip-all "target/release/surface-dtx-daemon" 27 | strip --strip-all "target/release/surface-dtx-userd" 28 | 29 | %install 30 | 31 | # binary files 32 | install -D -m755 "target/release/surface-dtx-daemon" "%{buildroot}/usr/bin/surface-dtx-daemon" 33 | install -D -m755 "target/release/surface-dtx-userd" "%{buildroot}/usr/bin/surface-dtx-userd" 34 | 35 | # application files 36 | install -D -m644 "target/etc/dtx/surface-dtx-daemon.conf" "%{buildroot}/etc/surface-dtx/surface-dtx-daemon.conf" 37 | install -D -m644 "target/etc/dtx/surface-dtx-userd.conf" "%{buildroot}/etc/surface-dtx/surface-dtx-userd.conf" 38 | install -D -m755 "target/etc/dtx/attach.sh" "%{buildroot}/etc/surface-dtx/attach.sh" 39 | install -D -m755 "target/etc/dtx/detach.sh" "%{buildroot}/etc/surface-dtx/detach.sh" 40 | install -D -m644 "target/etc/systemd/surface-dtx-daemon.service" "%{buildroot}/usr/lib/systemd/system/surface-dtx-daemon.service" 41 | install -D -m644 "target/etc/systemd/surface-dtx-userd.service" "%{buildroot}/usr/lib/systemd/user/surface-dtx-userd.service" 42 | install -D -m644 "target/etc/dbus/org.surface.dtx.conf" "%{buildroot}/etc/dbus-1/system.d/org.surface.dtx.conf" 43 | install -D -m644 "target/etc/udev/40-surface_dtx.rules" "%{buildroot}/etc/udev/rules.d/40-surface_dtx.rules" 44 | 45 | # completion files 46 | install -D -m644 "target/surface-dtx-daemon.bash" "%{buildroot}/usr/share/bash-completion/completions/surface-dtx-daemon" 47 | install -D -m644 "target/surface-dtx-userd.bash" "%{buildroot}/usr/share/bash-completion/completions/surface-dtx-userd" 48 | install -D -m644 "target/_surface-dtx-daemon" "%{buildroot}/usr/share/zsh/site-functions/_surface-dtx-daemon" 49 | install -D -m644 "target/_surface-dtx-userd" "%{buildroot}/usr/share/zsh/site-functions/_surface-dtx-userd" 50 | install -D -m644 "target/surface-dtx-daemon.fish" "%{buildroot}/usr/share/fish/vendor_completions.d/surface-dtx-daemon.fish" 51 | install -D -m644 "target/surface-dtx-userd.fish" "%{buildroot}/usr/share/fish/vendor_completions.d/surface-dtx-userd.fish" 52 | 53 | %files 54 | %config /etc/dbus-1/system.d/org.surface.dtx.conf 55 | %config /etc/udev/rules.d/40-surface_dtx.rules 56 | %config(noreplace) /etc/surface-dtx/* 57 | /usr/bin/surface-dtx-daemon 58 | /usr/bin/surface-dtx-userd 59 | /usr/lib/systemd/system/surface-dtx-daemon.service 60 | /usr/lib/systemd/user/surface-dtx-userd.service 61 | /usr/share/bash-completion/completions/surface-dtx-daemon 62 | /usr/share/bash-completion/completions/surface-dtx-userd 63 | /usr/share/zsh/site-functions/_surface-dtx-daemon 64 | /usr/share/zsh/site-functions/_surface-dtx-userd 65 | /usr/share/fish/vendor_completions.d/surface-dtx-daemon.fish 66 | /usr/share/fish/vendor_completions.d/surface-dtx-userd.fish 67 | 68 | %changelog 69 | * Sat Apr 19 2025 Maximilian Luz - 0.3.9-1 70 | - Update dependencies 71 | 72 | * Sat Sep 14 2024 Maximilian Luz - 0.3.8-1 73 | - Update dependencies 74 | 75 | * Thu Mar 14 2024 Maximilian Luz - 0.3.7-1 76 | - Update dependencies 77 | 78 | * Tue Oct 03 2023 Maximilian Luz - 0.3.6-2 79 | - Bump release for Fedora 39 build 80 | 81 | * Tue Oct 03 2023 Maximilian Luz - 0.3.6-1 82 | - Update dependencies 83 | 84 | * Tue Jul 11 2023 Maximilian Luz - 0.3.5-1 85 | - Update dependencies 86 | 87 | * Wed Apr 19 2023 Maximilian Luz - 0.3.4-1 88 | - Update dependencies 89 | 90 | * Thu Apr 28 2022 Dorian Stoll - 0.3.2-1 91 | - Update dependencies 92 | 93 | * Wed Apr 27 2022 Dorian Stoll - 0.3.1-3 94 | - Bump release to build for Fedora 36 95 | 96 | * Wed Nov 03 2021 Dorian Stoll - 0.3.1-2 97 | - Bump release to build for Fedora 35 98 | 99 | * Mon Aug 23 2021 Maximilian Luz - 0.3.1-1 100 | - Fix typo causing the user-daemon to crash on latch error 101 | - Update dependencies 102 | 103 | * Wed Apr 07 2021 Maximilian Luz - 0.3.0-1 104 | - Properly forward hardware-errors, runtime-errors, and other notifications to user 105 | - Support DTX heartbeat commend 106 | - Support handler script timeouts 107 | - Various stability improvements 108 | 109 | * Fri Mar 19 2021 Dorian Stoll - 0.2.0-2 110 | - Bump release to build for Fedora 34 111 | 112 | * Tue Sep 29 2020 Dorian Stoll - 0.1.5-2 113 | - Bump release to build for Fedora 33 114 | 115 | * Sat Jul 04 2020 Maximilian Luz 0.1.5-1 116 | - Update dependencies 117 | 118 | * Tue Mar 31 2020 Dorian Stoll 0.1.4-3 119 | - Bump pkgrel 120 | 121 | * Fri Sep 27 2019 Dorian Stoll 122 | - Update packaging 123 | 124 | * Sat Sep 14 2019 Dorian Stoll 125 | - Update to 0.1.4 126 | 127 | * Fri May 17 2019 Dorian Stoll 128 | - Initial version 129 | -------------------------------------------------------------------------------- /surface-dtx-daemon/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "surface-dtx-daemon" 3 | version = "0.3.9" 4 | authors = ["Maximilian Luz "] 5 | description = "Surface Detachment System (DTX) Daemon" 6 | 7 | repository = "https://github.com/linux-surface/surface-dtx-daemon/" 8 | license = "MIT" 9 | 10 | edition = "2018" 11 | build = "build.rs" 12 | 13 | [dependencies] 14 | anyhow = "1.0.98" 15 | clap = { version = "4.5.37", features = ["cargo"] } 16 | dbus = "0.9.7" 17 | dbus-tokio = "0.7.6" 18 | dbus-crossroads = "0.5.2" 19 | futures = "0.3.31" 20 | libc = "0.2.172" 21 | nix = "0.29.0" 22 | sdtx = { git = "https://github.com/linux-surface/libsurfacedtx", tag = "v0.1.6" } 23 | sdtx-tokio = { git = "https://github.com/linux-surface/libsurfacedtx", tag = "v0.1.6" } 24 | serde = { version = "1.0.219", features = ['derive'] } 25 | tokio = { version = "1.44.2", features = ["fs", "sync", "process", "signal", "io-util", "rt", "macros"] } 26 | toml = "0.8.20" 27 | serde_ignored = "0.1.11" 28 | tracing = "0.1.41" 29 | tracing-subscriber = { version = "0.3.19", features = ["std", "env-filter"] } 30 | 31 | [build-dependencies] 32 | clap = "4.5.37" 33 | clap_complete = "4.5.47" 34 | -------------------------------------------------------------------------------- /surface-dtx-daemon/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | use clap_complete::shells; 4 | 5 | include!("src/cli.rs"); 6 | 7 | 8 | fn main() { 9 | let outdir: PathBuf = env::var_os("CARGO_TARGET_DIR") 10 | .or_else(|| env::var_os("OUT_DIR")) 11 | .unwrap() 12 | .into(); 13 | 14 | let rootdir = env::current_dir().unwrap(); 15 | let rootdir = rootdir 16 | .parent().unwrap(); 17 | 18 | println!("{rootdir:?}"); 19 | 20 | // generate shell completions 21 | let mut app = app(); 22 | clap_complete::generate_to(shells::Bash, &mut app, "surface-dtx-daemon", &outdir).unwrap(); 23 | clap_complete::generate_to(shells::Zsh, &mut app, "surface-dtx-daemon", &outdir).unwrap(); 24 | clap_complete::generate_to(shells::Fish, &mut app, "surface-dtx-daemon", &outdir).unwrap(); 25 | 26 | // copy config files 27 | let files = [ 28 | "etc/dbus/org.surface.dtx.conf", 29 | "etc/dtx/attach.sh", 30 | "etc/dtx/detach.sh", 31 | "etc/dtx/surface-dtx-daemon.conf", 32 | "etc/systemd/surface-dtx-daemon.service", 33 | "etc/udev/40-surface_dtx.rules", 34 | ]; 35 | 36 | for file in files { 37 | let src = rootdir.join(file); 38 | let tgt = outdir.join(file); 39 | 40 | std::fs::create_dir_all(tgt.parent().unwrap()).unwrap(); 41 | std::fs::copy(src, tgt).unwrap(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{Arg, Command, ArgAction}; 2 | 3 | pub fn app() -> Command { 4 | Command::new("Surface DTX Daemon") 5 | .about(clap::crate_description!()) 6 | .version(clap::crate_version!()) 7 | .author(clap::crate_authors!()) 8 | .arg(Arg::new("config") 9 | .short('c') 10 | .long("config") 11 | .value_name("FILE") 12 | .help("Use the specified config file") 13 | .value_parser(clap::value_parser!(std::path::PathBuf))) 14 | .arg(Arg::new("no-log-time") 15 | .long("no-log-time") 16 | .help("Do not emit timestamps in log") 17 | .action(ArgAction::SetTrue)) 18 | } 19 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use anyhow::{Context, Result}; 5 | use serde::{Deserialize, Serialize}; 6 | use tracing::{debug, warn}; 7 | 8 | 9 | const DEFAULT_CONFIG_PATH: &str = "/etc/surface-dtx/surface-dtx-daemon.conf"; 10 | 11 | 12 | #[derive(Debug, Serialize, Deserialize, Default, Clone)] 13 | pub struct Config { 14 | #[serde(skip)] 15 | pub dir: PathBuf, 16 | 17 | #[serde(default)] 18 | pub log: Log, 19 | 20 | #[serde(default)] 21 | pub handler: Handler, 22 | } 23 | 24 | #[derive(Debug, Serialize, Deserialize, Default, Clone)] 25 | pub struct Log { 26 | #[serde(default)] 27 | pub level: LogLevel, 28 | } 29 | 30 | #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)] 31 | #[serde(rename_all="lowercase")] 32 | pub enum LogLevel { 33 | Error, 34 | Warn, 35 | #[default] 36 | Info, 37 | Debug, 38 | Trace, 39 | } 40 | 41 | #[derive(Debug, Serialize, Deserialize, Default, Clone)] 42 | pub struct Handler { 43 | #[serde(default)] 44 | pub detach: DetachHandler, 45 | 46 | #[serde(default)] 47 | pub detach_abort: DetachAbortHandler, 48 | 49 | #[serde(default)] 50 | pub attach: AttachHandler, 51 | } 52 | 53 | #[derive(Debug, Serialize, Deserialize, Default, Clone)] 54 | pub struct DetachHandler { 55 | #[serde(default)] 56 | pub exec: Option, 57 | 58 | #[serde(default="defaults::task_timeout")] 59 | pub timeout: f32, 60 | } 61 | 62 | #[derive(Debug, Serialize, Deserialize, Default, Clone)] 63 | pub struct DetachAbortHandler { 64 | #[serde(default)] 65 | pub exec: Option, 66 | 67 | #[serde(default="defaults::task_timeout")] 68 | pub timeout: f32, 69 | } 70 | 71 | #[derive(Debug, Serialize, Deserialize, Default, Clone)] 72 | pub struct AttachHandler { 73 | #[serde(default)] 74 | pub exec: Option, 75 | 76 | #[serde(default="defaults::task_timeout")] 77 | pub timeout: f32, 78 | 79 | #[serde(default="defaults::delay_attach")] 80 | pub delay: f32, 81 | } 82 | 83 | 84 | impl Config { 85 | pub fn load() -> Result<(Config, Diagnostics)> { 86 | if Path::new(DEFAULT_CONFIG_PATH).exists() { 87 | Config::load_file(DEFAULT_CONFIG_PATH) 88 | } else { 89 | Ok((Config::default(), Diagnostics::empty())) 90 | } 91 | } 92 | 93 | pub fn load_file>(path: P) -> Result<(Config, Diagnostics)> { 94 | use std::io::Read; 95 | 96 | let mut buf = Vec::new(); 97 | let mut file = std::fs::File::open(path.as_ref()) 98 | .context("Failed to open config file")?; 99 | 100 | file.read_to_end(&mut buf) 101 | .with_context(|| format!("Failed to read config file (path: {:?})", path.as_ref()))?; 102 | 103 | let data = std::str::from_utf8(&buf) 104 | .with_context(|| format!("Failed to read config file (path: {:?})", path.as_ref()))?; 105 | 106 | let de = toml::Deserializer::new(data); 107 | 108 | let mut unknowns = BTreeSet::new(); 109 | let mut config: Config = serde_ignored::deserialize(de, |path| { 110 | unknowns.insert(path.to_string()); 111 | }).with_context(|| format!("Failed to read config file (path: {:?})", path.as_ref()))?; 112 | 113 | config.dir = path.as_ref().parent().unwrap().into(); 114 | 115 | let diag = Diagnostics { 116 | path: path.as_ref().into(), 117 | unknowns, 118 | }; 119 | 120 | Ok((config, diag)) 121 | } 122 | } 123 | 124 | 125 | pub struct Diagnostics { 126 | pub path: PathBuf, 127 | pub unknowns: BTreeSet, 128 | } 129 | 130 | impl Diagnostics { 131 | fn empty() -> Self { 132 | Diagnostics { 133 | path: PathBuf::new(), 134 | unknowns: BTreeSet::new() 135 | } 136 | } 137 | 138 | pub fn log(&self) { 139 | let span = tracing::info_span!("config", file=?self.path); 140 | let _guard = span.enter(); 141 | 142 | debug!(target: "sdtxd::config", "configuration loaded"); 143 | for item in &self.unknowns { 144 | warn!(target: "sdtxd::config", item = %item, "unknown config item") 145 | } 146 | } 147 | } 148 | 149 | 150 | mod defaults { 151 | pub fn delay_attach() -> f32 { 152 | 5.0 153 | } 154 | 155 | pub fn task_timeout() -> f32 { 156 | 60.0 157 | } 158 | } 159 | 160 | 161 | impl From for tracing::Level { 162 | fn from(level: LogLevel) -> Self { 163 | match level { 164 | LogLevel::Error => tracing::Level::ERROR, 165 | LogLevel::Warn => tracing::Level::WARN, 166 | LogLevel::Info => tracing::Level::INFO, 167 | LogLevel::Debug => tracing::Level::DEBUG, 168 | LogLevel::Trace => tracing::Level::TRACE, 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/logic/mod.rs: -------------------------------------------------------------------------------- 1 | mod core; 2 | pub use self::core::{Adapter, AtHandle, Core, DtHandle, DtcHandle}; 3 | 4 | mod proc; 5 | pub use self::proc::ProcessAdapter; 6 | 7 | mod srvc; 8 | pub use self::srvc::ServiceAdapter; 9 | 10 | 11 | use sdtx::event; 12 | pub use sdtx::{BaseInfo, BaseState, DeviceMode, DeviceType, HardwareError, LatchStatus}; 13 | 14 | 15 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 16 | pub enum RuntimeError { 17 | NotAttached, 18 | NotFeasible, 19 | Timeout, 20 | Unknown(u8), 21 | } 22 | 23 | impl From for RuntimeError { 24 | fn from(err: sdtx::RuntimeError) -> Self { 25 | match err { 26 | sdtx::RuntimeError::NotFeasible => Self::NotFeasible, 27 | sdtx::RuntimeError::Timeout => Self::Timeout, 28 | sdtx::RuntimeError::Unknown(x) => Self::Unknown(x), 29 | } 30 | } 31 | } 32 | 33 | impl std::fmt::Display for RuntimeError { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | match self { 36 | Self::NotAttached => write!(f, "no base attached"), 37 | Self::NotFeasible => write!(f, "not feasible"), 38 | Self::Timeout => write!(f, "timeout"), 39 | Self::Unknown(x) => write!(f, "unknown: {x:#04x}"), 40 | } 41 | } 42 | } 43 | 44 | 45 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 46 | pub enum CancelReason { 47 | UserRequest, // user or higher layer requested cancelation, or user did not act 48 | HandlerTimeout, 49 | DisconnectTimeout, 50 | Runtime(RuntimeError), 51 | Hardware(HardwareError), 52 | Unknown(u16), 53 | } 54 | 55 | impl From for CancelReason { 56 | fn from(reason: event::CancelReason) -> Self { 57 | match reason { 58 | event::CancelReason::Runtime(e) => Self::Runtime(RuntimeError::from(e)), 59 | event::CancelReason::Hardware(e) => Self::Hardware(e), 60 | event::CancelReason::Unknown(x) => Self::Unknown(x), 61 | } 62 | } 63 | } 64 | 65 | impl std::fmt::Display for CancelReason { 66 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 67 | match self { 68 | Self::UserRequest => write!(f, "user request"), 69 | Self::HandlerTimeout => write!(f, "timed out waiting for detachment handler"), 70 | Self::DisconnectTimeout => write!(f, "timed out waiting for user to disconnect base"), 71 | Self::Runtime(err) => write!(f, "runtime error: {err}"), 72 | Self::Hardware(err) => write!(f, "hardware error: {err}"), 73 | Self::Unknown(x) => write!(f, "unknown: {x:#04x}"), 74 | } 75 | } 76 | } 77 | 78 | 79 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 80 | pub enum LatchState { 81 | Closed, 82 | Opened, 83 | } 84 | 85 | impl From for LatchStatus { 86 | fn from(status: LatchState) -> Self { 87 | match status { 88 | LatchState::Closed => Self::Closed, 89 | LatchState::Opened => Self::Opened, 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/logic/proc.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::logic::{ 3 | Adapter, 4 | AtHandle, 5 | DtHandle, 6 | DtcHandle, 7 | }; 8 | use crate::utils::taskq::TaskSender; 9 | 10 | use std::time::Duration; 11 | 12 | use anyhow::{Context, Error, Result}; 13 | use tokio::process::Command; 14 | use tracing::{Level, debug, trace}; 15 | 16 | 17 | const HEARTBEAT_PERIOD_MS: u64 = 2500; 18 | 19 | 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 21 | enum ExitStatus { 22 | Commence = 0, 23 | Abort = 1, 24 | } 25 | 26 | impl ExitStatus { 27 | fn as_str(&self) -> &'static str { 28 | match self { 29 | Self::Commence => "0", 30 | Self::Abort => "1", 31 | } 32 | } 33 | } 34 | 35 | impl From for ExitStatus { 36 | fn from(status: std::process::ExitStatus) -> Self { 37 | status.code().map(|s| if s == 0 { 38 | ExitStatus::Commence 39 | } else { 40 | ExitStatus::Abort 41 | }).unwrap_or(ExitStatus::Abort) 42 | } 43 | } 44 | 45 | 46 | pub struct ProcessAdapter { 47 | config: Config, 48 | queue: TaskSender, 49 | } 50 | 51 | impl ProcessAdapter { 52 | pub fn new(config: Config, queue: TaskSender) -> Self { 53 | Self { 54 | config, 55 | queue, 56 | } 57 | } 58 | } 59 | 60 | impl Adapter for ProcessAdapter { 61 | fn detachment_start(&mut self, handle: DtHandle) -> Result<()> { 62 | // build heartbeat task 63 | let h = handle.clone(); 64 | let heartbeat = async move { 65 | loop { 66 | tokio::time::sleep(Duration::from_millis(HEARTBEAT_PERIOD_MS)).await; 67 | h.heartbeat()?; 68 | } 69 | }; 70 | 71 | // build timeout task 72 | let h = handle.clone(); 73 | let timeout = self.config.handler.detach.timeout * 1000.0; 74 | let timeout = async move { 75 | tokio::time::sleep(Duration::from_millis(timeout as _)).await; 76 | 77 | trace!(target: "sdtxd::proc", "detachment process timed out, canceling"); 78 | h.timeout(); 79 | 80 | Ok(()) 81 | }; 82 | 83 | // build process task 84 | let dir = self.config.dir.clone(); 85 | let handler = self.config.handler.detach.exec.clone(); 86 | let proc = async move { 87 | trace!(target: "sdtxd::proc", "detachment process started"); 88 | 89 | // run handler if specified 90 | let status = if let Some(ref path) = handler { 91 | debug!(target: "sdtxd::proc", ?path, ?dir, "running detachment handler"); 92 | 93 | // run handler 94 | let output = Command::new(path) 95 | .current_dir(dir) 96 | .env("EXIT_DETACH_COMMENCE", ExitStatus::Commence.as_str()) 97 | .env("EXIT_DETACH_ABORT", ExitStatus::Abort.as_str()) 98 | .kill_on_drop(true) 99 | .output().await 100 | .context("Subprocess error (detachment)")?; 101 | 102 | // log output 103 | output.log("detachment handler"); 104 | 105 | // confirm latch open/detach commence based on return status 106 | ExitStatus::from(output.status) 107 | 108 | } else { 109 | debug!(target: "sdtxd::proc", "no detachment handler specified, skipping"); 110 | ExitStatus::Commence 111 | }; 112 | 113 | // send response, will be ignored if already canceled 114 | if status == ExitStatus::Commence { 115 | debug!(target: "sdtxd::proc", "detachment commencing based on handler response"); 116 | handle.confirm(); 117 | } else { 118 | debug!(target: "sdtxd::proc", "detachment canceled based on handler response"); 119 | handle.cancel(); 120 | } 121 | 122 | trace!(target: "sdtxd::proc", "detachment process completed"); 123 | Ok(()) 124 | }; 125 | 126 | // build task 127 | let task = async move { 128 | tokio::select! { 129 | r = proc => r, 130 | r = heartbeat => r, 131 | r = timeout => r, 132 | } 133 | }; 134 | 135 | // submit task 136 | trace!(target: "sdtxd::proc", "scheduling detachment task"); 137 | if self.queue.submit(task).is_err() { 138 | unreachable!("receiver dropped"); 139 | } 140 | 141 | Ok(()) 142 | } 143 | 144 | fn detachment_cancel_start(&mut self, handle: DtcHandle) -> Result<()> { 145 | // build timeout task 146 | let h = handle.clone(); 147 | let timeout = self.config.handler.detach_abort.timeout * 1000.0; 148 | let timeout = async move { 149 | tokio::time::sleep(Duration::from_millis(timeout as _)).await; 150 | 151 | trace!(target: "sdtxd::proc", "detachment-abort timed out, canceling"); 152 | h.timeout(); 153 | 154 | Ok(()) 155 | }; 156 | 157 | // build process task 158 | let dir = self.config.dir.clone(); 159 | let handler = self.config.handler.detach_abort.exec.clone(); 160 | let proc = async move { 161 | trace!(target: "sdtxd::proc", "detachment-abort process started"); 162 | 163 | // run handler if specified 164 | if let Some(ref path) = handler { 165 | debug!(target: "sdtxd::proc", ?path, ?dir, "running detachment-abort handler"); 166 | 167 | // run handler 168 | let output = Command::new(path) 169 | .current_dir(dir) 170 | .kill_on_drop(true) 171 | .output().await 172 | .context("Subprocess error (detachment-abort)")?; 173 | 174 | // log output 175 | output.log("detachment-abort handler"); 176 | 177 | } else { 178 | debug!(target: "sdtxd::proc", "no detachment-abort handler specified, skipping"); 179 | }; 180 | 181 | trace!(target: "sdtxd::proc", "detachment-abort process completed"); 182 | handle.complete(); 183 | 184 | Ok(()) 185 | }; 186 | 187 | // build task 188 | let task = async move { 189 | tokio::select! { 190 | r = proc => r, 191 | r = timeout => r, 192 | } 193 | }; 194 | 195 | // submit task 196 | trace!(target: "sdtxd::proc", "scheduling detachment-abort task"); 197 | if self.queue.submit(task).is_err() { 198 | unreachable!("receiver dropped"); 199 | } 200 | 201 | Ok(()) 202 | } 203 | 204 | fn attachment_start(&mut self, handle: AtHandle) -> Result<()> { 205 | // build timeout task 206 | let h = handle.clone(); 207 | let timeout = self.config.handler.attach.timeout * 1000.0; 208 | let timeout = async move { 209 | tokio::time::sleep(Duration::from_millis(timeout as _)).await; 210 | 211 | trace!(target: "sdtxd::proc", "detachment-abort timed out, canceling"); 212 | h.timeout(); 213 | 214 | Ok(()) 215 | }; 216 | 217 | // build process task 218 | let dir = self.config.dir.clone(); 219 | let handler = self.config.handler.attach.exec.clone(); 220 | let proc = async move { 221 | trace!(target: "sdtxd::proc", "attachment process started"); 222 | 223 | // run handler if specified 224 | if let Some(ref path) = handler { 225 | debug!(target: "sdtxd::proc", ?path, ?dir, "running attachment handler"); 226 | 227 | // run handler 228 | let output = Command::new(path) 229 | .current_dir(dir) 230 | .kill_on_drop(true) 231 | .output().await 232 | .context("Subprocess error (attachment)")?; 233 | 234 | // log output 235 | output.log("attachment handler"); 236 | 237 | } else { 238 | debug!(target: "sdtxd::proc", "no attachment handler specified, skipping"); 239 | }; 240 | 241 | trace!(target: "sdtxd::proc", "attachment process completed"); 242 | handle.complete(); 243 | 244 | Ok(()) 245 | }; 246 | 247 | // build task 248 | let delay = Duration::from_millis((self.config.handler.attach.delay * 1000.0) as _); 249 | let task = async move { 250 | // delay to ensure all devices are set up 251 | debug!(target: "sdtxd::proc", "delaying attachment process by {}ms", delay.as_millis()); 252 | tokio::time::sleep(delay).await; 253 | 254 | // drive main tasks 255 | tokio::select! { 256 | r = proc => r, 257 | r = timeout => r, 258 | } 259 | }; 260 | 261 | // submit task 262 | trace!(target: "sdtxd::proc", "scheduling attachment task"); 263 | if self.queue.submit(task).is_err() { 264 | unreachable!("receiver dropped"); 265 | } 266 | 267 | Ok(()) 268 | } 269 | } 270 | 271 | 272 | trait ProcessOutputExt { 273 | fn log>(&self, procname: S); 274 | } 275 | 276 | impl ProcessOutputExt for std::process::Output { 277 | fn log>(&self, procname: S) { 278 | 279 | fn log_stream(level: Level, name: &'static str, data: &[u8]) { 280 | if !data.is_empty() { 281 | event!(target: "sdtxd::proc", level, " (contd.)"); 282 | event!(target: "sdtxd::proc", level, " (contd.) {}:", name); 283 | 284 | let data = std::str::from_utf8(data); 285 | match data { 286 | Ok(str) => { 287 | for line in str.lines() { 288 | event!(target: "sdtxd::proc", level, " (contd.) {}", line); 289 | } 290 | }, 291 | Err(_) => { 292 | event!(target: "sdtxd::proc", level, " (contd.) {:?}", data); 293 | }, 294 | } 295 | } 296 | } 297 | 298 | let level = if !self.stderr.is_empty() { 299 | tracing::Level::WARN 300 | } else if !self.stdout.is_empty() { 301 | tracing::Level::INFO 302 | } else { 303 | tracing::Level::DEBUG 304 | }; 305 | 306 | event!(target: "sdtxd::proc", level, "{} exited with {}", procname.as_ref(), self.status); 307 | log_stream(level, "stdout", &self.stdout); 308 | log_stream(level, "stderr", &self.stderr); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/logic/srvc.rs: -------------------------------------------------------------------------------- 1 | use crate::logic::{ 2 | Adapter, 3 | AtHandle, 4 | BaseInfo, 5 | CancelReason, 6 | DeviceMode, 7 | DtHandle, 8 | DtcHandle, 9 | LatchState, 10 | LatchStatus, 11 | }; 12 | use crate::service::{ServiceHandle, Event}; 13 | 14 | use anyhow::Result; 15 | 16 | 17 | pub struct ServiceAdapter { 18 | service: ServiceHandle, 19 | } 20 | 21 | impl ServiceAdapter { 22 | pub fn new(service: ServiceHandle) -> Self { 23 | Self { service } 24 | } 25 | } 26 | 27 | impl Adapter for ServiceAdapter { 28 | fn set_state(&mut self, mode: DeviceMode, base: BaseInfo, latch: LatchState) { 29 | self.service.set_base_info(base); 30 | self.service.set_latch_status(latch.into()); 31 | self.service.set_device_mode(mode); 32 | } 33 | 34 | fn on_base_state(&mut self, info: BaseInfo) -> Result<()> { 35 | self.service.set_base_info(info); 36 | Ok(()) 37 | } 38 | 39 | fn on_latch_status(&mut self, status: LatchStatus) -> Result<()> { 40 | self.service.set_latch_status(status); 41 | Ok(()) 42 | } 43 | 44 | fn on_device_mode(&mut self, mode: DeviceMode) -> Result<()> { 45 | self.service.set_device_mode(mode); 46 | Ok(()) 47 | } 48 | 49 | fn request_inhibited(&mut self, reason: CancelReason) -> Result<()> { 50 | self.service.emit_event(Event::DetachmentInhibited { reason }); 51 | Ok(()) 52 | } 53 | 54 | fn detachment_start(&mut self, _handle: DtHandle) -> Result<()> { 55 | self.service.emit_event(Event::DetachmentStart); 56 | Ok(()) 57 | } 58 | 59 | fn detachment_ready(&mut self) -> Result<()> { 60 | self.service.emit_event(Event::DetachmentReady); 61 | Ok(()) 62 | } 63 | 64 | fn detachment_complete(&mut self) -> Result<()> { 65 | self.service.emit_event(Event::DetachmentComplete); 66 | Ok(()) 67 | } 68 | 69 | fn detachment_cancel(&mut self, reason: CancelReason) -> Result<()> { 70 | self.service.emit_event(Event::DetachmentCancel { reason }); 71 | Ok(()) 72 | } 73 | 74 | fn detachment_cancel_start(&mut self, _handle: DtcHandle) -> Result<()> { 75 | self.service.emit_event(Event::DetachmentCancelStart); 76 | Ok(()) 77 | } 78 | 79 | fn detachment_cancel_complete(&mut self) -> Result<()> { 80 | self.service.emit_event(Event::DetachmentCancelComplete); 81 | Ok(()) 82 | } 83 | 84 | fn detachment_cancel_timeout(&mut self) -> Result<()> { 85 | self.service.emit_event(Event::DetachmentCancelTimeout); 86 | Ok(()) 87 | } 88 | 89 | fn detachment_unexpected(&mut self) -> Result<()> { 90 | self.service.emit_event(Event::DetachmentUnexpected); 91 | Ok(()) 92 | } 93 | 94 | fn attachment_start(&mut self, _handle: AtHandle) -> Result<()> { 95 | self.service.emit_event(Event::AttachmentStart); 96 | Ok(()) 97 | } 98 | 99 | fn attachment_complete(&mut self) -> Result<()> { 100 | self.service.emit_event(Event::AttachmentComplete); 101 | Ok(()) 102 | } 103 | 104 | fn attachment_timeout(&mut self) -> Result<()> { 105 | self.service.emit_event(Event::AttachmentTimeout); 106 | Ok(()) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod utils; 3 | use utils::task::JoinHandleExt; 4 | 5 | mod cli; 6 | 7 | mod config; 8 | use config::Config; 9 | 10 | mod logic; 11 | 12 | mod service; 13 | use service::Service; 14 | 15 | 16 | use std::{sync::{Arc, Mutex}, path::PathBuf, io::IsTerminal}; 17 | 18 | use anyhow::{Context, Result}; 19 | 20 | use dbus::channel::MatchingReceiver; 21 | use dbus::message::MatchRule; 22 | use dbus_tokio::connection; 23 | use dbus_crossroads::Crossroads; 24 | 25 | use futures::prelude::*; 26 | 27 | use tokio::signal::unix::{signal, SignalKind}; 28 | 29 | use tracing::{error, info, trace, warn}; 30 | 31 | 32 | fn bootstrap() -> Result { 33 | // handle command line input 34 | let matches = cli::app().get_matches(); 35 | 36 | // set up config 37 | let (config, diag) = match matches.get_one::("config") { 38 | Some(path) => Config::load_file(path)?, 39 | None => Config::load()?, 40 | }; 41 | 42 | // set up logger 43 | let filter = tracing_subscriber::EnvFilter::from_env("SDTXD_LOG") 44 | .add_directive(tracing::Level::from(config.log.level).into()); 45 | 46 | let fmt = tracing_subscriber::fmt::format::PrettyFields::new(); 47 | 48 | let subscriber = tracing_subscriber::fmt() 49 | .fmt_fields(fmt) 50 | .with_env_filter(filter) 51 | .with_ansi(std::io::stdout().is_terminal()); 52 | 53 | if matches.get_flag("no-log-time") { 54 | subscriber.without_time().init(); 55 | } else { 56 | subscriber.init(); 57 | } 58 | 59 | // warn about unknown config items 60 | diag.log(); 61 | 62 | Ok(config) 63 | } 64 | 65 | async fn run() -> Result<()> { 66 | let config = bootstrap()?; 67 | 68 | // set up signal handling 69 | trace!(target: "sdtxd", "setting up signal handling"); 70 | 71 | let mut sigint = signal(SignalKind::interrupt()).context("Failed to set up signal handling")?; 72 | let mut sigterm = signal(SignalKind::terminate()).context("Failed to set up signal handling")?; 73 | 74 | let sig = async { tokio::select! { 75 | _ = sigint.recv() => "SIGINT", 76 | _ = sigterm.recv() => "SIGTERM", 77 | }}; 78 | 79 | // prepare devices 80 | trace!(target: "sdtxd", "preparing devices"); 81 | 82 | let event_device = sdtx_tokio::connect().await 83 | .context("Failed to access DTX device")?; 84 | 85 | let control_device = sdtx_tokio::connect().await 86 | .context("Failed to access DTX device")?; 87 | 88 | // set up D-Bus connection 89 | trace!(target: "sdtxd", "connecting to D-Bus"); 90 | 91 | let (dbus_rsrc, dbus_conn) = connection::new_system_sync() 92 | .context("Failed to connect to D-Bus")?; 93 | 94 | let dbus_rsrc = dbus_rsrc.map(|e| Err(e).context("D-Bus connection error")); 95 | let mut dbus_task = tokio::spawn(dbus_rsrc).guard(); 96 | 97 | // set up D-Bus service 98 | trace!(target: "sdtxd", "setting up D-Bus service"); 99 | 100 | let dbus_cr = Arc::new(Mutex::new(Crossroads::new())); 101 | 102 | let serv = Service::new(dbus_conn.clone(), control_device); 103 | serv.request_name().await?; 104 | serv.register(&mut dbus_cr.lock().unwrap())?; 105 | 106 | let cr = dbus_cr.clone(); 107 | let token = dbus_conn.start_receive(MatchRule::new_method_call(), Box::new(move |msg, conn| { 108 | // Crossroads::handle_message() only fails if message is not a method call 109 | cr.lock().unwrap().handle_message(msg, conn).unwrap(); 110 | true 111 | })); 112 | 113 | let recv_guard = utils::scope::guard(|| { let _ = dbus_conn.stop_receive(token).unwrap(); }); 114 | let serv_guard = utils::scope::guard(|| { serv.unregister(&mut dbus_cr.lock().unwrap()); }); 115 | 116 | // set up task-queue 117 | trace!(target: "sdtxd", "setting up task queue"); 118 | 119 | let (mut queue, queue_tx) = utils::taskq::new(); 120 | let mut queue_task = tokio::spawn(async move { queue.run().await }).guard(); 121 | 122 | // set up event handler 123 | trace!(target: "sdtxd", "setting up DTX event handling"); 124 | 125 | let proc_adp = logic::ProcessAdapter::new(config, queue_tx); 126 | let srvc_adp = logic::ServiceAdapter::new(serv.handle()); 127 | 128 | let mut core = logic::Core::new(event_device, (proc_adp, srvc_adp)); 129 | let mut event_task = tokio::spawn(async move { core.run().await }).guard(); 130 | 131 | // collect main driver tasks 132 | let tasks = async { tokio::select! { 133 | result = &mut dbus_task => result, 134 | result = &mut event_task => result, 135 | result = &mut queue_task => result, 136 | }}; 137 | 138 | // run until whatever comes first: error, panic, or shutdown signal 139 | info!(target: "sdtxd", "running..."); 140 | 141 | tokio::select! { 142 | signame = sig => { 143 | // first shutdown signal: try to do a clean shutdown and complete 144 | // the task queue 145 | info!(target: "sdtxd", "received {}, shutting down...", signame); 146 | 147 | // stop event task: don't handle any new DTX events and drop task 148 | // queue transmitter to eventually cause the task queue task to 149 | // complete 150 | event_task.abort(); 151 | 152 | // unregister service 153 | drop(serv_guard); 154 | 155 | // stop D-Bus message handling 156 | drop(recv_guard); 157 | 158 | // pepare handling for second shutdown signal 159 | let sig = async { tokio::select! { 160 | _ = sigint.recv() => ("SIGINT", 2), 161 | _ = sigterm.recv() => ("SIGTERM", 15), 162 | }}; 163 | 164 | // try to run task queue to completion, shut down and exit if 165 | // second signal received 166 | tokio::select! { 167 | (signame, tval) = sig => { 168 | warn!(target: "sdtxd", "received {} during shutdown, terminating...", signame); 169 | std::process::exit(128 + tval) 170 | }, 171 | result = queue_task => match result { 172 | Ok(res) => res, 173 | Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()), 174 | Err(_) => unreachable!("Task unexpectedly canceled"), 175 | } 176 | } 177 | } 178 | result = tasks => match result { 179 | Ok(res) => res, 180 | Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()), 181 | Err(_) => unreachable!("Task unexpectedly canceled"), 182 | }, 183 | } 184 | } 185 | 186 | #[tokio::main(flavor = "current_thread")] 187 | async fn main() -> Result<()> { 188 | // run main function and log critical errors 189 | let result = run().await; 190 | if let Err(ref err) = result { 191 | error!(target: "sdtxd", "critical error: {}\n", err); 192 | } 193 | 194 | // for some reason tokio won't properly shut down, even though every task 195 | // we spawned should be either canceled or completed by now... 196 | if let Err(err) = result { 197 | eprintln!("{err:?}"); 198 | std::process::exit(1) 199 | } else { 200 | std::process::exit(0) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/service/arg.rs: -------------------------------------------------------------------------------- 1 | use crate::logic::{ 2 | BaseInfo, 3 | BaseState, 4 | CancelReason, 5 | DeviceMode, 6 | DeviceType, 7 | HardwareError, 8 | LatchStatus, 9 | RuntimeError, 10 | }; 11 | 12 | use dbus::arg::Variant; 13 | 14 | 15 | pub trait DbusArg { 16 | type Arg: dbus::arg::RefArg + 'static; 17 | 18 | fn as_arg(&self) -> Self::Arg; 19 | 20 | fn as_variant(&self) -> Variant> { 21 | Variant(Box::new(self.as_arg())) 22 | } 23 | } 24 | 25 | impl DbusArg for DeviceMode { 26 | type Arg = String; 27 | 28 | fn as_arg(&self) -> String { 29 | match self { 30 | DeviceMode::Tablet => "tablet", 31 | DeviceMode::Laptop => "laptop", 32 | DeviceMode::Studio => "studio", 33 | }.into() 34 | } 35 | } 36 | 37 | impl DbusArg for LatchStatus { 38 | type Arg = String; 39 | 40 | fn as_arg(&self) -> String { 41 | match self { 42 | LatchStatus::Closed => "closed".into(), 43 | LatchStatus::Opened => "opened".into(), 44 | LatchStatus::Error(error) => match error { 45 | HardwareError::FailedToOpen => "error:hardware:failed-to-open".into(), 46 | HardwareError::FailedToRemainOpen => "error:hardware:failed-to-remain-open".into(), 47 | HardwareError::FailedToClose => "error:hardware:failed-to-close".into(), 48 | HardwareError::Unknown(x) => format!("error:hardware:unknown:{x}"), 49 | }, 50 | } 51 | } 52 | } 53 | 54 | impl DbusArg for BaseInfo { 55 | type Arg = (String, String, u8); 56 | 57 | fn as_arg(&self) -> Self::Arg { 58 | (self.state.as_arg(), self.device_type.as_arg(), self.id) 59 | } 60 | } 61 | 62 | impl DbusArg for BaseState { 63 | type Arg = String; 64 | 65 | fn as_arg(&self) -> Self::Arg { 66 | match self { 67 | BaseState::Detached => "detached", 68 | BaseState::Attached => "attached", 69 | BaseState::NotFeasible => "not-feasible", 70 | }.into() 71 | } 72 | } 73 | 74 | impl DbusArg for DeviceType { 75 | type Arg = String; 76 | 77 | fn as_arg(&self) -> Self::Arg { 78 | match self { 79 | DeviceType::Hid => "hid".into(), 80 | DeviceType::Ssh => "ssh".into(), 81 | DeviceType::Unknown(x) => format!("unknown:{x}"), 82 | } 83 | } 84 | } 85 | 86 | impl DbusArg for CancelReason { 87 | type Arg = String; 88 | 89 | fn as_arg(&self) -> Self::Arg { 90 | match self { 91 | CancelReason::UserRequest => "request".into(), 92 | CancelReason::HandlerTimeout => "timeout:handler".into(), 93 | CancelReason::DisconnectTimeout => "timeout:disconnect".into(), 94 | CancelReason::Runtime(rt) => match rt { 95 | RuntimeError::NotAttached => "error:runtime:not-attached".into(), 96 | RuntimeError::NotFeasible => "error:runtime:not-feasible".into(), 97 | RuntimeError::Timeout => "error:runtime:timeout".into(), 98 | RuntimeError::Unknown(x) => format!("error:runtime:unknown:{x}"), 99 | }, 100 | CancelReason::Hardware(hw) => match hw { 101 | HardwareError::FailedToOpen => "error:hardware:failed-to-open".into(), 102 | HardwareError::FailedToRemainOpen => "error:hardware:failed-to-remain-open".into(), 103 | HardwareError::FailedToClose => "error:hardware:failed-to-close".into(), 104 | HardwareError::Unknown(x) => format!("error:hardware:unknown:{x}"), 105 | }, 106 | CancelReason::Unknown(x) => format!("unknown:{x}"), 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/service/event.rs: -------------------------------------------------------------------------------- 1 | use crate::logic::CancelReason; 2 | use crate::service::arg::DbusArg; 3 | 4 | use dbus::arg::{Append, Dict, RefArg, Variant}; 5 | 6 | 7 | #[derive(Debug, Clone, Copy)] 8 | pub enum Event { 9 | DetachmentInhibited { reason: CancelReason }, 10 | DetachmentStart, 11 | DetachmentReady, 12 | DetachmentComplete, 13 | DetachmentCancel { reason: CancelReason }, 14 | DetachmentCancelStart, 15 | DetachmentCancelComplete, 16 | DetachmentCancelTimeout, 17 | DetachmentUnexpected, 18 | AttachmentStart, 19 | AttachmentComplete, 20 | AttachmentTimeout, 21 | } 22 | 23 | impl dbus::arg::AppendAll for Event { 24 | fn append(&self, ia: &mut dbus::arg::IterAppend) { 25 | match self { 26 | Self::DetachmentInhibited { reason } => append1(ia, "detachment:inhibited", "reason", reason), 27 | Self::DetachmentStart => append0(ia, "detachment:start"), 28 | Self::DetachmentReady => append0(ia, "detachment:ready"), 29 | Self::DetachmentComplete => append0(ia, "detachment:complete"), 30 | Self::DetachmentCancel { reason } => append1(ia, "detachment:cancel", "reason", reason), 31 | Self::DetachmentCancelStart => append0(ia, "detachment:cancel:start"), 32 | Self::DetachmentCancelComplete => append0(ia, "detachment:cancel:complete"), 33 | Self::DetachmentCancelTimeout => append0(ia, "detachment:cancel:timeout"), 34 | Self::DetachmentUnexpected => append0(ia, "detachment:unexpected"), 35 | Self::AttachmentStart => append0(ia, "attachment:start"), 36 | Self::AttachmentComplete => append0(ia, "attachment:complete"), 37 | Self::AttachmentTimeout => append0(ia, "attachment:timeout"), 38 | } 39 | } 40 | } 41 | 42 | fn append0(ia: &mut dbus::arg::IterAppend, ty: &'static str) { 43 | let values: Dict>, _> = Dict::new(std::iter::empty()); 44 | 45 | ty.append(ia); 46 | values.append(ia); 47 | } 48 | 49 | fn append1(ia: &mut dbus::arg::IterAppend, ty: &'static str, name: &'static str, value: &T) 50 | where 51 | T: DbusArg, 52 | { 53 | ty.append(ia); 54 | 55 | ia.append_dict(&"s".into(), &"v".into(), |ia| { 56 | ia.append_dict_entry(|ia| { 57 | ia.append(name.to_owned()); 58 | ia.append(value.as_variant()); 59 | }) 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/service/mod.rs: -------------------------------------------------------------------------------- 1 | mod arg; 2 | use arg::DbusArg; 3 | 4 | mod event; 5 | pub use event::Event; 6 | 7 | mod prop; 8 | use prop::Property; 9 | 10 | 11 | use crate::logic::{ 12 | BaseInfo, 13 | BaseState, 14 | DeviceMode, 15 | DeviceType, 16 | LatchStatus, 17 | }; 18 | 19 | use std::collections::HashMap; 20 | use std::sync::Arc; 21 | 22 | use anyhow::{Context, Result}; 23 | 24 | use dbus::{Message, arg::{RefArg, Variant}}; 25 | use dbus::nonblock::SyncConnection; 26 | use dbus_crossroads::{Crossroads, IfaceBuilder, MethodErr}; 27 | 28 | use sdtx_tokio::Device; 29 | 30 | use tracing::trace; 31 | 32 | 33 | pub struct Service { 34 | conn: Arc, 35 | inner: Arc, 36 | } 37 | 38 | impl Service { 39 | const PATH: &'static str = "/org/surface/dtx"; 40 | const INTERFACE: &'static str = "org.surface.dtx"; 41 | 42 | pub fn new(conn: Arc, device: Device) -> Self { 43 | Self { conn, inner: Arc::new(Shared::new(device)) } 44 | } 45 | 46 | pub async fn request_name(&self) -> Result<()> { 47 | self.conn.request_name(Self::INTERFACE, false, true, false).await 48 | .context("Failed to set up D-Bus service") 49 | .map(|_| ()) 50 | } 51 | 52 | pub fn register(&self, cr: &mut Crossroads) -> Result<()> { 53 | let iface_token = cr.register(Self::INTERFACE, |b: &mut IfaceBuilder>| { 54 | // device-mode property 55 | b.property("DeviceMode") 56 | .emits_changed_true() 57 | .get(|_, service| { Ok(service.device_mode.as_arg()) }); 58 | 59 | // latch status 60 | b.property("LatchStatus") 61 | .emits_changed_true() 62 | .get(|_, service| Ok(service.latch_status.as_arg())); 63 | 64 | // base info 65 | b.property("Base") 66 | .emits_changed_true() 67 | .get(|_, service| Ok(service.base_info.as_arg())); 68 | 69 | // request method 70 | b.method("Request", (), (), move |_ctx, service, _args: ()| { 71 | match service.device.latch_request() { 72 | Ok(()) => { Ok(()) }, 73 | Err(e) => { Err(MethodErr::failed(&e)) }, 74 | } 75 | }); 76 | 77 | // event signal 78 | b.signal::<(String, HashMap>>), _> 79 | ("Event", ("type", "values")); 80 | }); 81 | 82 | cr.insert(Self::PATH, &[iface_token], self.inner.clone()); 83 | Ok(()) 84 | } 85 | 86 | pub fn unregister(&self, cr: &mut Crossroads) { 87 | let _ : Option> = cr.remove(&Self::PATH.into()); 88 | } 89 | 90 | pub fn handle(&self) -> ServiceHandle { 91 | ServiceHandle { conn: self.conn.clone(), inner: self.inner.clone() } 92 | } 93 | } 94 | 95 | 96 | #[derive(Clone)] 97 | pub struct ServiceHandle { 98 | conn: Arc, 99 | inner: Arc, 100 | } 101 | 102 | impl ServiceHandle { 103 | pub fn set_device_mode(&self, value: DeviceMode) { 104 | self.inner.device_mode.set(self.conn.as_ref(), value); 105 | } 106 | 107 | pub fn set_latch_status(&self, value: LatchStatus) { 108 | self.inner.latch_status.set(self.conn.as_ref(), value); 109 | } 110 | 111 | pub fn set_base_info(&self, value: BaseInfo) { 112 | self.inner.base_info.set(self.conn.as_ref(), value); 113 | } 114 | 115 | pub fn emit_event(&self, event: Event) { 116 | use dbus::channel::Sender; 117 | 118 | let path = Service::PATH.into(); 119 | let interface = Service::INTERFACE.into(); 120 | 121 | // build signal message 122 | let mut signal = Message::signal(&path, &interface, &"Event".into()); 123 | signal.append_all(event); 124 | 125 | trace!(target: "sdtxd::srvc", object=Service::PATH, interface=Service::INTERFACE, 126 | value=?event, "emmiting event"); 127 | 128 | // only fails when memory runs out 129 | self.conn.send(signal).unwrap(); 130 | } 131 | } 132 | 133 | 134 | struct Shared { 135 | device: Device, 136 | device_mode: Property, 137 | latch_status: Property, 138 | base_info: Property, 139 | } 140 | 141 | impl Shared { 142 | fn new(device: Device) -> Self { 143 | let base = BaseInfo { 144 | state: BaseState::Attached, 145 | device_type: DeviceType::Ssh, 146 | id: 0, 147 | }; 148 | 149 | Self { 150 | device, 151 | device_mode: Property::new("DeviceMode", DeviceMode::Laptop), 152 | latch_status: Property::new("LatchStatus", LatchStatus::Closed), 153 | base_info: Property::new("Base", base), 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/service/prop.rs: -------------------------------------------------------------------------------- 1 | use crate::service::Service; 2 | use crate::service::arg::DbusArg; 3 | 4 | use std::collections::HashMap; 5 | use std::sync::Mutex; 6 | 7 | use dbus::arg::Variant; 8 | 9 | use tracing::trace; 10 | 11 | 12 | #[derive(Debug)] 13 | pub struct Property { 14 | name: &'static str, 15 | value: Mutex, 16 | } 17 | 18 | impl Property { 19 | pub fn new(name: &'static str, value: T) -> Self { 20 | Self { name, value: Mutex::new(value) } 21 | } 22 | 23 | pub fn set(&self, conn: &C, value: T) 24 | where 25 | C: dbus::channel::Sender, 26 | T: DbusArg + PartialEq + std::fmt::Debug, 27 | { 28 | // update stored value and get variant 29 | let value = { 30 | let mut stored = self.value.lock().unwrap(); 31 | 32 | // check for actual change 33 | if *stored == value { 34 | return; 35 | } 36 | 37 | trace!(target: "sdtxd::srvc", object=Service::PATH, interface=Service::INTERFACE, 38 | name=self.name, old=?*stored, new=?value, "changing property"); 39 | 40 | *stored = value; 41 | stored.as_variant() 42 | }; 43 | 44 | // signal property changed 45 | use dbus::arg::RefArg; 46 | use dbus::message::SignalArgs; 47 | use dbus::ffidisp::stdintf::org_freedesktop_dbus as dbffi; 48 | use dbffi::PropertiesPropertiesChanged as PropertiesChanged; 49 | 50 | let mut changed: HashMap>> = HashMap::new(); 51 | changed.insert(self.name.into(), value); 52 | 53 | let changed = PropertiesChanged { 54 | interface_name: Service::INTERFACE.into(), 55 | changed_properties: changed, 56 | invalidated_properties: Vec::new(), 57 | }; 58 | 59 | let msg = changed.to_emit_message(&Service::PATH.into()); 60 | 61 | // send will only fail due to lack of memory 62 | conn.send(msg).unwrap(); 63 | } 64 | } 65 | 66 | impl DbusArg for Property 67 | where 68 | T: DbusArg 69 | { 70 | type Arg = T::Arg; 71 | 72 | fn as_arg(&self) -> Self::Arg { 73 | self.value.lock().unwrap().as_arg() 74 | } 75 | } 76 | 77 | impl std::ops::Deref for Property { 78 | type Target = Mutex; 79 | 80 | fn deref(&self) -> &Self::Target { 81 | &self.value 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod tracing; 3 | 4 | pub mod scope; 5 | pub mod task; 6 | pub mod taskq; 7 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/utils/scope.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct ScopeGuard { 3 | callback: Option, 4 | } 5 | 6 | impl Drop for ScopeGuard { 7 | fn drop(&mut self) { 8 | self.callback.take().unwrap()(); 9 | } 10 | } 11 | 12 | pub fn guard(callback: F) -> ScopeGuard { 13 | ScopeGuard { callback: Some(callback) } 14 | } 15 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/utils/task.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | 6 | use tokio::task::{JoinError, JoinHandle}; 7 | 8 | 9 | /// Ensures that all tasks spawned by a future will be canceled when the future 10 | /// (i.e. created by an async fn) is dropped or when it completes. 11 | pub struct JoinGuard { 12 | inner: JoinHandle, 13 | } 14 | 15 | impl Drop for JoinGuard { 16 | fn drop(&mut self) { 17 | self.inner.abort(); 18 | } 19 | } 20 | 21 | impl Deref for JoinGuard { 22 | type Target = JoinHandle; 23 | 24 | fn deref(&self) -> &Self::Target { 25 | &self.inner 26 | } 27 | } 28 | 29 | impl DerefMut for JoinGuard { 30 | fn deref_mut(&mut self) -> &mut Self::Target { 31 | &mut self.inner 32 | } 33 | } 34 | 35 | impl Future for JoinGuard { 36 | type Output = std::result::Result; 37 | 38 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 39 | std::pin::Pin::new(&mut self.inner).poll(cx) 40 | } 41 | } 42 | 43 | 44 | pub trait JoinHandleExt { 45 | fn guard(self) -> JoinGuard; 46 | } 47 | 48 | impl JoinHandleExt for JoinHandle { 49 | fn guard(self) -> JoinGuard { 50 | JoinGuard { inner: self } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/utils/taskq.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::pin::Pin; 3 | 4 | use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; 5 | use tokio::sync::mpsc::error::SendError; 6 | 7 | use tracing::trace; 8 | 9 | 10 | pub type Task = Pin> + Send>>; 11 | 12 | 13 | #[derive(Debug)] 14 | pub struct TaskQueue { 15 | rx: UnboundedReceiver>, 16 | } 17 | 18 | impl TaskQueue { 19 | pub async fn run(&mut self) -> Result<(), E> { 20 | while let Some(task) = self.rx.recv().await { 21 | trace!(target: "sdtxd::tq", "running next task"); 22 | let result = task.await; 23 | trace!(target: "sdtxd::tq", "task completed"); 24 | result?; 25 | } 26 | 27 | Ok(()) 28 | } 29 | } 30 | 31 | 32 | #[derive(Debug, Clone)] 33 | pub struct TaskSender { 34 | tx: UnboundedSender>, 35 | } 36 | 37 | impl TaskSender { 38 | pub fn submit(&self, task: T) -> Result<(), SendError>> 39 | where 40 | T: Future> + Send + 'static 41 | { 42 | trace!(target: "sdtxd::tq", "submitting new task"); 43 | self.tx.send(Box::pin(task)) 44 | } 45 | } 46 | 47 | 48 | pub fn new() -> (TaskQueue, TaskSender) { 49 | let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); 50 | 51 | (TaskQueue { rx }, TaskSender { tx }) 52 | } 53 | -------------------------------------------------------------------------------- /surface-dtx-daemon/src/utils/tracing.rs: -------------------------------------------------------------------------------- 1 | 2 | // for logging with dynamic level 3 | macro_rules! event { 4 | (target: $target:expr, $lvl:expr, $($k:ident).+ = $($fields:tt)* ) => { 5 | match $lvl { 6 | ::tracing::Level::ERROR => ::tracing::event!(target: $target, ::tracing::Level::ERROR, $($k).+ = $($fields)*), 7 | ::tracing::Level::WARN => ::tracing::event!(target: $target, ::tracing::Level::WARN, $($k).+ = $($fields)*), 8 | ::tracing::Level::INFO => ::tracing::event!(target: $target, ::tracing::Level::INFO, $($k).+ = $($fields)*), 9 | ::tracing::Level::DEBUG => ::tracing::event!(target: $target, ::tracing::Level::DEBUG, $($k).+ = $($fields)*), 10 | ::tracing::Level::TRACE => ::tracing::event!(target: $target, ::tracing::Level::TRACE, $($k).+ = $($fields)*), 11 | } 12 | }; 13 | 14 | (target: $target:expr, $lvl:expr, $($arg:tt)+ ) => { 15 | match $lvl { 16 | ::tracing::Level::ERROR => ::tracing::event!(target: $target, ::tracing::Level::ERROR, $($arg)+), 17 | ::tracing::Level::WARN => ::tracing::event!(target: $target, ::tracing::Level::WARN, $($arg)+), 18 | ::tracing::Level::INFO => ::tracing::event!(target: $target, ::tracing::Level::INFO, $($arg)+), 19 | ::tracing::Level::DEBUG => ::tracing::event!(target: $target, ::tracing::Level::DEBUG, $($arg)+), 20 | ::tracing::Level::TRACE => ::tracing::event!(target: $target, ::tracing::Level::TRACE, $($arg)+), 21 | } 22 | }; 23 | 24 | ($lvl:expr, $($k:ident).+ = $($field:tt)*) => { 25 | match $lvl { 26 | ::tracing::Level::ERROR => ::tracing::event!(::tracing::Level::ERROR, $($k).+ = $($field)*), 27 | ::tracing::Level::WARN => ::tracing::event!(::tracing::Level::WARN, $($k).+ = $($field)*), 28 | ::tracing::Level::INFO => ::tracing::event!(::tracing::Level::INFO, $($k).+ = $($field)*), 29 | ::tracing::Level::DEBUG => ::tracing::event!(::tracing::Level::DEBUG, $($k).+ = $($field)*), 30 | ::tracing::Level::TRACE => ::tracing::event!(::tracing::Level::TRACE, $($k).+ = $($field)*), 31 | } 32 | }; 33 | 34 | ( $lvl:expr, $($arg:tt)+ ) => { 35 | match $lvl { 36 | ::tracing::Level::ERROR => ::tracing::event!(::tracing::Level::ERROR, $($arg)+), 37 | ::tracing::Level::WARN => ::tracing::event!(::tracing::Level::WARN, $($arg)+), 38 | ::tracing::Level::INFO => ::tracing::event!(::tracing::Level::INFO, $($arg)+), 39 | ::tracing::Level::DEBUG => ::tracing::event!(::tracing::Level::DEBUG, $($arg)+), 40 | ::tracing::Level::TRACE => ::tracing::event!(::tracing::Level::TRACE, $($arg)+), 41 | } 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /surface-dtx-userd/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "surface-dtx-userd" 3 | version = "0.3.9" 4 | authors = ["Maximilian Luz "] 5 | description = "Surface Detachment System (DTX) User Daemon" 6 | 7 | repository = "https://github.com/linux-surface/surface-dtx-daemon/" 8 | license = "MIT" 9 | 10 | edition = "2018" 11 | build = "build.rs" 12 | 13 | [dependencies] 14 | anyhow = "1.0.98" 15 | clap = { version = "4.5.37", features = ["cargo"] } 16 | dbus = "0.9.7" 17 | dbus-tokio = "0.7.6" 18 | futures = "0.3.31" 19 | serde = { version = "1.0.219", features = ["derive"] } 20 | serde_ignored = "0.1.11" 21 | tokio = { version = "1.44.2", features = ["macros", "rt", "signal"] } 22 | toml = "0.8.20" 23 | tracing = "0.1.41" 24 | tracing-subscriber = { version = "0.3.19", features = ["std", "env-filter"] } 25 | 26 | [build-dependencies] 27 | clap = "4.5.37" 28 | clap_complete = "4.5.47" 29 | -------------------------------------------------------------------------------- /surface-dtx-userd/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | use clap_complete::shells; 4 | 5 | include!("src/cli.rs"); 6 | 7 | 8 | fn main() { 9 | let outdir: PathBuf = env::var_os("CARGO_TARGET_DIR") 10 | .or_else(|| env::var_os("OUT_DIR")) 11 | .unwrap() 12 | .into(); 13 | 14 | let rootdir = env::current_dir().unwrap(); 15 | let rootdir = rootdir 16 | .parent().unwrap(); 17 | 18 | println!("{rootdir:?}"); 19 | 20 | // generate shell completions 21 | let mut app = app(); 22 | clap_complete::generate_to(shells::Bash, &mut app, "surface-dtx-userd", &outdir).unwrap(); 23 | clap_complete::generate_to(shells::Zsh, &mut app, "surface-dtx-userd", &outdir).unwrap(); 24 | clap_complete::generate_to(shells::Fish, &mut app, "surface-dtx-userd", &outdir).unwrap(); 25 | 26 | // copy config files 27 | let files = [ 28 | "etc/dtx/surface-dtx-userd.conf", 29 | "etc/systemd/surface-dtx-userd.service", 30 | ]; 31 | 32 | for file in files { 33 | let src = rootdir.join(file); 34 | let tgt = outdir.join(file); 35 | 36 | std::fs::create_dir_all(tgt.parent().unwrap()).unwrap(); 37 | std::fs::copy(src, tgt).unwrap(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /surface-dtx-userd/src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{Arg, Command, ArgAction}; 2 | 3 | pub fn app() -> Command { 4 | Command::new("Surface DTX User Daemon") 5 | .about(clap::crate_description!()) 6 | .version(clap::crate_version!()) 7 | .author(clap::crate_authors!()) 8 | .arg(Arg::new("config") 9 | .short('c') 10 | .long("config") 11 | .value_name("FILE") 12 | .help("Use the specified config file") 13 | .value_parser(clap::value_parser!(std::path::PathBuf))) 14 | .arg(Arg::new("no-log-time") 15 | .long("no-log-time") 16 | .help("Do not emit timestamps in log") 17 | .action(ArgAction::SetTrue)) 18 | } 19 | -------------------------------------------------------------------------------- /surface-dtx-userd/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use anyhow::{Context, Result}; 5 | use serde::{Deserialize, Serialize}; 6 | use tracing::{debug, warn}; 7 | 8 | 9 | const SYSTEM_CONFIG_PATH: &str = "/etc/surface-dtx/surface-dtx-userd.conf"; 10 | const USER_CONFIG_LOCAL_PATH: &str = "surface-dtx/surface-dtx-userd.conf"; 11 | 12 | 13 | #[derive(Debug, Serialize, Deserialize, Default, Clone)] 14 | pub struct Config { 15 | #[serde(skip)] 16 | pub dir: PathBuf, 17 | 18 | #[serde(default)] 19 | pub log: Log, 20 | } 21 | 22 | #[derive(Debug, Serialize, Deserialize, Default, Clone)] 23 | pub struct Log { 24 | #[serde(default)] 25 | pub level: LogLevel, 26 | } 27 | 28 | #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)] 29 | #[serde(rename_all="lowercase")] 30 | pub enum LogLevel { 31 | Error, 32 | Warn, 33 | #[default] 34 | Info, 35 | Debug, 36 | Trace, 37 | } 38 | 39 | 40 | impl Config { 41 | pub fn load() -> Result<(Config, Diagnostics)> { 42 | let mut user_config = std::env::var_os("XDG_CONFIG_HOME") 43 | .and_then(|d| if !d.is_empty() { Some(d) } else { None }) 44 | .map(PathBuf::from) 45 | .unwrap_or_else(|| PathBuf::from("~/.config")); 46 | user_config.push(USER_CONFIG_LOCAL_PATH); 47 | 48 | if user_config.exists() { 49 | Config::load_file(user_config) 50 | } else if Path::new(SYSTEM_CONFIG_PATH).exists() { 51 | Config::load_file(SYSTEM_CONFIG_PATH) 52 | } else { 53 | Ok((Config::default(), Diagnostics::empty())) 54 | } 55 | } 56 | 57 | pub fn load_file>(path: P) -> Result<(Config, Diagnostics)> { 58 | use std::io::Read; 59 | 60 | let mut buf = Vec::new(); 61 | let mut file = std::fs::File::open(path.as_ref()) 62 | .context("Failed to open config file")?; 63 | 64 | file.read_to_end(&mut buf) 65 | .with_context(|| format!("Failed to read config file (path: {:?})", path.as_ref()))?; 66 | 67 | let data = std::str::from_utf8(&buf) 68 | .with_context(|| format!("Failed to read config file (path: {:?})", path.as_ref()))?; 69 | 70 | let de = toml::Deserializer::new(data); 71 | 72 | let mut unknowns = BTreeSet::new(); 73 | let mut config: Config = serde_ignored::deserialize(de, |path| { 74 | unknowns.insert(path.to_string()); 75 | }).with_context(|| format!("Failed to read config file (path: {:?})", path.as_ref()))?; 76 | 77 | config.dir = path.as_ref().parent().unwrap().into(); 78 | 79 | let diag = Diagnostics { 80 | path: path.as_ref().into(), 81 | unknowns, 82 | }; 83 | 84 | Ok((config, diag)) 85 | } 86 | } 87 | 88 | 89 | pub struct Diagnostics { 90 | pub path: PathBuf, 91 | pub unknowns: BTreeSet, 92 | } 93 | 94 | impl Diagnostics { 95 | fn empty() -> Self { 96 | Diagnostics { 97 | path: PathBuf::new(), 98 | unknowns: BTreeSet::new() 99 | } 100 | } 101 | 102 | pub fn log(&self) { 103 | let span = tracing::info_span!("config", file=?self.path); 104 | let _guard = span.enter(); 105 | 106 | debug!(target: "sdtxu::config", "configuration loaded"); 107 | for item in &self.unknowns { 108 | warn!(target: "sdtxu::config", item = %item, "unknown config item") 109 | } 110 | } 111 | } 112 | 113 | 114 | impl From for tracing::Level { 115 | fn from(level: LogLevel) -> Self { 116 | match level { 117 | LogLevel::Error => tracing::Level::ERROR, 118 | LogLevel::Warn => tracing::Level::WARN, 119 | LogLevel::Info => tracing::Level::INFO, 120 | LogLevel::Debug => tracing::Level::DEBUG, 121 | LogLevel::Trace => tracing::Level::TRACE, 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /surface-dtx-userd/src/logic/core.rs: -------------------------------------------------------------------------------- 1 | use crate::logic::{CancelReason, Event}; 2 | use crate::utils::notify::{Notification, NotificationHandle, Timeout}; 3 | 4 | use std::borrow::Cow; 5 | use std::sync::Arc; 6 | 7 | use anyhow::{Context, Result}; 8 | 9 | use dbus::nonblock::SyncConnection; 10 | 11 | use tracing::{debug, trace}; 12 | 13 | 14 | pub struct Core { 15 | session: Arc, 16 | canceled: bool, 17 | notif: Option, 18 | } 19 | 20 | impl Core { 21 | pub fn new(session: Arc) -> Self { 22 | Core { 23 | session, 24 | canceled: false, 25 | notif: None, 26 | } 27 | } 28 | 29 | pub async fn handle(&mut self, event: Event) -> Result<()> { 30 | debug!(target: "sdtxu::core", ?event, "event received"); 31 | 32 | match event { 33 | Event::DetachmentInhibited { reason } => self.on_detachment_inhibited(reason).await, 34 | Event::DetachmentStart => self.on_detachment_start().await, 35 | Event::DetachmentReady => self.on_detachment_ready().await, 36 | Event::DetachmentComplete => self.on_detachment_complete().await, 37 | Event::DetachmentCancel { reason } => self.on_detachment_cancel(reason).await, 38 | Event::DetachmentCancelTimeout => self.on_detachment_cancel_timeout().await, 39 | Event::DetachmentUnexpected => self.on_detachment_unexpected().await, 40 | Event::AttachmentComplete => self.on_attachment_complete().await, 41 | Event::AttachmentTimeout => self.on_attachment_timeout().await, 42 | _ => Ok(()), 43 | } 44 | } 45 | 46 | async fn on_detachment_inhibited(&mut self, reason: CancelReason) -> Result<()> { 47 | let (category, summary, body): (_, _, Cow<'static, str>) = match reason { 48 | CancelReason::Runtime(err) => match err { 49 | super::types::RuntimeError::NotFeasible => ( 50 | "device", 51 | "Surface DTX: Cannot detach", 52 | "Detachment inhibited by the controller. \ 53 | Please make sure that the tablet battery is sufficently charged." 54 | .into() 55 | ), 56 | super::types::RuntimeError::Unknown(x) => ( 57 | "device.error", 58 | "Surface DTX: Error", 59 | format!("Detachment inhibited due to unknown runtime error ({x}).") 60 | .into() 61 | ), 62 | _ => { return Ok(()); }, 63 | }, 64 | CancelReason::Hardware(err) => match err { 65 | super::types::HardwareError::FailedToOpen => ( 66 | "device.error", 67 | "Surface DTX: Error", 68 | "Hardware error: The controller failed to open the latch." 69 | .into() 70 | ), 71 | super::types::HardwareError::FailedToRemainOpen => ( 72 | "device.error", 73 | "Surface DTX: Error", 74 | "Hardware error: The controller failed to keep the latch open." 75 | .into() 76 | ), 77 | super::types::HardwareError::FailedToClose => ( 78 | "device.error", 79 | "Surface DTX: Error", 80 | "Hardware error: The controller failed to close the latch." 81 | .into() 82 | ), 83 | super::types::HardwareError::Unknown(x) => ( 84 | "device.error", 85 | "Surface DTX: Error", 86 | format!("Detachment inhibited due to unknown hardware error ({x}).") 87 | .into() 88 | ), 89 | }, 90 | CancelReason::Unknown(x) => ( 91 | "device.error", 92 | "Surface DTX: Error", 93 | format!("Detachment inhibited due to unknown error ({x}).") 94 | .into() 95 | ), 96 | _ => { return Ok(()); }, 97 | }; 98 | 99 | let handle = Notification::create("Surface DTX") 100 | .summary(summary) 101 | .body(body) 102 | .hint_s("image-path", "input-tablet") 103 | .hint_s("category", category) 104 | .hint("urgency", 2) 105 | .build() 106 | .show(&self.session).await 107 | .context("Failed to display notification")?; 108 | 109 | trace!(target: "sdtxu::notify", id = handle.id, ty = "detach-inhibited", 110 | "displaying notification"); 111 | 112 | Ok(()) 113 | } 114 | 115 | async fn on_detachment_start(&mut self) -> Result<()> { 116 | // reset state 117 | self.close_current_notification().await?; 118 | self.canceled = false; 119 | 120 | Ok(()) 121 | } 122 | 123 | async fn on_detachment_ready(&mut self) -> Result<()> { 124 | if self.canceled { 125 | return Ok(()); 126 | } 127 | 128 | // display detachment-ready notification 129 | let handle = Notification::create("Surface DTX") 130 | .summary("Surface DTX: Clipboard can be detached") 131 | .body("You can disconnect the clipboard now.") 132 | .hint_s("image-path", "input-tablet") 133 | .hint_s("category", "device.removed") 134 | .hint("urgency", 2) 135 | .hint("resident", true) 136 | .expires(Timeout::Never) 137 | .build() 138 | .show(&self.session).await 139 | .context("Failed to display notification")?; 140 | 141 | trace!(target: "sdtxu::notify", id = handle.id, ty = "detach-ready", 142 | "displaying notification"); 143 | 144 | self.notif = Some(handle); 145 | Ok(()) 146 | } 147 | 148 | async fn on_detachment_complete(&mut self) -> Result<()> { 149 | // close detachment-ready notification 150 | self.close_current_notification().await 151 | } 152 | 153 | async fn on_detachment_cancel(&mut self, reason: CancelReason) -> Result<()> { 154 | // close detachment-ready notification 155 | self.close_current_notification().await?; 156 | 157 | // mark ourselves as canceled and prevent new detachment-ready notifications 158 | self.canceled = true; 159 | 160 | let (category, summary, body): (_, _, Cow<'static, str>) = match reason { 161 | CancelReason::HandlerTimeout => ( 162 | "device.error", 163 | "Surface DTX: Error", 164 | "Detachment canceled due to handler timeout. \ 165 | This may lead to data loss! \ 166 | Please consult the logs for mode details." 167 | .into() 168 | ), 169 | CancelReason::Runtime(err) => match err { 170 | super::types::RuntimeError::NotFeasible => ( 171 | "device", 172 | "Surface DTX: Detachment canceled", 173 | "Detachment canceled by the controller. \ 174 | Please make sure that the tablet battery is sufficently charged." 175 | .into() 176 | ), 177 | super::types::RuntimeError::Timeout => ( 178 | "device.error", 179 | "Surface DTX: Detachment canceled", 180 | "The detachment process has timed out while the base was locked. \ 181 | Please ensure that the detachment handler is set up correctly." 182 | .into() 183 | ), 184 | super::types::RuntimeError::Unknown(x) => ( 185 | "device.error", 186 | "Surface DTX: Error", 187 | format!("Detachment canceled due to unknown runtime error ({x}).") 188 | .into() 189 | ), 190 | _ => { return Ok(()); }, 191 | }, 192 | CancelReason::Hardware(err) => match err { 193 | super::types::HardwareError::FailedToOpen => ( 194 | "device.error", 195 | "Surface DTX: Error", 196 | "Hardware error: The controller failed to open the latch." 197 | .into() 198 | ), 199 | super::types::HardwareError::FailedToRemainOpen => ( 200 | "device.error", 201 | "Surface DTX: Error", 202 | "Hardware error: The controller failed to keep the latch open." 203 | .into() 204 | ), 205 | super::types::HardwareError::FailedToClose => ( 206 | "device.error", 207 | "Surface DTX: Error", 208 | "Hardware error: The controller failed to close the latch." 209 | .into() 210 | ), 211 | super::types::HardwareError::Unknown(x) => ( 212 | "device.error", 213 | "Surface DTX: Error", 214 | format!("Detachment canceled due to unknown hardware error ({x}).") 215 | .into() 216 | ), 217 | }, 218 | CancelReason::Unknown(x) => ( 219 | "device.error", 220 | "Surface DTX: Error", 221 | format!("Detachment canceled due to unknown error ({x}).") 222 | .into() 223 | ), 224 | _ => { return Ok(()); }, 225 | }; 226 | 227 | let handle = Notification::create("Surface DTX") 228 | .summary(summary) 229 | .body(body) 230 | .hint_s("image-path", "input-tablet") 231 | .hint_s("category", category) 232 | .hint("urgency", 2) 233 | .build() 234 | .show(&self.session).await 235 | .context("Failed to display notification")?; 236 | 237 | trace!(target: "sdtxu::notify", id = handle.id, ty = "detach-cancel", 238 | "displaying notification"); 239 | 240 | Ok(()) 241 | } 242 | 243 | async fn on_detachment_cancel_timeout(&mut self) -> Result<()> { 244 | let handle = Notification::create("Surface DTX") 245 | .summary("Surface DTX: Error") 246 | .body("The detachment cancellation handler has timed out. \ 247 | This may lead to data loss! \ 248 | Please consult the logs for more details.") 249 | .hint_s("image-path", "input-tablet") 250 | .hint_s("category", "device.error") 251 | .hint("urgency", 2) 252 | .build() 253 | .show(&self.session).await 254 | .context("Failed to display notification")?; 255 | 256 | trace!(target: "sdtxu::notify", id = handle.id, ty = "detach-cancel-timeout", 257 | "displaying notification"); 258 | 259 | Ok(()) 260 | } 261 | 262 | async fn on_detachment_unexpected(&mut self) -> Result<()> { 263 | let handle = Notification::create("Surface DTX") 264 | .summary("Surface DTX: Error") 265 | .body("Base disconnected unexpectedly. \ 266 | This may lead to data loss! \ 267 | Please consult the logs for more details.") 268 | .hint_s("image-path", "input-tablet") 269 | .hint_s("category", "device.error") 270 | .hint("urgency", 2) 271 | .build() 272 | .show(&self.session).await 273 | .context("Failed to display notification")?; 274 | 275 | trace!(target: "sdtxu::notify", id = handle.id, ty = "detach-unexpected", 276 | "displaying notification"); 277 | 278 | Ok(()) 279 | } 280 | 281 | async fn on_attachment_complete(&mut self) -> Result<()> { 282 | let handle = Notification::create("Surface DTX") 283 | .summary("Surface DTX: Base attached") 284 | .body("The base has been successfully attached and is ready.") 285 | .hint_s("image-path", "input-tablet") 286 | .hint_s("category", "device.added") 287 | .hint("transient", true) 288 | .build() 289 | .show(&self.session).await 290 | .context("Failed to display notification")?; 291 | 292 | trace!(target: "sdtxu::notify", id = handle.id, ty = "attach-complete", 293 | "displaying notification"); 294 | 295 | Ok(()) 296 | } 297 | 298 | async fn on_attachment_timeout(&mut self) -> Result<()> { 299 | let handle = Notification::create("Surface DTX") 300 | .summary("Surface DTX: Error") 301 | .body("The attachment handler has timed out. \ 302 | Please consult the logs for more details.") 303 | .hint_s("image-path", "input-tablet") 304 | .hint_s("category", "device.error") 305 | .hint("urgency", 2) 306 | .build() 307 | .show(&self.session).await 308 | .context("Failed to display notification")?; 309 | 310 | trace!(target: "sdtxu::notify", id = handle.id, ty = "attach-timeout", 311 | "displaying notification"); 312 | 313 | Ok(()) 314 | } 315 | 316 | async fn close_current_notification(&mut self) -> Result<()> { 317 | match self.notif { 318 | Some(handle) => { 319 | trace!(target: "sdtxu::notify", id = handle.id, "closing notification"); 320 | 321 | handle.close(&self.session).await 322 | .context("Failed to close notification") 323 | }, 324 | None => Ok(()), 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /surface-dtx-userd/src/logic/mod.rs: -------------------------------------------------------------------------------- 1 | mod core; 2 | use self::core::Core; 3 | 4 | mod types; 5 | pub use self::types::{CancelReason, Event}; 6 | 7 | 8 | use crate::utils::task::JoinHandleExt; 9 | 10 | use anyhow::{Context, Result}; 11 | 12 | use dbus::message::MatchRule; 13 | use dbus_tokio::connection; 14 | 15 | use futures::prelude::*; 16 | 17 | use tracing::trace; 18 | 19 | 20 | pub async fn run() -> Result<()> { 21 | // set up and start D-Bus connections (system and user-session) 22 | let (sys_rsrc, sys_conn) = connection::new_system_sync() 23 | .context("Failed to connect to D-Bus (system)")?; 24 | 25 | let (ses_rsrc, ses_conn) = connection::new_session_sync() 26 | .context("Failed to connect to D-Bus (session)")?; 27 | 28 | let sys_rsrc = sys_rsrc.map(|e| Err(e).context("D-Bus connection error (system)")); 29 | let ses_rsrc = ses_rsrc.map(|e| Err(e).context("D-Bus connection error (session)")); 30 | 31 | let mut dsys_task = tokio::spawn(sys_rsrc).guard(); 32 | let mut dses_task = tokio::spawn(ses_rsrc).guard(); 33 | 34 | // set up D-Bus message listener task 35 | let mut main_task = tokio::spawn(async move { 36 | let mut core = Core::new(ses_conn); 37 | 38 | let mr = MatchRule::new_signal("org.surface.dtx", "Event"); 39 | let (_msgs, mut stream) = sys_conn 40 | .add_match(mr).await 41 | .context("Failed to set up D-Bus connection")? 42 | .msg_stream(); 43 | 44 | while let Some(mut msg) = stream.next().await { 45 | trace!(target: "sdtxu::core", message = ?msg, "message received"); 46 | 47 | let msg = msg.as_result().context("D-Bus remote error")?; 48 | let evt = Event::try_from_message(msg)?; 49 | 50 | if let Some(evt) = evt { 51 | core.handle(evt).await?; 52 | } 53 | } 54 | 55 | Ok(()) 56 | }).guard(); 57 | 58 | // wait for error, panic, or shutdown signal 59 | let result = tokio::select! { 60 | result = &mut main_task => result, 61 | result = &mut dsys_task => result, 62 | result = &mut dses_task => result, 63 | }; 64 | 65 | // (try to) shut down all active tasks 66 | main_task.abort(); 67 | dses_task.abort(); 68 | dsys_task.abort(); 69 | 70 | // handle subtask result, propagate resume unwind panic 71 | match result { 72 | Ok(result) => result, 73 | Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()), 74 | Err(_) => unreachable!("Subtask canceled before completing parent task"), 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /surface-dtx-userd/src/logic/types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::convert::TryFrom; 3 | use std::str::FromStr; 4 | 5 | use anyhow::{Context, Error, Result}; 6 | 7 | use dbus::{Message, MessageType}; 8 | use dbus::arg::{Variant, RefArg}; 9 | 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | pub enum Event { 13 | DetachmentInhibited { reason: CancelReason }, 14 | DetachmentStart, 15 | DetachmentReady, 16 | DetachmentComplete, 17 | DetachmentCancel { reason: CancelReason }, 18 | DetachmentCancelStart, 19 | DetachmentCancelComplete, 20 | DetachmentCancelTimeout, 21 | DetachmentUnexpected, 22 | AttachmentStart, 23 | AttachmentComplete, 24 | AttachmentTimeout, 25 | } 26 | 27 | impl Event { 28 | pub fn match_message(msg: &Message) -> bool { 29 | msg.msg_type() == MessageType::Signal 30 | && msg.path() == Some("/org/surface/dtx".into()) 31 | && msg.interface() == Some("org.surface.dtx".into()) 32 | && msg.member() == Some("Event".into()) 33 | } 34 | 35 | pub fn try_from_message(msg: &Message) -> Result> { 36 | if Self::match_message(msg) { 37 | Self::from_message(msg).map(Some) 38 | } else { 39 | Ok(None) 40 | } 41 | } 42 | 43 | #[allow(clippy::type_complexity)] 44 | pub fn from_message(msg: &Message) -> Result { 45 | let (ty, args): (&str, HashMap<&str, Variant>>) = msg.read2() 46 | .context("Protocol error")?; 47 | 48 | let event = match ty { 49 | "detachment:inhibited" => { 50 | let reason = args.get("reason") 51 | .ok_or_else(|| anyhow::anyhow!("Missing argument: reason")) 52 | .and_then(CancelReason::try_from) 53 | .context("Protocol error")?; 54 | 55 | Event::DetachmentInhibited { reason } 56 | }, 57 | "detachment:start" => { 58 | Event::DetachmentStart 59 | }, 60 | "detachment:ready" => { 61 | Event::DetachmentReady 62 | }, 63 | "detachment:complete" => { 64 | Event::DetachmentComplete 65 | }, 66 | "detachment:cancel" => { 67 | let reason = args.get("reason") 68 | .ok_or_else(|| anyhow::anyhow!("Missing argument: reason")) 69 | .and_then(CancelReason::try_from) 70 | .context("Protocol error")?; 71 | 72 | Event::DetachmentCancel { reason } 73 | }, 74 | "detachment:cancel:start" => { 75 | Event::DetachmentCancelStart 76 | }, 77 | "detachment:cancel:complete" => { 78 | Event::DetachmentCancelComplete 79 | }, 80 | "detachment:cancel:timeout" => { 81 | Event::DetachmentCancelTimeout 82 | }, 83 | "detachment:unexpected" => { 84 | Event::DetachmentUnexpected 85 | }, 86 | "attachment:start" => { 87 | Event::AttachmentStart 88 | }, 89 | "attachment:complete" => { 90 | Event::AttachmentComplete 91 | }, 92 | "attachment:timeout" => { 93 | Event::AttachmentTimeout 94 | }, 95 | _ => { 96 | Err(anyhow::anyhow!("Unsupported event type: {}", ty)) 97 | .context("Protocol error")? 98 | }, 99 | }; 100 | 101 | Ok(event) 102 | } 103 | } 104 | 105 | impl TryFrom<&Message> for Event { 106 | type Error = Error; 107 | 108 | fn try_from(msg: &Message) -> Result { 109 | Self::from_message(msg) 110 | } 111 | } 112 | 113 | 114 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 115 | pub enum CancelReason { 116 | UserRequest, 117 | HandlerTimeout, 118 | DisconnectTimeout, 119 | Runtime(RuntimeError), 120 | Hardware(HardwareError), 121 | Unknown(u16), 122 | } 123 | 124 | impl FromStr for CancelReason { 125 | type Err = Error; 126 | 127 | fn from_str(s: &str) -> Result { 128 | match s { 129 | "request" => Ok(Self::UserRequest), 130 | "timeout:handler" => Ok(Self::HandlerTimeout), 131 | "timeout:disconnect" => Ok(Self::DisconnectTimeout), 132 | _ if s.starts_with("error:runtime") => Ok(Self::Runtime(RuntimeError::from_str(s)?)), 133 | _ if s.starts_with("error:hardware") => Ok(Self::Hardware(HardwareError::from_str(s)?)), 134 | _ if s.starts_with("unknown:") => { 135 | let value = s.strip_prefix("unknown:") 136 | .unwrap_or("") 137 | .parse() 138 | .context("Failed to parse unknown cancel reason") 139 | .context("Protocol error")?; 140 | 141 | Ok(Self::Unknown(value)) 142 | }, 143 | _ => { 144 | Err(anyhow::anyhow!("Unknown cancel reason: {}", s)) 145 | .context("Protocol error") 146 | }, 147 | } 148 | } 149 | } 150 | 151 | impl TryFrom<&Variant>> for CancelReason { 152 | type Error = Error; 153 | 154 | fn try_from(value: &Variant>) -> Result { 155 | let value = value.as_str() 156 | .ok_or_else(|| anyhow::anyhow!("Invalid value type: {:?}", value)) 157 | .context("Protocol error")?; 158 | 159 | Self::from_str(value) 160 | } 161 | } 162 | 163 | 164 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 165 | pub enum RuntimeError { 166 | NotAttached, 167 | NotFeasible, 168 | Timeout, 169 | Unknown(u8), 170 | } 171 | 172 | impl FromStr for RuntimeError { 173 | type Err = Error; 174 | 175 | fn from_str(s: &str) -> Result { 176 | match s { 177 | "error:runtime:not-attached" => Ok(Self::NotAttached), 178 | "error:runtime:not-feasible" => Ok(Self::NotFeasible), 179 | "error:runtime:timeout" => Ok(Self::Timeout), 180 | _ if s.starts_with("error:runtime:unknown:") => { 181 | let value = s.strip_prefix("error:runtime:unknown:") 182 | .unwrap_or("") 183 | .parse() 184 | .context("Failed to parse unknown runtime error value") 185 | .context("Protocol error")?; 186 | 187 | Ok(Self::Unknown(value)) 188 | }, 189 | _ => { 190 | Err(anyhow::anyhow!("Unknown runtime error value: {}", s)) 191 | .context("Protocol error") 192 | }, 193 | } 194 | } 195 | } 196 | 197 | 198 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 199 | pub enum HardwareError { 200 | FailedToOpen, 201 | FailedToRemainOpen, 202 | FailedToClose, 203 | Unknown(u8), 204 | } 205 | 206 | impl FromStr for HardwareError { 207 | type Err = Error; 208 | 209 | fn from_str(s: &str) -> Result { 210 | match s { 211 | "error:hardware:failed-to-open" => Ok(Self::FailedToOpen), 212 | "error:hardware:failed-to-remain-open" => Ok(Self::FailedToRemainOpen), 213 | "error:hardware:failed-to-close" => Ok(Self::FailedToClose), 214 | _ if s.starts_with("error:hardware:unknown:") => { 215 | let value = s.strip_prefix("error:hardware:unknown:") 216 | .unwrap_or("") 217 | .parse() 218 | .context("Failed to parse unknown hardware error value") 219 | .context("Protocol error")?; 220 | 221 | Ok(Self::Unknown(value)) 222 | }, 223 | _ => { 224 | Err(anyhow::anyhow!("Unknown hardware error value: {}", s)) 225 | .context("Protocol error") 226 | }, 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /surface-dtx-userd/src/main.rs: -------------------------------------------------------------------------------- 1 | mod cli; 2 | mod config; 3 | mod logic; 4 | mod utils; 5 | 6 | use std::{path::PathBuf, io::IsTerminal}; 7 | 8 | use crate::config::Config; 9 | 10 | use anyhow::{Context, Result}; 11 | use tokio::signal::unix::{SignalKind, signal}; 12 | 13 | use tracing::{error, info}; 14 | 15 | 16 | fn bootstrap() -> Result { 17 | // handle command line input 18 | let matches = cli::app().get_matches(); 19 | 20 | // set up config 21 | let (config, diag) = match matches.get_one::("config") { 22 | Some(path) => Config::load_file(path)?, 23 | None => Config::load()?, 24 | }; 25 | 26 | // set up logger 27 | let filter = tracing_subscriber::EnvFilter::from_env("SDTXU_LOG") 28 | .add_directive(tracing::Level::from(config.log.level).into()); 29 | 30 | let fmt = tracing_subscriber::fmt::format::PrettyFields::new(); 31 | 32 | let subscriber = tracing_subscriber::fmt() 33 | .fmt_fields(fmt) 34 | .with_env_filter(filter) 35 | .with_ansi(std::io::stdout().is_terminal()); 36 | 37 | if matches.get_flag("no-log-time") { 38 | subscriber.without_time().init(); 39 | } else { 40 | subscriber.init(); 41 | } 42 | 43 | // warn about unknown config items 44 | diag.log(); 45 | 46 | Ok(config) 47 | } 48 | 49 | async fn run() -> Result<()> { 50 | let _config = bootstrap()?; 51 | 52 | // set up signal handling for shutdown 53 | let mut sigint = signal(SignalKind::interrupt()).context("Failed to set up signal handling")?; 54 | let mut sigterm = signal(SignalKind::terminate()).context("Failed to set up signal handling")?; 55 | 56 | let sig = async move { 57 | let cause = tokio::select! { 58 | _ = sigint.recv() => "SIGINT", 59 | _ = sigterm.recv() => "SIGTERM", 60 | }; 61 | 62 | info!(target: "sdtxu", "received {}, shutting down...", cause); 63 | }; 64 | 65 | // set up main logic task 66 | let main = logic::run(); 67 | 68 | // wait for error or shutdown signal 69 | info!(target: "sdtxu", "running..."); 70 | 71 | tokio::select! { 72 | _ = sig => Ok(()), 73 | res = main => res, 74 | } 75 | } 76 | 77 | #[tokio::main(flavor = "current_thread")] 78 | async fn main() -> Result<()> { 79 | // run main function and log critical errors 80 | let result = run().await; 81 | if let Err(ref err) = result { 82 | error!(target: "sdtxu", "critical error: {}\n", err); 83 | } 84 | 85 | result 86 | } 87 | -------------------------------------------------------------------------------- /surface-dtx-userd/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod notify; 2 | pub mod task; 3 | -------------------------------------------------------------------------------- /surface-dtx-userd/src/utils/notify.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::HashMap; 3 | use std::time::Duration; 4 | 5 | use dbus::arg::{RefArg, Variant}; 6 | use dbus::nonblock::{Proxy, SyncConnection}; 7 | 8 | 9 | #[derive(Debug)] 10 | pub struct Notification<'a> { 11 | app_name: Cow<'a, str>, 12 | replaces: u32, 13 | icon: Cow<'a, str>, 14 | summary: Cow<'a, str>, 15 | body: Cow<'a, str>, 16 | actions: Vec, 17 | hints: HashMap>>, 18 | expires: i32, 19 | } 20 | 21 | #[derive(Debug)] 22 | pub struct NotificationBuilder<'a> { 23 | notif: Notification<'a>, 24 | } 25 | 26 | #[derive(Debug, Copy, Clone)] 27 | pub struct NotificationHandle { 28 | pub id: u32, 29 | } 30 | 31 | 32 | #[allow(unused)] 33 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 34 | pub enum Timeout { 35 | Unspecified, 36 | Never, 37 | Millis(u32), 38 | } 39 | 40 | 41 | #[allow(unused)] 42 | impl<'a> Notification<'a> { 43 | pub fn create>>(app_name: S) -> NotificationBuilder<'a> { 44 | NotificationBuilder { notif: Notification::new(app_name) } 45 | } 46 | 47 | pub fn new>>(app_name: S) -> Self { 48 | Notification { 49 | app_name: app_name.into(), 50 | replaces: 0, 51 | icon: Default::default(), 52 | summary: Default::default(), 53 | body: Default::default(), 54 | actions: Default::default(), 55 | hints: Default::default(), 56 | expires: -1, 57 | } 58 | } 59 | 60 | pub fn set_replaces(&mut self, id: u32) { 61 | self.replaces = id 62 | } 63 | 64 | pub fn set_icon>>(&mut self, icon: S) { 65 | self.icon = icon.into() 66 | } 67 | 68 | pub fn set_summary>>(&mut self, summary: S) { 69 | self.summary = summary.into() 70 | } 71 | 72 | pub fn set_body>>(&mut self, body: S) { 73 | self.body = body.into() 74 | } 75 | 76 | pub fn add_hint_s(&mut self, key: K, value: V) 77 | where 78 | K: Into, 79 | V: Into>, 80 | { 81 | let value = value.into().into_owned(); 82 | self.hints.insert(key.into(), Variant(Box::new(value) as Box)); 83 | } 84 | 85 | pub fn add_hint(&mut self, key: K, value: V) 86 | where 87 | K: Into, 88 | V: RefArg + 'static, 89 | { 90 | self.hints.insert(key.into(), Variant(Box::new(value) as Box)); 91 | } 92 | 93 | pub fn set_expires(&mut self, timeout: Timeout) { 94 | self.expires = match timeout { 95 | Timeout::Unspecified => -1, 96 | Timeout::Never => 0, 97 | Timeout::Millis(t) => t as _, 98 | } 99 | } 100 | 101 | pub async fn show(self, conn: &SyncConnection) -> Result { 102 | let proxy = Proxy::new( 103 | "org.freedesktop.Notifications", 104 | "/org/freedesktop/Notifications", 105 | Duration::from_secs(5), 106 | conn, 107 | ); 108 | 109 | let (id,): (u32,) = proxy 110 | .method_call( 111 | "org.freedesktop.Notifications", 112 | "Notify", 113 | ( 114 | self.app_name.into_owned(), 115 | self.replaces, 116 | self.icon.into_owned(), 117 | self.summary.into_owned(), 118 | self.body.into_owned(), 119 | self.actions, 120 | self.hints, 121 | self.expires, 122 | ), 123 | ) 124 | .await?; 125 | 126 | Ok(NotificationHandle { id }) 127 | } 128 | } 129 | 130 | 131 | #[allow(unused)] 132 | impl<'a> NotificationBuilder<'a> { 133 | pub fn build(self) -> Notification<'a> { 134 | self.notif 135 | } 136 | 137 | pub fn replaces(mut self, id: u32) -> Self { 138 | self.notif.set_replaces(id); 139 | self 140 | } 141 | 142 | pub fn icon>>(mut self, icon: S) -> Self { 143 | self.notif.set_icon(icon); 144 | self 145 | } 146 | 147 | pub fn summary>>(mut self, summary: S) -> Self { 148 | self.notif.set_summary(summary); 149 | self 150 | } 151 | 152 | pub fn body>>(mut self, body: S) -> Self { 153 | self.notif.set_body(body); 154 | self 155 | } 156 | 157 | pub fn hint_s(mut self, key: K, value: V) -> Self 158 | where 159 | K: Into, 160 | V: Into>, 161 | { 162 | self.notif.add_hint_s(key, value); 163 | self 164 | } 165 | 166 | pub fn hint(mut self, key: K, value: V) -> Self 167 | where 168 | K: Into, 169 | V: RefArg + 'static, 170 | { 171 | self.notif.add_hint(key, value); 172 | self 173 | } 174 | 175 | pub fn expires(mut self, timeout: Timeout) -> Self { 176 | self.notif.set_expires(timeout); 177 | self 178 | } 179 | } 180 | 181 | 182 | impl NotificationHandle { 183 | pub async fn close(self, conn: &SyncConnection) -> Result<(), dbus::Error> { 184 | let proxy = Proxy::new( 185 | "org.freedesktop.Notifications", 186 | "/org/freedesktop/Notifications", 187 | Duration::from_secs(5), 188 | conn, 189 | ); 190 | 191 | proxy 192 | .method_call( 193 | "org.freedesktop.Notifications", 194 | "CloseNotification", 195 | (self.id,), 196 | ) 197 | .await 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /surface-dtx-userd/src/utils/task.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | 6 | use tokio::task::{JoinError, JoinHandle}; 7 | 8 | 9 | /// Ensures that all tasks spawned by a future will be canceled when the future 10 | /// (i.e. created by an async fn) is dropped or when it completes. 11 | pub struct JoinGuard { 12 | inner: JoinHandle, 13 | } 14 | 15 | impl Drop for JoinGuard { 16 | fn drop(&mut self) { 17 | self.inner.abort(); 18 | } 19 | } 20 | 21 | impl Deref for JoinGuard { 22 | type Target = JoinHandle; 23 | 24 | fn deref(&self) -> &Self::Target { 25 | &self.inner 26 | } 27 | } 28 | 29 | impl DerefMut for JoinGuard { 30 | fn deref_mut(&mut self) -> &mut Self::Target { 31 | &mut self.inner 32 | } 33 | } 34 | 35 | impl Future for JoinGuard { 36 | type Output = std::result::Result; 37 | 38 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 39 | std::pin::Pin::new(&mut self.inner).poll(cx) 40 | } 41 | } 42 | 43 | 44 | pub trait JoinHandleExt { 45 | fn guard(self) -> JoinGuard; 46 | } 47 | 48 | impl JoinHandleExt for JoinHandle { 49 | fn guard(self) -> JoinGuard { 50 | JoinGuard { inner: self } 51 | } 52 | } 53 | --------------------------------------------------------------------------------