├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── justfile ├── rust-toolchain.toml ├── rustfmt.toml ├── spec ├── org.mpris.MediaPlayer2.Player.xml ├── org.mpris.MediaPlayer2.Playlists.xml ├── org.mpris.MediaPlayer2.TrackList.xml └── org.mpris.MediaPlayer2.xml └── src ├── ffi.rs ├── llb.rs ├── macros.rs ├── mpris2.rs └── plugin.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: cargo 9 | directory: / 10 | schedule: 11 | interval: daily 12 | - package-ecosystem: github-actions 13 | directory: / 14 | schedule: 15 | interval: daily 16 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - "dependabot/**" 7 | - "pr/**" 8 | tags: 9 | - "v*" 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - run: sudo apt-get update 17 | - run: sudo apt-get install libmpv-dev 18 | - uses: actions/checkout@v4 19 | - uses: actions/cache@v4 20 | with: 21 | path: | 22 | ~/.cargo/ 23 | ~/.rustup/ 24 | target/ 25 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 26 | - run: | 27 | cargo build --release --locked --target x86_64-unknown-linux-gnu 28 | - if: startsWith(github.ref, 'refs/tags/v') 29 | run: cp target/x86_64-unknown-linux-gnu/release/libmpv_mpris2.so mpris-x86_64-unknown-linux-gnu.so 30 | - if: startsWith(github.ref, 'refs/tags/v') 31 | run: strip --strip-unneeded mpris-x86_64-unknown-linux-gnu.so 32 | - if: startsWith(github.ref, 'refs/tags/v') 33 | name: Release 34 | uses: softprops/action-gh-release@v2 35 | with: 36 | files: mpris-x86_64-unknown-linux-gnu.so 37 | - name: Install required clippy tools 38 | run: cargo install clippy-sarif sarif-fmt 39 | continue-on-error: true 40 | - name: Run rust-clippy 41 | run: cargo clippy --all-features --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 42 | continue-on-error: true 43 | - name: Upload analysis results to GitHub 44 | uses: github/codeql-action/upload-sarif@v3 45 | with: 46 | sarif_file: rust-clippy-results.sarif 47 | wait-for-processing: true 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .ccls-cache 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.checkOnSave.command": "clippy" 3 | } 4 | -------------------------------------------------------------------------------- /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 = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "async-broadcast" 16 | version = "0.7.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" 19 | dependencies = [ 20 | "event-listener", 21 | "event-listener-strategy", 22 | "futures-core", 23 | "pin-project-lite", 24 | ] 25 | 26 | [[package]] 27 | name = "async-channel" 28 | version = "2.3.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" 31 | dependencies = [ 32 | "concurrent-queue", 33 | "event-listener-strategy", 34 | "futures-core", 35 | "pin-project-lite", 36 | ] 37 | 38 | [[package]] 39 | name = "async-executor" 40 | version = "1.13.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" 43 | dependencies = [ 44 | "async-task", 45 | "concurrent-queue", 46 | "fastrand", 47 | "futures-lite", 48 | "slab", 49 | ] 50 | 51 | [[package]] 52 | name = "async-fs" 53 | version = "2.1.2" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" 56 | dependencies = [ 57 | "async-lock", 58 | "blocking", 59 | "futures-lite", 60 | ] 61 | 62 | [[package]] 63 | name = "async-io" 64 | version = "2.3.4" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" 67 | dependencies = [ 68 | "async-lock", 69 | "cfg-if", 70 | "concurrent-queue", 71 | "futures-io", 72 | "futures-lite", 73 | "parking", 74 | "polling", 75 | "rustix", 76 | "slab", 77 | "tracing", 78 | "windows-sys 0.59.0", 79 | ] 80 | 81 | [[package]] 82 | name = "async-lock" 83 | version = "3.4.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" 86 | dependencies = [ 87 | "event-listener", 88 | "event-listener-strategy", 89 | "pin-project-lite", 90 | ] 91 | 92 | [[package]] 93 | name = "async-net" 94 | version = "2.0.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" 97 | dependencies = [ 98 | "async-io", 99 | "blocking", 100 | "futures-lite", 101 | ] 102 | 103 | [[package]] 104 | name = "async-process" 105 | version = "2.3.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" 108 | dependencies = [ 109 | "async-channel", 110 | "async-io", 111 | "async-lock", 112 | "async-signal", 113 | "async-task", 114 | "blocking", 115 | "cfg-if", 116 | "event-listener", 117 | "futures-lite", 118 | "rustix", 119 | "tracing", 120 | ] 121 | 122 | [[package]] 123 | name = "async-recursion" 124 | version = "1.1.1" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" 127 | dependencies = [ 128 | "proc-macro2", 129 | "quote", 130 | "syn", 131 | ] 132 | 133 | [[package]] 134 | name = "async-signal" 135 | version = "0.2.10" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" 138 | dependencies = [ 139 | "async-io", 140 | "async-lock", 141 | "atomic-waker", 142 | "cfg-if", 143 | "futures-core", 144 | "futures-io", 145 | "rustix", 146 | "signal-hook-registry", 147 | "slab", 148 | "windows-sys 0.59.0", 149 | ] 150 | 151 | [[package]] 152 | name = "async-task" 153 | version = "4.7.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 156 | 157 | [[package]] 158 | name = "async-trait" 159 | version = "0.1.83" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 162 | dependencies = [ 163 | "proc-macro2", 164 | "quote", 165 | "syn", 166 | ] 167 | 168 | [[package]] 169 | name = "atomic-waker" 170 | version = "1.1.2" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 173 | 174 | [[package]] 175 | name = "autocfg" 176 | version = "1.4.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 179 | 180 | [[package]] 181 | name = "bindgen" 182 | version = "0.71.1" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" 185 | dependencies = [ 186 | "bitflags", 187 | "cexpr", 188 | "clang-sys", 189 | "itertools", 190 | "log", 191 | "prettyplease", 192 | "proc-macro2", 193 | "quote", 194 | "regex", 195 | "rustc-hash", 196 | "shlex", 197 | "syn", 198 | ] 199 | 200 | [[package]] 201 | name = "bitflags" 202 | version = "2.6.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 205 | 206 | [[package]] 207 | name = "blocking" 208 | version = "1.6.1" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" 211 | dependencies = [ 212 | "async-channel", 213 | "async-task", 214 | "futures-io", 215 | "futures-lite", 216 | "piper", 217 | ] 218 | 219 | [[package]] 220 | name = "cexpr" 221 | version = "0.6.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 224 | dependencies = [ 225 | "nom", 226 | ] 227 | 228 | [[package]] 229 | name = "cfg-if" 230 | version = "1.0.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 233 | 234 | [[package]] 235 | name = "cfg_aliases" 236 | version = "0.2.1" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 239 | 240 | [[package]] 241 | name = "clang-sys" 242 | version = "1.8.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 245 | dependencies = [ 246 | "glob", 247 | "libc", 248 | "libloading", 249 | ] 250 | 251 | [[package]] 252 | name = "concurrent-queue" 253 | version = "2.5.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 256 | dependencies = [ 257 | "crossbeam-utils", 258 | ] 259 | 260 | [[package]] 261 | name = "crossbeam-utils" 262 | version = "0.8.20" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 265 | 266 | [[package]] 267 | name = "data-encoding" 268 | version = "2.9.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 271 | 272 | [[package]] 273 | name = "displaydoc" 274 | version = "0.2.5" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 277 | dependencies = [ 278 | "proc-macro2", 279 | "quote", 280 | "syn", 281 | ] 282 | 283 | [[package]] 284 | name = "either" 285 | version = "1.13.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 288 | 289 | [[package]] 290 | name = "endi" 291 | version = "1.1.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" 294 | 295 | [[package]] 296 | name = "enumflags2" 297 | version = "0.7.10" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" 300 | dependencies = [ 301 | "enumflags2_derive", 302 | "serde", 303 | ] 304 | 305 | [[package]] 306 | name = "enumflags2_derive" 307 | version = "0.7.10" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" 310 | dependencies = [ 311 | "proc-macro2", 312 | "quote", 313 | "syn", 314 | ] 315 | 316 | [[package]] 317 | name = "equivalent" 318 | version = "1.0.1" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 321 | 322 | [[package]] 323 | name = "errno" 324 | version = "0.3.9" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 327 | dependencies = [ 328 | "libc", 329 | "windows-sys 0.52.0", 330 | ] 331 | 332 | [[package]] 333 | name = "event-listener" 334 | version = "5.3.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" 337 | dependencies = [ 338 | "concurrent-queue", 339 | "parking", 340 | "pin-project-lite", 341 | ] 342 | 343 | [[package]] 344 | name = "event-listener-strategy" 345 | version = "0.5.2" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" 348 | dependencies = [ 349 | "event-listener", 350 | "pin-project-lite", 351 | ] 352 | 353 | [[package]] 354 | name = "fastrand" 355 | version = "2.1.1" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 358 | 359 | [[package]] 360 | name = "form_urlencoded" 361 | version = "1.2.1" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 364 | dependencies = [ 365 | "percent-encoding", 366 | ] 367 | 368 | [[package]] 369 | name = "futures-core" 370 | version = "0.3.31" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 373 | 374 | [[package]] 375 | name = "futures-io" 376 | version = "0.3.31" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 379 | 380 | [[package]] 381 | name = "futures-lite" 382 | version = "2.6.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" 385 | dependencies = [ 386 | "fastrand", 387 | "futures-core", 388 | "futures-io", 389 | "parking", 390 | "pin-project-lite", 391 | ] 392 | 393 | [[package]] 394 | name = "glob" 395 | version = "0.3.1" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 398 | 399 | [[package]] 400 | name = "hashbrown" 401 | version = "0.15.1" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" 404 | 405 | [[package]] 406 | name = "hermit-abi" 407 | version = "0.4.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 410 | 411 | [[package]] 412 | name = "hex" 413 | version = "0.4.3" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 416 | 417 | [[package]] 418 | name = "icu_collections" 419 | version = "1.5.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 422 | dependencies = [ 423 | "displaydoc", 424 | "yoke", 425 | "zerofrom", 426 | "zerovec", 427 | ] 428 | 429 | [[package]] 430 | name = "icu_locid" 431 | version = "1.5.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 434 | dependencies = [ 435 | "displaydoc", 436 | "litemap", 437 | "tinystr", 438 | "writeable", 439 | "zerovec", 440 | ] 441 | 442 | [[package]] 443 | name = "icu_locid_transform" 444 | version = "1.5.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 447 | dependencies = [ 448 | "displaydoc", 449 | "icu_locid", 450 | "icu_locid_transform_data", 451 | "icu_provider", 452 | "tinystr", 453 | "zerovec", 454 | ] 455 | 456 | [[package]] 457 | name = "icu_locid_transform_data" 458 | version = "1.5.0" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 461 | 462 | [[package]] 463 | name = "icu_normalizer" 464 | version = "1.5.0" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 467 | dependencies = [ 468 | "displaydoc", 469 | "icu_collections", 470 | "icu_normalizer_data", 471 | "icu_properties", 472 | "icu_provider", 473 | "smallvec", 474 | "utf16_iter", 475 | "utf8_iter", 476 | "write16", 477 | "zerovec", 478 | ] 479 | 480 | [[package]] 481 | name = "icu_normalizer_data" 482 | version = "1.5.0" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 485 | 486 | [[package]] 487 | name = "icu_properties" 488 | version = "1.5.1" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 491 | dependencies = [ 492 | "displaydoc", 493 | "icu_collections", 494 | "icu_locid_transform", 495 | "icu_properties_data", 496 | "icu_provider", 497 | "tinystr", 498 | "zerovec", 499 | ] 500 | 501 | [[package]] 502 | name = "icu_properties_data" 503 | version = "1.5.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 506 | 507 | [[package]] 508 | name = "icu_provider" 509 | version = "1.5.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 512 | dependencies = [ 513 | "displaydoc", 514 | "icu_locid", 515 | "icu_provider_macros", 516 | "stable_deref_trait", 517 | "tinystr", 518 | "writeable", 519 | "yoke", 520 | "zerofrom", 521 | "zerovec", 522 | ] 523 | 524 | [[package]] 525 | name = "icu_provider_macros" 526 | version = "1.5.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 529 | dependencies = [ 530 | "proc-macro2", 531 | "quote", 532 | "syn", 533 | ] 534 | 535 | [[package]] 536 | name = "idna" 537 | version = "1.0.3" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 540 | dependencies = [ 541 | "idna_adapter", 542 | "smallvec", 543 | "utf8_iter", 544 | ] 545 | 546 | [[package]] 547 | name = "idna_adapter" 548 | version = "1.2.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 551 | dependencies = [ 552 | "icu_normalizer", 553 | "icu_properties", 554 | ] 555 | 556 | [[package]] 557 | name = "indexmap" 558 | version = "2.6.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 561 | dependencies = [ 562 | "equivalent", 563 | "hashbrown", 564 | ] 565 | 566 | [[package]] 567 | name = "itertools" 568 | version = "0.13.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 571 | dependencies = [ 572 | "either", 573 | ] 574 | 575 | [[package]] 576 | name = "itoa" 577 | version = "1.0.11" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 580 | 581 | [[package]] 582 | name = "libc" 583 | version = "0.2.172" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 586 | 587 | [[package]] 588 | name = "libloading" 589 | version = "0.8.5" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 592 | dependencies = [ 593 | "cfg-if", 594 | "windows-targets", 595 | ] 596 | 597 | [[package]] 598 | name = "linux-raw-sys" 599 | version = "0.4.14" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 602 | 603 | [[package]] 604 | name = "litemap" 605 | version = "0.7.3" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" 608 | 609 | [[package]] 610 | name = "log" 611 | version = "0.4.22" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 614 | 615 | [[package]] 616 | name = "memchr" 617 | version = "2.7.4" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 620 | 621 | [[package]] 622 | name = "memoffset" 623 | version = "0.9.1" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 626 | dependencies = [ 627 | "autocfg", 628 | ] 629 | 630 | [[package]] 631 | name = "minimal-lexical" 632 | version = "0.2.1" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 635 | 636 | [[package]] 637 | name = "mpv-mpris2" 638 | version = "0.0.2" 639 | dependencies = [ 640 | "bindgen", 641 | "data-encoding", 642 | "pkg-config", 643 | "scopeguard", 644 | "serde_json", 645 | "smol", 646 | "static_assertions", 647 | "url", 648 | "zbus", 649 | ] 650 | 651 | [[package]] 652 | name = "nix" 653 | version = "0.30.1" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" 656 | dependencies = [ 657 | "bitflags", 658 | "cfg-if", 659 | "cfg_aliases", 660 | "libc", 661 | "memoffset", 662 | ] 663 | 664 | [[package]] 665 | name = "nom" 666 | version = "7.1.3" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 669 | dependencies = [ 670 | "memchr", 671 | "minimal-lexical", 672 | ] 673 | 674 | [[package]] 675 | name = "once_cell" 676 | version = "1.20.2" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 679 | 680 | [[package]] 681 | name = "ordered-stream" 682 | version = "0.2.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" 685 | dependencies = [ 686 | "futures-core", 687 | "pin-project-lite", 688 | ] 689 | 690 | [[package]] 691 | name = "parking" 692 | version = "2.2.1" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 695 | 696 | [[package]] 697 | name = "percent-encoding" 698 | version = "2.3.1" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 701 | 702 | [[package]] 703 | name = "pin-project-lite" 704 | version = "0.2.15" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 707 | 708 | [[package]] 709 | name = "piper" 710 | version = "0.2.4" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" 713 | dependencies = [ 714 | "atomic-waker", 715 | "fastrand", 716 | "futures-io", 717 | ] 718 | 719 | [[package]] 720 | name = "pkg-config" 721 | version = "0.3.32" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 724 | 725 | [[package]] 726 | name = "polling" 727 | version = "3.7.3" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" 730 | dependencies = [ 731 | "cfg-if", 732 | "concurrent-queue", 733 | "hermit-abi", 734 | "pin-project-lite", 735 | "rustix", 736 | "tracing", 737 | "windows-sys 0.59.0", 738 | ] 739 | 740 | [[package]] 741 | name = "prettyplease" 742 | version = "0.2.25" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" 745 | dependencies = [ 746 | "proc-macro2", 747 | "syn", 748 | ] 749 | 750 | [[package]] 751 | name = "proc-macro-crate" 752 | version = "3.2.0" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" 755 | dependencies = [ 756 | "toml_edit", 757 | ] 758 | 759 | [[package]] 760 | name = "proc-macro2" 761 | version = "1.0.89" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 764 | dependencies = [ 765 | "unicode-ident", 766 | ] 767 | 768 | [[package]] 769 | name = "quote" 770 | version = "1.0.37" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 773 | dependencies = [ 774 | "proc-macro2", 775 | ] 776 | 777 | [[package]] 778 | name = "regex" 779 | version = "1.11.1" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 782 | dependencies = [ 783 | "aho-corasick", 784 | "memchr", 785 | "regex-automata", 786 | "regex-syntax", 787 | ] 788 | 789 | [[package]] 790 | name = "regex-automata" 791 | version = "0.4.8" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 794 | dependencies = [ 795 | "aho-corasick", 796 | "memchr", 797 | "regex-syntax", 798 | ] 799 | 800 | [[package]] 801 | name = "regex-syntax" 802 | version = "0.8.5" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 805 | 806 | [[package]] 807 | name = "rustc-hash" 808 | version = "2.1.0" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" 811 | 812 | [[package]] 813 | name = "rustix" 814 | version = "0.38.39" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" 817 | dependencies = [ 818 | "bitflags", 819 | "errno", 820 | "libc", 821 | "linux-raw-sys", 822 | "windows-sys 0.52.0", 823 | ] 824 | 825 | [[package]] 826 | name = "ryu" 827 | version = "1.0.18" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 830 | 831 | [[package]] 832 | name = "scopeguard" 833 | version = "1.2.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 836 | 837 | [[package]] 838 | name = "serde" 839 | version = "1.0.214" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" 842 | dependencies = [ 843 | "serde_derive", 844 | ] 845 | 846 | [[package]] 847 | name = "serde_derive" 848 | version = "1.0.214" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" 851 | dependencies = [ 852 | "proc-macro2", 853 | "quote", 854 | "syn", 855 | ] 856 | 857 | [[package]] 858 | name = "serde_json" 859 | version = "1.0.140" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 862 | dependencies = [ 863 | "itoa", 864 | "memchr", 865 | "ryu", 866 | "serde", 867 | ] 868 | 869 | [[package]] 870 | name = "serde_repr" 871 | version = "0.1.19" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" 874 | dependencies = [ 875 | "proc-macro2", 876 | "quote", 877 | "syn", 878 | ] 879 | 880 | [[package]] 881 | name = "shlex" 882 | version = "1.3.0" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 885 | 886 | [[package]] 887 | name = "signal-hook-registry" 888 | version = "1.4.2" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 891 | dependencies = [ 892 | "libc", 893 | ] 894 | 895 | [[package]] 896 | name = "slab" 897 | version = "0.4.9" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 900 | dependencies = [ 901 | "autocfg", 902 | ] 903 | 904 | [[package]] 905 | name = "smallvec" 906 | version = "1.13.2" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 909 | 910 | [[package]] 911 | name = "smol" 912 | version = "2.0.2" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" 915 | dependencies = [ 916 | "async-channel", 917 | "async-executor", 918 | "async-fs", 919 | "async-io", 920 | "async-lock", 921 | "async-net", 922 | "async-process", 923 | "blocking", 924 | "futures-lite", 925 | ] 926 | 927 | [[package]] 928 | name = "stable_deref_trait" 929 | version = "1.2.0" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 932 | 933 | [[package]] 934 | name = "static_assertions" 935 | version = "1.1.0" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 938 | 939 | [[package]] 940 | name = "syn" 941 | version = "2.0.87" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 944 | dependencies = [ 945 | "proc-macro2", 946 | "quote", 947 | "unicode-ident", 948 | ] 949 | 950 | [[package]] 951 | name = "synstructure" 952 | version = "0.13.1" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 955 | dependencies = [ 956 | "proc-macro2", 957 | "quote", 958 | "syn", 959 | ] 960 | 961 | [[package]] 962 | name = "tempfile" 963 | version = "3.13.0" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 966 | dependencies = [ 967 | "cfg-if", 968 | "fastrand", 969 | "once_cell", 970 | "rustix", 971 | "windows-sys 0.59.0", 972 | ] 973 | 974 | [[package]] 975 | name = "tinystr" 976 | version = "0.7.6" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 979 | dependencies = [ 980 | "displaydoc", 981 | "zerovec", 982 | ] 983 | 984 | [[package]] 985 | name = "toml_datetime" 986 | version = "0.6.8" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 989 | 990 | [[package]] 991 | name = "toml_edit" 992 | version = "0.22.22" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 995 | dependencies = [ 996 | "indexmap", 997 | "toml_datetime", 998 | "winnow 0.6.20", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "tracing" 1003 | version = "0.1.40" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1006 | dependencies = [ 1007 | "pin-project-lite", 1008 | "tracing-attributes", 1009 | "tracing-core", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "tracing-attributes" 1014 | version = "0.1.27" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1017 | dependencies = [ 1018 | "proc-macro2", 1019 | "quote", 1020 | "syn", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "tracing-core" 1025 | version = "0.1.32" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1028 | dependencies = [ 1029 | "once_cell", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "uds_windows" 1034 | version = "1.1.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" 1037 | dependencies = [ 1038 | "memoffset", 1039 | "tempfile", 1040 | "winapi", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "unicode-ident" 1045 | version = "1.0.13" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1048 | 1049 | [[package]] 1050 | name = "url" 1051 | version = "2.5.4" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1054 | dependencies = [ 1055 | "form_urlencoded", 1056 | "idna", 1057 | "percent-encoding", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "utf16_iter" 1062 | version = "1.0.5" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1065 | 1066 | [[package]] 1067 | name = "utf8_iter" 1068 | version = "1.0.4" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1071 | 1072 | [[package]] 1073 | name = "winapi" 1074 | version = "0.3.9" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1077 | dependencies = [ 1078 | "winapi-i686-pc-windows-gnu", 1079 | "winapi-x86_64-pc-windows-gnu", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "winapi-i686-pc-windows-gnu" 1084 | version = "0.4.0" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1087 | 1088 | [[package]] 1089 | name = "winapi-x86_64-pc-windows-gnu" 1090 | version = "0.4.0" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1093 | 1094 | [[package]] 1095 | name = "windows-sys" 1096 | version = "0.52.0" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1099 | dependencies = [ 1100 | "windows-targets", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "windows-sys" 1105 | version = "0.59.0" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1108 | dependencies = [ 1109 | "windows-targets", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "windows-targets" 1114 | version = "0.52.6" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1117 | dependencies = [ 1118 | "windows_aarch64_gnullvm", 1119 | "windows_aarch64_msvc", 1120 | "windows_i686_gnu", 1121 | "windows_i686_gnullvm", 1122 | "windows_i686_msvc", 1123 | "windows_x86_64_gnu", 1124 | "windows_x86_64_gnullvm", 1125 | "windows_x86_64_msvc", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "windows_aarch64_gnullvm" 1130 | version = "0.52.6" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1133 | 1134 | [[package]] 1135 | name = "windows_aarch64_msvc" 1136 | version = "0.52.6" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1139 | 1140 | [[package]] 1141 | name = "windows_i686_gnu" 1142 | version = "0.52.6" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1145 | 1146 | [[package]] 1147 | name = "windows_i686_gnullvm" 1148 | version = "0.52.6" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1151 | 1152 | [[package]] 1153 | name = "windows_i686_msvc" 1154 | version = "0.52.6" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1157 | 1158 | [[package]] 1159 | name = "windows_x86_64_gnu" 1160 | version = "0.52.6" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1163 | 1164 | [[package]] 1165 | name = "windows_x86_64_gnullvm" 1166 | version = "0.52.6" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1169 | 1170 | [[package]] 1171 | name = "windows_x86_64_msvc" 1172 | version = "0.52.6" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1175 | 1176 | [[package]] 1177 | name = "winnow" 1178 | version = "0.6.20" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 1181 | dependencies = [ 1182 | "memchr", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "winnow" 1187 | version = "0.7.1" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" 1190 | dependencies = [ 1191 | "memchr", 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "write16" 1196 | version = "1.0.0" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 1199 | 1200 | [[package]] 1201 | name = "writeable" 1202 | version = "0.5.5" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 1205 | 1206 | [[package]] 1207 | name = "yoke" 1208 | version = "0.7.4" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" 1211 | dependencies = [ 1212 | "serde", 1213 | "stable_deref_trait", 1214 | "yoke-derive", 1215 | "zerofrom", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "yoke-derive" 1220 | version = "0.7.4" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" 1223 | dependencies = [ 1224 | "proc-macro2", 1225 | "quote", 1226 | "syn", 1227 | "synstructure", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "zbus" 1232 | version = "5.7.1" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68" 1235 | dependencies = [ 1236 | "async-broadcast", 1237 | "async-executor", 1238 | "async-io", 1239 | "async-lock", 1240 | "async-process", 1241 | "async-recursion", 1242 | "async-task", 1243 | "async-trait", 1244 | "blocking", 1245 | "enumflags2", 1246 | "event-listener", 1247 | "futures-core", 1248 | "futures-lite", 1249 | "hex", 1250 | "nix", 1251 | "ordered-stream", 1252 | "serde", 1253 | "serde_repr", 1254 | "tracing", 1255 | "uds_windows", 1256 | "windows-sys 0.59.0", 1257 | "winnow 0.7.1", 1258 | "zbus_macros", 1259 | "zbus_names", 1260 | "zvariant", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "zbus_macros" 1265 | version = "5.7.1" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a" 1268 | dependencies = [ 1269 | "proc-macro-crate", 1270 | "proc-macro2", 1271 | "quote", 1272 | "syn", 1273 | "zbus_names", 1274 | "zvariant", 1275 | "zvariant_utils", 1276 | ] 1277 | 1278 | [[package]] 1279 | name = "zbus_names" 1280 | version = "4.2.0" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" 1283 | dependencies = [ 1284 | "serde", 1285 | "static_assertions", 1286 | "winnow 0.7.1", 1287 | "zvariant", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "zerofrom" 1292 | version = "0.1.4" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" 1295 | dependencies = [ 1296 | "zerofrom-derive", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "zerofrom-derive" 1301 | version = "0.1.4" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" 1304 | dependencies = [ 1305 | "proc-macro2", 1306 | "quote", 1307 | "syn", 1308 | "synstructure", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "zerovec" 1313 | version = "0.10.4" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 1316 | dependencies = [ 1317 | "yoke", 1318 | "zerofrom", 1319 | "zerovec-derive", 1320 | ] 1321 | 1322 | [[package]] 1323 | name = "zerovec-derive" 1324 | version = "0.10.3" 1325 | source = "registry+https://github.com/rust-lang/crates.io-index" 1326 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 1327 | dependencies = [ 1328 | "proc-macro2", 1329 | "quote", 1330 | "syn", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "zvariant" 1335 | version = "5.5.3" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "9d30786f75e393ee63a21de4f9074d4c038d52c5b1bb4471f955db249f9dffb1" 1338 | dependencies = [ 1339 | "endi", 1340 | "enumflags2", 1341 | "serde", 1342 | "winnow 0.7.1", 1343 | "zvariant_derive", 1344 | "zvariant_utils", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "zvariant_derive" 1349 | version = "5.5.3" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "75fda702cd42d735ccd48117b1630432219c0e9616bf6cb0f8350844ee4d9580" 1352 | dependencies = [ 1353 | "proc-macro-crate", 1354 | "proc-macro2", 1355 | "quote", 1356 | "syn", 1357 | "zvariant_utils", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "zvariant_utils" 1362 | version = "3.2.0" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" 1365 | dependencies = [ 1366 | "proc-macro2", 1367 | "quote", 1368 | "serde", 1369 | "static_assertions", 1370 | "syn", 1371 | "winnow 0.7.1", 1372 | ] 1373 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpv-mpris2" 3 | version = "0.0.2" 4 | edition = "2021" 5 | license = "MIT-0" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | path = "src/plugin.rs" 10 | 11 | [profile.release] 12 | lto = true 13 | 14 | [lints.clippy] 15 | suspicious = "warn" 16 | complexity = "warn" 17 | perf = "warn" 18 | pedantic = "warn" 19 | 20 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 21 | 22 | [dependencies] 23 | data-encoding = "2" 24 | scopeguard = "1" 25 | serde_json = "1" 26 | smol = "2" 27 | static_assertions = "1.1.0" 28 | url = "2" 29 | zbus = "5" 30 | 31 | [build-dependencies] 32 | bindgen = "0.71" 33 | pkg-config = "0.3" 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mpris.so 2 | 3 | This mpv plugin implements the MPRIS v2 DBus interface. The MPRIS API is used by 4 | Linux desktop environments and tools like `playerctl` to control media player 5 | and provide metadata. 6 | 7 | This implementation has been tested with KDE Plasma and `playerctl`. 8 | 9 | There is no relation [@hoyon's mpv-mpris](https://github.com/hoyon/mpv-mpris). 10 | 11 | ## Installation 12 | 13 | The plugin must be installed to the `scripts/` subdirectory of the mpv configuration 14 | directory. See the mpv manual to find the configuration directory. The filename `mpris.so` is recommended. 15 | Pre-build binary is available in GitHub. 16 | 17 | Any other implementations of the MPRIS API (like [hoyon/mpv-mpris](https://github.com/hoyon/mpv-mpris)) must be uninstalled. 18 | 19 | ### Building from source 20 | 21 | ``` 22 | $ git clone https://github.com/eNV25/mpv-mpris2 23 | $ cargo build --release 24 | $ install -v target/release/libmpv_mpris.so ~/.config/mpv/scripts/mpris.so 25 | ``` 26 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error, path::Path}; 2 | 3 | fn main() -> Result<(), Box> { 4 | const MPV_SYMBOLS: &str = "(MPV|mpv)_.*"; 5 | let header = Path::new(&pkg_config::get_variable("mpv", "includedir")?).join("mpv/client.h"); 6 | let header = <&str>::try_from(header.as_os_str())?; 7 | let output = Path::new(&env::var("OUT_DIR")?).join("ffi.rs"); 8 | bindgen::builder() 9 | .header(header) 10 | .clang_arg("-Wp,-D_FORTIFY_SOURCE=2") 11 | .clang_arg("-Wp,-DMPV_ENABLE_DEPRECATED=0") 12 | .opaque_type("mpv_handle") 13 | .allowlist_item(MPV_SYMBOLS) 14 | .constified_enum(MPV_SYMBOLS) 15 | .prepend_enum_name(false) 16 | .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) 17 | .generate()? 18 | .write_to_file(output)?; 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | 2 | export prefix := "/usr/local" 3 | export config_system := if clean(prefix) == "/usr" { 4 | "/etc" 5 | } else if clean(prefix) == "/" { 6 | "/etc" 7 | } else { 8 | "/usr/local/etc" 9 | } 10 | export config_user := env("XDG_CONFIG_HOME", join(env("HOME"), ".config")) 11 | 12 | set shell := ["sh", "-xc"] 13 | 14 | default: 15 | @just --choose 16 | 17 | build *args="": 18 | cargo build {{args}} 19 | 20 | dups: 21 | rg '.*"(.*) (.*)".*' -r '$1 v$2' Cargo.lock | sort -u 22 | 23 | install: (build "--release") 24 | install -v -D target/release/libmpv_mpris2.so "${prefix}/lib/mpv-mpris2/mpris.so" 25 | strip --strip-unneeded "${prefix}/lib/mpv-mpris2/mpris.so" 26 | mkdir -p "${config_system}/mpv/scripts/" 27 | ln -v -s -t "${config_system}/mpv/scripts/" "${prefix}/lib/mpv-mpris2/mpris.so" 28 | 29 | uninstall: 30 | rm "${config_system}/mpv/scripts/mpris.so" 31 | rm -rf "${prefix}/lib/mpv-mpris2/" 32 | 33 | install-user: (build "--release") 34 | install -v -D target/release/libmpv_mpris2.so "${config_user}/mpv/scripts/mpris.so" 35 | strip --strip-unneeded "${config_user}/mpv/scripts/mpris.so" 36 | 37 | uninstall-user: 38 | rm "${config_user}/mpv/scripts/mpris.so" 39 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | -------------------------------------------------------------------------------- /spec/org.mpris.MediaPlayer2.Player.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 | -------------------------------------------------------------------------------- /spec/org.mpris.MediaPlayer2.Playlists.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 | -------------------------------------------------------------------------------- /spec/org.mpris.MediaPlayer2.TrackList.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 | -------------------------------------------------------------------------------- /spec/org.mpris.MediaPlayer2.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 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(non_upper_case_globals)] 3 | #![allow(non_camel_case_types)] 4 | #![allow(non_snake_case)] 5 | 6 | include!(concat!(env!("OUT_DIR"), "/ffi.rs")); 7 | 8 | #[repr(transparent)] 9 | #[derive(Clone, Copy)] 10 | pub struct MPVHandle(pub *mut mpv_handle); 11 | unsafe impl Send for MPVHandle {} 12 | unsafe impl Sync for MPVHandle {} 13 | 14 | impl From for *mut mpv_handle { 15 | #[inline] 16 | fn from(value: MPVHandle) -> Self { 17 | value.0 18 | } 19 | } 20 | 21 | #[repr(transparent)] 22 | pub struct Error(pub mpv_error); 23 | 24 | impl std::error::Error for Error {} 25 | 26 | impl From for Error { 27 | #[inline] 28 | fn from(value: mpv_error) -> Self { 29 | Self(value) 30 | } 31 | } 32 | 33 | impl From for zbus::fdo::Error { 34 | #[inline] 35 | fn from(value: Error) -> Self { 36 | Self::Failed(value.to_string()) 37 | } 38 | } 39 | 40 | impl From for zbus::Error { 41 | #[inline] 42 | fn from(value: Error) -> Self { 43 | Self::Failure(value.to_string()) 44 | } 45 | } 46 | 47 | impl std::fmt::Debug for Error { 48 | #[inline] 49 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 | f.write_str("Error(")?; 51 | std::fmt::Display::fmt(self, f)?; 52 | f.write_str(")") 53 | } 54 | } 55 | 56 | impl std::fmt::Display for Error { 57 | #[inline] 58 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 59 | let str = unsafe { std::ffi::CStr::from_ptr(mpv_error_string(self.0)) } 60 | .to_str() 61 | .unwrap_or_default(); 62 | f.write_str(str) 63 | } 64 | } 65 | 66 | pub unsafe fn string_from_cstr( 67 | ptr: *const std::ffi::c_char, 68 | ) -> Result { 69 | std::ffi::CStr::from_ptr(ptr).to_str().map(str::to_owned) 70 | } 71 | 72 | pub unsafe fn string_from_cstr_lossy(ptr: *const std::ffi::c_char) -> String { 73 | std::ffi::CStr::from_ptr(ptr).to_string_lossy().into() 74 | } 75 | 76 | pub trait AsBytes { 77 | fn as_bytes(&self) -> &[u8]; 78 | } 79 | 80 | impl AsBytes for &[u8] { 81 | #[inline] 82 | fn as_bytes(&self) -> &[u8] { 83 | self 84 | } 85 | } 86 | 87 | impl AsBytes for str { 88 | #[inline] 89 | fn as_bytes(&self) -> &[u8] { 90 | self.as_bytes() 91 | } 92 | } 93 | 94 | impl AsBytes for std::ffi::CStr { 95 | #[inline] 96 | fn as_bytes(&self) -> &[u8] { 97 | self.to_bytes_with_nul() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/llb.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, collections::HashMap, future::Future}; 2 | 3 | use zbus::{fdo, object_server::SignalEmitter, zvariant}; 4 | 5 | pub trait Block: Sized + Future { 6 | //fn block_lite(self) -> ::Output { 7 | // smol::future::block_on(self) 8 | //} 9 | fn block(self) -> ::Output { 10 | smol::block_on(self) 11 | } 12 | } 13 | 14 | impl Block for F {} 15 | 16 | pub fn properties_changed( 17 | emitter: &SignalEmitter<'_>, 18 | changed_properties: HashMap<&str, zvariant::Value<'_>>, 19 | ) -> zbus::Result<()> { 20 | if changed_properties.is_empty() { 21 | Ok(()) 22 | } else { 23 | fdo::Properties::properties_changed(emitter, I::name(), changed_properties, Cow::default()) 24 | .block() 25 | } 26 | } 27 | 28 | pub fn seeked(emitter: &SignalEmitter<'_>, position: i64) -> zbus::Result<()> { 29 | crate::mpris2::Player::seeked(emitter, position).block() 30 | } 31 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | 3 | macro_rules! cstr { 4 | ($s:literal) => {{ 5 | ::static_assertions::const_assert!(matches!($s.to_bytes_with_nul(), [.., b'\0'])); 6 | $s.as_ptr() 7 | }}; 8 | ($s:expr) => {{ 9 | debug_assert!(matches!($crate::AsBytes::as_bytes($s), [.., b'\0'])); 10 | $s.as_ptr().cast::() 11 | }}; 12 | } 13 | 14 | macro_rules! command { 15 | ($mpv:ident, $($arg:expr),+ $(,)?) => {{ 16 | let args = [$(cstr!($arg)),+, std::ptr::null()]; 17 | match unsafe { $crate::mpv_command($mpv.into(), std::ptr::addr_of!(args).cast_mut().cast()) } { 18 | 0.. => Ok(()), 19 | error => Err($crate::Error(error.into())), 20 | } 21 | }}; 22 | } 23 | 24 | macro_rules! get { 25 | ($mpv:ident, $prop:literal) => { 26 | get!($mpv, $prop, MPV_FORMAT_STRING) 27 | }; 28 | ($mpv:ident, $prop:literal, bool) => { 29 | get!($mpv, $prop, MPV_FORMAT_FLAG).map(|x| x != 0) 30 | }; 31 | ($mpv:ident, $prop:literal, i64) => { 32 | get!($mpv, $prop, MPV_FORMAT_INT64) 33 | }; 34 | ($mpv:ident, $prop:literal, f64) => { 35 | get!($mpv, $prop, MPV_FORMAT_DOUBLE) 36 | }; 37 | ($mpv:ident, $prop:literal, MPV_FORMAT_STRING) => { 38 | unsafe { 39 | let ptr = $crate::mpv_get_property_string($mpv.into(), cstr!($prop)); 40 | if ptr.is_null() { 41 | "".to_owned() 42 | } else { 43 | let prop = $crate::string_from_cstr_lossy(ptr); 44 | $crate::mpv_free(ptr.cast()); 45 | prop 46 | } 47 | } 48 | }; 49 | ($mpv:ident, $prop:literal, MPV_FORMAT_FLAG) => { 50 | get!($mpv, $prop, MPV_FORMAT_FLAG, std::ffi::c_int::default()) 51 | }; 52 | ($mpv:ident, $prop:literal, MPV_FORMAT_INT64) => { 53 | get!($mpv, $prop, MPV_FORMAT_INT64, i64::default()) 54 | }; 55 | ($mpv:ident, $prop:literal, MPV_FORMAT_DOUBLE) => { 56 | get!($mpv, $prop, MPV_FORMAT_DOUBLE, f64::default()) 57 | }; 58 | ($mpv:ident, $prop:literal, $format:ident, $default:expr) => {{ 59 | let mut data = $default; 60 | match unsafe { 61 | $crate::mpv_get_property( 62 | $mpv.into(), 63 | cstr!($prop), 64 | $crate::$format, 65 | std::ptr::addr_of_mut!(data).cast(), 66 | ) 67 | } { 68 | 0.. => Ok(data), 69 | error => Err($crate::Error(error.into())), 70 | } 71 | }}; 72 | } 73 | 74 | macro_rules! set { 75 | ($mpv:ident, $prop:literal, $data:expr) => { 76 | set!($mpv, $prop, MPV_FORMAT_STRING, cstr!($data)) 77 | }; 78 | ($mpv:ident, $prop:literal, bool, $data:expr) => { 79 | set!($mpv, $prop, MPV_FORMAT_FLAG, std::ffi::c_int::from($data)) 80 | }; 81 | ($mpv:ident, $prop:literal, i64, $data:expr) => { 82 | set!($mpv, $prop, MPV_FORMAT_INT64, $data as i64) 83 | }; 84 | ($mpv:ident, $prop:literal, f64, $data:expr) => { 85 | set!($mpv, $prop, MPV_FORMAT_DOUBLE, $data as f64) 86 | }; 87 | ($mpv:ident, $prop:literal, $format:ident, $data:expr) => {{ 88 | let data = $data; 89 | match unsafe { 90 | $crate::mpv_set_property( 91 | $mpv.into(), 92 | cstr!($prop), 93 | $crate::$format, 94 | std::ptr::addr_of!(data).cast_mut().cast(), 95 | ) 96 | } { 97 | 0.. => Ok(()), 98 | error => Err($crate::Error(error.into())), 99 | } 100 | }}; 101 | } 102 | 103 | macro_rules! observe { 104 | ($mpv:ident, $($prop:literal),+ $(,)?) => { 105 | $(observe!($mpv, 0, $prop, MPV_FORMAT_NONE));+ 106 | }; 107 | ($mpv:ident, $format:ident, $($prop:literal),+ $(,)?) => { 108 | $(observe!($mpv, 0, $prop, $format));+ 109 | }; 110 | ($mpv:ident, $userdata:expr, $prop:literal, $format:ident) => {{ 111 | let userdata = $userdata; 112 | unsafe { 113 | $crate::mpv_observe_property( 114 | $mpv.into(), 115 | userdata, 116 | cstr!($prop), 117 | $crate::$format, 118 | ); 119 | } 120 | }}; 121 | } 122 | -------------------------------------------------------------------------------- /src/mpris2.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::ignored_unit_patterns)] // for interface macro 2 | 3 | use std::{ 4 | collections::HashMap, 5 | env, 6 | fs::File, 7 | io::{self, BufRead, BufReader}, 8 | path::{Path, PathBuf}, 9 | time::Duration, 10 | }; 11 | 12 | use data_encoding::BASE64; 13 | use smol::{future::FutureExt, process::Command, Timer}; 14 | use url::Url; 15 | use zbus::{ 16 | fdo, interface, 17 | object_server::SignalEmitter, 18 | zvariant::{ObjectPath, Value}, 19 | }; 20 | 21 | use crate::Block; 22 | 23 | #[repr(transparent)] 24 | #[derive(Clone, Copy)] 25 | pub struct Root(pub crate::MPVHandle); 26 | 27 | #[repr(transparent)] 28 | #[derive(Clone, Copy)] 29 | pub struct Player(pub crate::MPVHandle); 30 | 31 | impl From for *mut crate::mpv_handle { 32 | #[inline] 33 | fn from(value: Root) -> Self { 34 | value.0 .0 35 | } 36 | } 37 | 38 | impl From for *mut crate::mpv_handle { 39 | #[inline] 40 | fn from(value: Player) -> Self { 41 | value.0 .0 42 | } 43 | } 44 | 45 | pub fn time_as_secs(time: i64) -> f64 { 46 | Duration::from_micros(time.try_into().unwrap_or(u64::MIN)).as_secs_f64() 47 | } 48 | 49 | pub fn time_from_secs(secs: f64) -> i64 { 50 | let secs = Duration::try_from_secs_f64(secs).unwrap_or(Duration::ZERO); 51 | secs.as_micros().try_into().unwrap_or(i64::MAX) 52 | } 53 | 54 | #[allow(clippy::unused_self)] 55 | #[interface(name = "org.mpris.MediaPlayer2")] 56 | impl Root { 57 | #[zbus(property)] 58 | fn desktop_entry(self) -> &'static str { 59 | "mpv" 60 | } 61 | 62 | #[zbus(property)] 63 | fn identity(self) -> &'static str { 64 | "mpv Media Player" 65 | } 66 | 67 | #[zbus(property)] 68 | fn supported_mime_types(self) -> Vec { 69 | env::var("XDG_DATA_DIRS") 70 | .unwrap_or_else(|_| "/usr/local/share:/usr/share".to_owned()) 71 | .split(':') 72 | .map(Path::new) 73 | .filter_map(|dir| { 74 | dir.is_absolute() 75 | .then(|| dir.join("applications/mpv.desktop")) 76 | .and_then(|path| File::open(path).ok()) 77 | .map(BufReader::new) 78 | .map(BufRead::lines) 79 | }) 80 | .flatten() 81 | .filter_map(Result::ok) 82 | .find_map(|line| { 83 | line.strip_prefix("MimeType=") 84 | .map(|v| v.split_terminator(';').map(str::to_owned).collect()) 85 | }) 86 | .unwrap_or_default() 87 | } 88 | 89 | #[zbus(property)] 90 | fn supported_uri_schemes(self) -> Vec { 91 | get!(self, c"protocol-list") 92 | .split(',') 93 | .map(str::to_owned) 94 | .collect() 95 | } 96 | 97 | #[zbus(property)] 98 | fn can_quit(self) -> bool { 99 | true 100 | } 101 | 102 | fn quit(self) -> fdo::Result<()> { 103 | Ok(command!(self, c"quit")?) 104 | } 105 | 106 | #[zbus(property)] 107 | fn can_raise(self) -> bool { 108 | false 109 | } 110 | 111 | //fn raise(self) {} 112 | 113 | #[zbus(property)] 114 | fn can_set_fullscreen(self) -> bool { 115 | true 116 | } 117 | 118 | #[zbus(property)] 119 | fn fullscreen(self) -> fdo::Result { 120 | Ok(get!(self, c"fullscreen", bool)?) 121 | } 122 | 123 | #[zbus(property)] 124 | fn set_fullscreen(self, fullscreen: bool) -> zbus::Result<()> { 125 | Ok(set!(self, c"fullscreen", bool, fullscreen)?) 126 | } 127 | 128 | #[zbus(property)] 129 | fn has_track_list(self) -> bool { 130 | false 131 | } 132 | } 133 | 134 | #[allow(clippy::unused_self)] 135 | #[interface(name = "org.mpris.MediaPlayer2.Player")] 136 | impl Player { 137 | #[zbus(property)] 138 | fn can_go_next(self) -> bool { 139 | true 140 | } 141 | 142 | fn next(self) -> fdo::Result<()> { 143 | Ok(command!(self, c"playlist-next")?) 144 | } 145 | 146 | #[zbus(property)] 147 | fn can_go_previous(self) -> bool { 148 | true 149 | } 150 | 151 | fn previous(self) -> fdo::Result<()> { 152 | Ok(command!(self, c"playlist-prev")?) 153 | } 154 | 155 | #[zbus(property)] 156 | fn can_pause(self) -> bool { 157 | true 158 | } 159 | 160 | fn pause(self) -> fdo::Result<()> { 161 | Ok(set!(self, c"pause", bool, true)?) 162 | } 163 | 164 | fn play_pause(self) -> fdo::Result<()> { 165 | Ok(command!(self, c"cycle", c"pause")?) 166 | } 167 | 168 | #[zbus(property)] 169 | fn can_play(self) -> bool { 170 | true 171 | } 172 | 173 | fn play(self) -> fdo::Result<()> { 174 | Ok(set!(self, c"pause", bool, false)?) 175 | } 176 | 177 | #[zbus(property)] 178 | fn can_seek(self) -> fdo::Result { 179 | Ok(get!(self, c"seekable", bool)?) 180 | } 181 | 182 | fn seek(self, offset: i64) -> fdo::Result<()> { 183 | let offset = format!("{}\0", time_as_secs(offset)); 184 | Ok(command!(self, c"seek", offset.as_str())?) 185 | } 186 | 187 | #[zbus(signal)] 188 | pub async fn seeked(emitter: &SignalEmitter<'_>, position: i64) -> zbus::Result<()>; 189 | 190 | fn open_uri(self, mut uri: String) -> fdo::Result<()> { 191 | uri.push('\0'); 192 | Ok(command!(self, c"loadfile", uri.as_str())?) 193 | } 194 | 195 | #[zbus(property)] 196 | fn can_control(self) -> bool { 197 | true 198 | } 199 | 200 | fn stop(self) -> fdo::Result<()> { 201 | Ok(command!(self, c"stop")?) 202 | } 203 | 204 | #[zbus(property)] 205 | fn playback_status(self) -> fdo::Result<&'static str> { 206 | playback_status_from(self.0, None, None, None) 207 | } 208 | 209 | #[zbus(property)] 210 | fn loop_status(self) -> &'static str { 211 | loop_status_from(self.0, None, None) 212 | } 213 | 214 | #[zbus(property)] 215 | fn set_loop_status(self, loop_status: &str) -> zbus::Result<()> { 216 | set!( 217 | self, 218 | c"loop-file", 219 | match loop_status { 220 | "Track" => c"inf", 221 | _ => c"no", 222 | } 223 | )?; 224 | set!( 225 | self, 226 | c"loop-playlist", 227 | match loop_status { 228 | "Playlist" => c"inf", 229 | _ => c"no", 230 | } 231 | )?; 232 | Ok(()) 233 | } 234 | 235 | #[zbus(property)] 236 | fn rate(self) -> fdo::Result { 237 | Ok(get!(self, c"speed", f64)?) 238 | } 239 | 240 | #[zbus(property)] 241 | fn set_rate(self, rate: f64) -> zbus::Result<()> { 242 | Ok(set!(self, c"speed", f64, rate)?) 243 | } 244 | 245 | #[zbus(property)] 246 | fn minimum_rate(self) -> fdo::Result { 247 | Ok(get!(self, c"option-info/speed/min", f64)?) 248 | } 249 | 250 | #[zbus(property)] 251 | fn maximum_rate(self) -> fdo::Result { 252 | Ok(get!(self, c"option-info/speed/max", f64)?) 253 | } 254 | 255 | #[zbus(property)] 256 | fn shuffle(self) -> fdo::Result { 257 | Ok(get!(self, c"shuffle", bool)?) 258 | } 259 | 260 | #[zbus(property)] 261 | fn set_shuffle(self, shuffle: bool) -> zbus::Result<()> { 262 | Ok(set!(self, c"shuffle", bool, shuffle)?) 263 | } 264 | 265 | #[zbus(property)] 266 | pub fn metadata(self) -> HashMap<&'static str, Value<'static>> { 267 | metadata(self.0) 268 | } 269 | 270 | #[zbus(property)] 271 | fn volume(self) -> fdo::Result { 272 | Ok(get!(self, c"volume", f64)? / 100.0) 273 | } 274 | 275 | #[zbus(property)] 276 | fn set_volume(self, volume: f64) -> zbus::Result<()> { 277 | Ok(set!(self, c"volume", f64, volume * 100.0)?) 278 | } 279 | 280 | #[zbus(property)] 281 | fn position(self) -> fdo::Result { 282 | Ok(time_from_secs(get!(self, c"playback-time", f64)?)) 283 | } 284 | 285 | #[allow(clippy::needless_pass_by_value)] 286 | fn set_position(self, track_id: ObjectPath, position: i64) -> fdo::Result<()> { 287 | _ = track_id; 288 | Ok(set!(self, c"playback-time", f64, time_as_secs(position))?) 289 | } 290 | } 291 | 292 | pub fn playback_status_from( 293 | mpv: crate::MPVHandle, 294 | idle_active: Option, 295 | eof_reached: Option, 296 | pause: Option, 297 | ) -> fdo::Result<&'static str> { 298 | let idle_active = idle_active.ok_or(()); 299 | if idle_active.or_else(|()| get!(mpv, c"idle-active", bool))? 300 | || eof_reached 301 | .or_else(|| get!(mpv, c"eof-reached", bool).ok()) 302 | .unwrap_or(false) 303 | { 304 | Ok("Stopped") 305 | } else if pause.ok_or(()).or_else(|()| get!(mpv, c"pause", bool))? { 306 | Ok("Paused") 307 | } else { 308 | Ok("Playing") 309 | } 310 | } 311 | 312 | pub fn loop_status_from( 313 | mpv: crate::MPVHandle, 314 | loop_file: Option, 315 | loop_playlist: Option, 316 | ) -> &'static str { 317 | let loop_file = loop_file.unwrap_or_else(|| get!(mpv, c"loop-file") != "no"); 318 | let loop_playlist = loop_playlist.unwrap_or_else(|| get!(mpv, c"loop-playlist") != "no"); 319 | if loop_file { 320 | "Track" 321 | } else if loop_playlist { 322 | "Playlist" 323 | } else { 324 | "None" 325 | } 326 | } 327 | 328 | pub fn metadata(mpv: crate::MPVHandle) -> HashMap<&'static str, Value<'static>> { 329 | const TRACK_ID: Value<'static> = 330 | Value::ObjectPath(ObjectPath::from_static_str_unchecked("/io/mpv")); 331 | 332 | let mut m = HashMap::new(); 333 | m.insert("mpris:trackid", TRACK_ID); 334 | 335 | if let Ok(duration) = get!(mpv, c"duration", f64) { 336 | m.insert("mpris:length", time_from_secs(duration).into()); 337 | } 338 | 339 | let path = get!(mpv, c"path"); 340 | if Url::parse(&path).is_ok() { 341 | m.insert("xesam:url", path.into()); 342 | } else { 343 | let mut file = PathBuf::from(get!(mpv, c"working-directory")); 344 | file.push(path); 345 | if let Ok(uri) = Url::from_file_path(file) { 346 | m.insert("xesam:url", String::from(uri).into()); 347 | } 348 | } 349 | 350 | let data = get!(mpv, c"metadata"); 351 | if let Ok(data) = serde_json::from_str::>(&data) { 352 | for (key, value) in data { 353 | let integer = || -> i32 { 354 | value 355 | .find('/') 356 | .map_or_else(|| &value[..], |x| &value[..x]) 357 | .parse() 358 | .unwrap_or_default() 359 | }; 360 | let (key, value) = match key.to_ascii_lowercase().as_str() { 361 | "album" => ("xesam:album", value.into()), 362 | //"title" => ("xesam:title", value.into()), 363 | "album_artist" => ("xesam:albumArtist", vec![value].into()), 364 | "artist" => ("xesam:artist", vec![value].into()), 365 | "comment" => ("xesam:comment", vec![value].into()), 366 | "composer" => ("xesam:composer", vec![value].into()), 367 | "genre" => ("xesam:genre", vec![value].into()), 368 | "lyricist" => ("xesam:lyricist", vec![value].into()), 369 | "tbp" | "tbpm" | "bpm" => ("xesam:audioBPM", integer().into()), 370 | "disc" => ("xesam:discNumber", integer().into()), 371 | "track" => ("xesam:trackNumber", integer().into()), 372 | lyrics if lyrics.strip_prefix("lyrics").is_some() => ("xesam:asText", value.into()), 373 | _ => continue, 374 | }; 375 | m.insert(key, value); 376 | } 377 | } 378 | 379 | m.insert("xesam:title", get!(mpv, c"media-title").into()); 380 | 381 | if let Some(url) = thumbnail(mpv) { 382 | m.insert("mpris:artUrl", url.into()); 383 | } 384 | 385 | m 386 | } 387 | 388 | fn thumbnail(mpv: crate::MPVHandle) -> Option { 389 | let path = get!(mpv, c"path"); 390 | if path == get!(mpv, c"stream-open-filename") { 391 | Command::new("ffmpegthumbnailer") 392 | .args(["-m", "-cjpeg", "-s0", "-o-", "-i"]) 393 | .arg(&path) 394 | .kill_on_drop(true) 395 | .output() 396 | .or(async { 397 | Timer::after(Duration::from_secs(1)).await; 398 | Err(io::ErrorKind::TimedOut.into()) 399 | }) 400 | .block() 401 | .ok() 402 | .map(|output| { 403 | const PREFIX: &str = "data:image/jpeg;base64,"; 404 | let len = PREFIX.len() + BASE64.encode_len(output.stdout.len()); 405 | let mut data = String::with_capacity(len); 406 | data.push_str(PREFIX); 407 | BASE64.encode_append(&output.stdout, &mut data); 408 | data 409 | }) 410 | } else { 411 | ["yt-dlp", "yt-dlp_x86", "youtube-dl"] 412 | .into_iter() 413 | .find_map(|cmd| { 414 | Command::new(cmd) 415 | .args(["--no-warnings", "--get-thumbnail"]) 416 | .arg(&path) 417 | .kill_on_drop(true) 418 | .output() 419 | .or(async { 420 | Timer::after(Duration::from_secs(5)).await; 421 | Err(io::ErrorKind::TimedOut.into()) 422 | }) 423 | .block() 424 | .ok() 425 | .map(|output| String::from(String::from_utf8_lossy(&output.stdout))) 426 | .map(truncate_newline) 427 | }) 428 | } 429 | .filter(|url| Url::parse(url).is_ok()) 430 | } 431 | 432 | fn truncate_newline(mut s: String) -> String { 433 | if let [.., r, b'\n'] = s.as_bytes() { 434 | if let b'\r' = r { 435 | s.truncate(s.len() - 2); 436 | } else { 437 | s.truncate(s.len() - 1); 438 | } 439 | } 440 | s 441 | } 442 | -------------------------------------------------------------------------------- /src/plugin.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, ffi::c_int, iter, process, thread, vec}; 2 | 3 | use zbus::zvariant; 4 | 5 | #[allow(clippy::wildcard_imports)] 6 | use crate::ffi::*; 7 | #[allow(clippy::wildcard_imports)] 8 | use crate::llb::*; 9 | 10 | mod ffi; 11 | mod llb; 12 | mod macros; 13 | mod mpris2; 14 | 15 | macro_rules! strc { 16 | ($s:expr) => { 17 | std::ffi::CStr::from_ptr($s).to_str().unwrap_or_default() 18 | }; 19 | } 20 | 21 | /// # Safety 22 | #[no_mangle] 23 | pub unsafe extern "C" fn mpv_open_cplugin(mpv: *mut mpv_handle) -> c_int { 24 | if mpv.is_null() { 25 | return 1; 26 | } 27 | 28 | let name = strc!(mpv_client_name(mpv)); 29 | let mpv = MPVHandle(mpv); 30 | 31 | match init(mpv).as_ref() { 32 | Ok(ctxt) => { 33 | register(mpv); 34 | do_loop(mpv, ctxt, name); 35 | 0 36 | } 37 | Err(err) => { 38 | eprintln!("[{name}]: {err}"); 39 | 1 40 | } 41 | } 42 | } 43 | 44 | fn init(mpv: MPVHandle) -> zbus::Result> { 45 | use zbus::names::WellKnownName; 46 | use zvariant::ObjectPath; 47 | const PATH_STR: &str = "/org/mpris/MediaPlayer2"; 48 | const PATH: ObjectPath<'_> = ObjectPath::from_static_str_unchecked(PATH_STR); 49 | let pid = process::id(); 50 | let name = format!("org.mpris.MediaPlayer2.mpv.instance{pid}"); 51 | let connection = zbus::connection::Builder::session()? 52 | .name(WellKnownName::from_string_unchecked(name))? 53 | .serve_at(PATH, crate::mpris2::Root(mpv))? 54 | .serve_at(PATH, crate::mpris2::Player(mpv))? 55 | .internal_executor(false) 56 | .build() 57 | .block()?; 58 | let executor = connection.executor().clone(); 59 | thread::Builder::new() 60 | .name("mpv/mpris/zbus".into()) 61 | .spawn(move || { 62 | async move { 63 | while !executor.is_empty() { 64 | executor.tick().await; 65 | } 66 | } 67 | .block(); 68 | })?; 69 | let connection = zbus::object_server::SignalEmitter::from_parts(connection, PATH); 70 | Ok(connection) 71 | } 72 | 73 | // These properties and those handled in the main loop must be kept in sync with those 74 | // mentioned in the interface implementations. 75 | // It's a bit of a pain in the ass but there's no other way. 76 | fn register(mpv: MPVHandle) { 77 | observe!(mpv, c"media-title", c"metadata", c"duration"); 78 | observe!( 79 | mpv, 80 | MPV_FORMAT_STRING, 81 | c"keep-open", 82 | c"loop-file", 83 | c"loop-playlist", 84 | ); 85 | observe!( 86 | mpv, 87 | MPV_FORMAT_FLAG, 88 | c"fullscreen", 89 | c"seekable", 90 | c"idle-active", 91 | c"eof-reached", 92 | c"pause", 93 | c"shuffle", 94 | ); 95 | observe!(mpv, MPV_FORMAT_DOUBLE, c"speed", c"volume"); 96 | } 97 | 98 | fn do_loop(mpv: MPVHandle, ctxt: &zbus::object_server::SignalEmitter, name: &str) { 99 | macro_rules! data { 100 | ($source:expr, bool) => { 101 | data!($source, std::ffi::c_int) != 0 102 | }; 103 | ($source:expr, &str) => { 104 | strc!(*$source.data.cast()) 105 | }; 106 | ($source:expr, $type:ty) => { 107 | *$source.data.cast::<$type>() 108 | }; 109 | } 110 | 111 | let elog = |err| eprintln!("[{name}] {err:?}"); 112 | 113 | let mut keep_open = false; 114 | let mut seeking = false; 115 | let mut root = Vec::new(); 116 | let mut player = Vec::new(); 117 | loop { 118 | let mut state = scopeguard::guard( 119 | (State::default(), &mut root, &mut player), 120 | |(state, root, player)| { 121 | signal_changed(mpv, state, ctxt, root.drain(..), player.drain(..)) 122 | .for_each(|err| err.unwrap_or_else(elog)); 123 | }, 124 | ); 125 | let (state, &mut ref mut root_changed, &mut ref mut player_changed) = &mut *state; 126 | 127 | for ev in iter::once(-1.0) 128 | .chain(iter::repeat(0.0)) 129 | .map(|timeout| unsafe { *mpv_wait_event(mpv.into(), timeout) }) 130 | { 131 | match ev.event_id { 132 | MPV_EVENT_NONE => break, 133 | MPV_EVENT_SHUTDOWN => return, 134 | MPV_EVENT_SEEK => seeking = true, 135 | MPV_EVENT_PLAYBACK_RESTART if seeking => { 136 | seeking = false; 137 | if let Ok(position) = get!(mpv, c"playback-time", f64) { 138 | seeked(ctxt, mpris2::time_from_secs(position)).unwrap_or_else(elog); 139 | } 140 | } 141 | MPV_EVENT_PROPERTY_CHANGE => { 142 | let prop = unsafe { data!(ev, mpv_event_property) }; 143 | let name = unsafe { strc!(prop.name) }; 144 | match (name, prop.format) { 145 | ("media-title" | "metadata" | "duration", _) => { 146 | state.metadata = true; 147 | } 148 | ("keep-open", MPV_FORMAT_STRING) => { 149 | keep_open = unsafe { data!(prop, &str) } != "no"; 150 | state.keep_open = Some(keep_open); 151 | } 152 | ("loop-file", MPV_FORMAT_STRING) => { 153 | state.loop_file = Some(unsafe { data!(prop, &str) } != "no"); 154 | } 155 | ("loop-playlist", MPV_FORMAT_STRING) => { 156 | state.loop_playlist = Some(unsafe { data!(prop, &str) } != "no"); 157 | } 158 | ("fullscreen", MPV_FORMAT_FLAG) => { 159 | root_changed.push(("Fullscreen", unsafe { data!(prop, bool) }.into())); 160 | } 161 | ("seekable", MPV_FORMAT_FLAG) => { 162 | player_changed.push(("CanSeek", unsafe { data!(prop, bool) }.into())); 163 | } 164 | ("idle-active", MPV_FORMAT_FLAG) => { 165 | state.idle_active = Some(unsafe { data!(prop, bool) }); 166 | } 167 | ("eof-reached", MPV_FORMAT_FLAG) if keep_open => { 168 | state.eof_reached = Some(unsafe { data!(prop, bool) }); 169 | } 170 | ("pause", MPV_FORMAT_FLAG) => { 171 | state.pause = Some(unsafe { data!(prop, bool) }); 172 | } 173 | ("shuffle", MPV_FORMAT_FLAG) => { 174 | player_changed.push(("Shuffle", unsafe { data!(prop, bool) }.into())); 175 | } 176 | ("speed", MPV_FORMAT_DOUBLE) => { 177 | player_changed.push(("Rate", unsafe { data!(prop, f64) }.into())); 178 | } 179 | ("volume", MPV_FORMAT_DOUBLE) => { 180 | player_changed 181 | .push(("Volume", (unsafe { data!(prop, f64) } / 100.0).into())); 182 | } 183 | _ => {} 184 | } 185 | } 186 | _ => {} 187 | } 188 | } 189 | } 190 | } 191 | 192 | #[derive(Clone, Default)] 193 | struct State { 194 | idle_active: Option, 195 | keep_open: Option, 196 | eof_reached: Option, 197 | pause: Option, 198 | loop_file: Option, 199 | loop_playlist: Option, 200 | metadata: bool, 201 | } 202 | 203 | impl State { 204 | fn playback_status(&mut self, mpv: MPVHandle) -> Option> { 205 | if self.idle_active.is_some() 206 | | self.keep_open.is_some() 207 | | self.eof_reached.is_some() 208 | | self.pause.is_some() 209 | { 210 | self.keep_open.take(); 211 | mpris2::playback_status_from( 212 | mpv, 213 | self.idle_active.take(), 214 | self.eof_reached.take(), 215 | self.pause.take(), 216 | ) 217 | .ok() 218 | } else { 219 | None 220 | } 221 | .map(Into::into) 222 | } 223 | fn loop_status(&mut self, mpv: MPVHandle) -> Option> { 224 | if self.loop_file.is_some() | self.loop_playlist.is_some() { 225 | Some(mpris2::loop_status_from( 226 | mpv, 227 | self.loop_file.take(), 228 | self.loop_playlist.take(), 229 | )) 230 | } else { 231 | None 232 | } 233 | .map(Into::into) 234 | } 235 | fn metadata(&mut self, mpv: MPVHandle) -> Option> { 236 | if self.metadata { 237 | Some(mpris2::metadata(mpv)) 238 | } else { 239 | None 240 | } 241 | .map(Into::into) 242 | } 243 | } 244 | 245 | fn signal_changed( 246 | mpv: MPVHandle, 247 | mut state: State, 248 | emitter: &zbus::object_server::SignalEmitter<'_>, 249 | root: vec::Drain<(&str, zvariant::Value<'_>)>, 250 | player: vec::Drain<(&str, zvariant::Value<'_>)>, 251 | ) -> impl Iterator> { 252 | let root: HashMap<_, _> = root.collect(); 253 | let mut player: HashMap<_, _> = player.collect(); 254 | 255 | state 256 | .playback_status(mpv) 257 | .and_then(|v| player.insert("PlaybackStatus", v)); 258 | state 259 | .loop_status(mpv) 260 | .and_then(|v| player.insert("LoopStatus", v)); 261 | state 262 | .metadata(mpv) 263 | .and_then(|v| player.insert("Metadata", v)); 264 | 265 | [ 266 | properties_changed::(emitter, root), 267 | properties_changed::(emitter, player), 268 | ] 269 | .into_iter() 270 | } 271 | --------------------------------------------------------------------------------