pipewire-0.8.0/.cargo_vcs_info.json0000644000000001460000000000100126660ustar { "git": { "sha1": "449bf53f5d5edc8d0be6c0c80bc19d882f712dd7" }, "path_in_vcs": "pipewire" }pipewire-0.8.0/Cargo.lock0000644000000504600000000000100106450ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "annotate-snippets" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" dependencies = [ "unicode-width", "yansi-term", ] [[package]] name = "anstream" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "anyhow" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "bindgen" version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "annotate-snippets", "bitflags", "cexpr", "clang-sys", "itertools", "lazy_static", "lazycell", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", ] [[package]] name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfg-expr" version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6100bc57b6209840798d95cb2775684849d332f7bd788db2a8c8caf7ef82a41a" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "clap" version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "convert_case" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" dependencies = [ "unicode-segmentation", ] [[package]] name = "cookie-factory" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "indexmap" version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] [[package]] name = "libspa" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810" dependencies = [ "bitflags", "cc", "convert_case", "cookie-factory", "libc", "libspa-sys", "nix", "nom", "system-deps", ] [[package]] name = "libspa-sys" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f" dependencies = [ "bindgen", "cc", "system-deps", ] [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nix" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "pipewire" version = "0.8.0" dependencies = [ "anyhow", "bitflags", "clap", "libc", "libspa", "libspa-sys", "nix", "once_cell", "pipewire-sys", "thiserror", ] [[package]] name = "pipewire-sys" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112" dependencies = [ "bindgen", "libspa-sys", "system-deps", ] [[package]] name = "pkg-config" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "proc-macro2" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "serde" version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "smallvec" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" [[package]] name = "thiserror" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "toml" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "version-compare" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.0", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm 0.52.0", "windows_aarch64_msvc 0.52.0", "windows_i686_gnu 0.52.0", "windows_i686_msvc 0.52.0", "windows_x86_64_gnu 0.52.0", "windows_x86_64_gnullvm 0.52.0", "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" version = "0.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" dependencies = [ "memchr", ] [[package]] name = "yansi-term" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" dependencies = [ "winapi", ] pipewire-0.8.0/Cargo.toml0000644000000037370000000000100106750ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65" name = "pipewire" version = "0.8.0" authors = [ "Tom Wagner ", ] description = "Rust bindings for PipeWire" homepage = "https://pipewire.org" documentation = "https://pipewire.pages.freedesktop.org/pipewire-rs/pipewire/" readme = "README.md" keywords = [ "pipewire", "multimedia", "audio", "video", ] categories = [ "api-bindings", "multimedia", ] license = "MIT" repository = "https://gitlab.freedesktop.org/pipewire/pipewire-rs" [dependencies.anyhow] version = "1" [dependencies.bitflags] version = "2" [dependencies.libc] version = "0.2" [dependencies.nix] version = "0.27" features = [ "signal", "fs", ] [dependencies.once_cell] version = "1.0" [dependencies.pw_sys] version = "0.8" package = "pipewire-sys" [dependencies.spa] version = "0.8" package = "libspa" [dependencies.spa_sys] version = "0.8" package = "libspa-sys" [dependencies.thiserror] version = "1" [dev-dependencies.clap] version = "4.3.2" features = ["derive"] [dev-dependencies.once_cell] version = "1.5" [features] v0_3_32 = [] v0_3_33 = [ "spa/v0_3_33", "v0_3_32", ] v0_3_34 = ["v0_3_33"] v0_3_39 = ["v0_3_34"] v0_3_40 = ["v0_3_39"] v0_3_41 = ["v0_3_40"] v0_3_43 = ["v0_3_41"] v0_3_44 = ["v0_3_43"] v0_3_45 = ["v0_3_44"] v0_3_49 = ["v0_3_45"] v0_3_53 = ["v0_3_49"] v0_3_57 = ["v0_3_53"] v0_3_64 = ["v0_3_57"] v0_3_65 = [ "spa/v0_3_65", "v0_3_64", ] v0_3_77 = ["v0_3_65"] pipewire-0.8.0/Cargo.toml.orig000064400000000000000000000025471046102023000143540ustar 00000000000000[package] name = "pipewire" version = "0.8.0" authors = ["Tom Wagner "] rust-version = "1.65" edition = "2021" categories = ["api-bindings", "multimedia"] description = "Rust bindings for PipeWire" repository = "https://gitlab.freedesktop.org/pipewire/pipewire-rs" license = "MIT" readme = "README.md" homepage = "https://pipewire.org" documentation = "https://pipewire.pages.freedesktop.org/pipewire-rs/pipewire/" keywords = ["pipewire", "multimedia", "audio", "video"] [dependencies] pw_sys = { package = "pipewire-sys", version = "0.8", path = "../pipewire-sys" } spa_sys = { package = "libspa-sys", version = "0.8", path = "../libspa-sys" } spa = { package = "libspa", version = "0.8", path = "../libspa" } anyhow = "1" thiserror = "1" libc = "0.2" nix = { version = "0.27", features = ["signal", "fs"] } bitflags = "2" once_cell = "1.0" [dev-dependencies] clap = { version = "4.3.2", features = ["derive"] } once_cell = "1.5" [features] v0_3_32 = [] v0_3_33 = ["spa/v0_3_33", "v0_3_32"] v0_3_34 = ["v0_3_33"] v0_3_39 = ["v0_3_34"] v0_3_40 = ["v0_3_39"] v0_3_41 = ["v0_3_40"] v0_3_43 = ["v0_3_41"] v0_3_44 = ["v0_3_43"] v0_3_45 = ["v0_3_44"] v0_3_49 = ["v0_3_45"] v0_3_53 = ["v0_3_49"] v0_3_57 = ["v0_3_53"] v0_3_64 = ["v0_3_57"] v0_3_65 = ["spa/v0_3_65", "v0_3_64"] v0_3_77 = ["v0_3_65"] pipewire-0.8.0/LICENSE000064400000000000000000000021101046102023000124540ustar 00000000000000Copyright The pipewire-rs Contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pipewire-0.8.0/README.md000064400000000000000000000006701046102023000127370ustar 00000000000000# pipewire [![](https://img.shields.io/crates/v/pipewire.svg)](https://crates.io/crates/pipewire) [![](https://docs.rs/pipewire/badge.svg)](https://docs.rs/pipewire) [PipeWire](https://pipewire.org) bindings for Rust. These bindings are providing a safe API that can be used to interface with [PipeWire](https://pipewire.org). ## Documentation See the [crate documentation](https://pipewire.pages.freedesktop.org/pipewire-rs/pipewire/).pipewire-0.8.0/examples/audio-capture.rs000064400000000000000000000147771046102023000164230ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT //! This file is a rustic interpretation of the [PipeWire audio-capture.c example][example] //! //! example: https://docs.pipewire.org/audio-capture_8c-example.html use clap::Parser; use pipewire as pw; use pw::{properties::properties, spa}; use spa::param::format::{MediaSubtype, MediaType}; use spa::param::format_utils; use spa::pod::Pod; #[cfg(feature = "v0_3_44")] use spa::WritableDict; use std::convert::TryInto; use std::mem; struct UserData { format: spa::param::audio::AudioInfoRaw, cursor_move: bool, } #[derive(Parser)] #[clap(name = "audio-capture", about = "Audio stream capture example")] struct Opt { #[clap(short, long, help = "The target object id to connect to")] target: Option, } pub fn main() -> Result<(), pw::Error> { pw::init(); let mainloop = pw::main_loop::MainLoop::new(None)?; let context = pw::context::Context::new(&mainloop)?; let core = context.connect(None)?; let data = UserData { format: Default::default(), cursor_move: false, }; /* Create a simple stream, the simple stream manages the core and remote * objects for you if you don't need to deal with them. * * If you plan to autoconnect your stream, you need to provide at least * media, category and role properties. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to produce * the data. */ #[cfg(not(feature = "v0_3_44"))] let props = properties! { *pw::keys::MEDIA_TYPE => "Audio", *pw::keys::MEDIA_CATEGORY => "Capture", *pw::keys::MEDIA_ROLE => "Music", }; #[cfg(feature = "v0_3_44")] let props = { let opt = Opt::parse(); let mut props = properties! { *pw::keys::MEDIA_TYPE => "Audio", *pw::keys::MEDIA_CATEGORY => "Capture", *pw::keys::MEDIA_ROLE => "Music", }; if let Some(target) = opt.target { props.insert(*pw::keys::TARGET_OBJECT, target); } props }; // uncomment if you want to capture from the sink monitor ports // props.insert(*pw::keys::STREAM_CAPTURE_SINK, "true"); let stream = pw::stream::Stream::new(&core, "audio-capture", props)?; let _listener = stream .add_local_listener_with_user_data(data) .param_changed(|_, user_data, id, param| { // NULL means to clear the format let Some(param) = param else { return; }; if id != pw::spa::param::ParamType::Format.as_raw() { return; } let (media_type, media_subtype) = match format_utils::parse_format(param) { Ok(v) => v, Err(_) => return, }; // only accept raw audio if media_type != MediaType::Audio || media_subtype != MediaSubtype::Raw { return; } // call a helper function to parse the format for us. user_data .format .parse(param) .expect("Failed to parse param changed to AudioInfoRaw"); println!( "capturing rate:{} channels:{}", user_data.format.rate(), user_data.format.channels() ); }) .process(|stream, user_data| match stream.dequeue_buffer() { None => println!("out of buffers"), Some(mut buffer) => { let datas = buffer.datas_mut(); if datas.is_empty() { return; } let data = &mut datas[0]; let n_channels = user_data.format.channels(); let n_samples = data.chunk().size() / (mem::size_of::() as u32); if let Some(samples) = data.data() { if user_data.cursor_move { print!("\x1B[{}A", n_channels + 1); } println!("captured {} samples", n_samples / n_channels); for c in 0..n_channels { let mut max: f32 = 0.0; for n in (c..n_samples).step_by(n_channels as usize) { let start = n as usize * mem::size_of::(); let end = start + mem::size_of::(); let chan = &samples[start..end]; let f = f32::from_le_bytes(chan.try_into().unwrap()); max = max.max(f.abs()); } let peak = ((max * 30.0) as usize).clamp(0, 39); println!( "channel {}: |{:>w1$}{:w2$}| peak:{}", c, "*", "", max, w1 = peak + 1, w2 = 40 - peak ); } user_data.cursor_move = true; } } }) .register()?; /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat * id means that this is a format enumeration (of 1 value). * We leave the channels and rate empty to accept the native graph * rate and channels. */ let mut audio_info = spa::param::audio::AudioInfoRaw::new(); audio_info.set_format(spa::param::audio::AudioFormat::F32LE); let obj = pw::spa::pod::Object { type_: pw::spa::utils::SpaTypes::ObjectParamFormat.as_raw(), id: pw::spa::param::ParamType::EnumFormat.as_raw(), properties: audio_info.into(), }; let values: Vec = pw::spa::pod::serialize::PodSerializer::serialize( std::io::Cursor::new(Vec::new()), &pw::spa::pod::Value::Object(obj), ) .unwrap() .0 .into_inner(); let mut params = [Pod::from_bytes(&values).unwrap()]; /* Now connect this stream. We ask that our process function is * called in a realtime thread. */ stream.connect( spa::utils::Direction::Input, None, pw::stream::StreamFlags::AUTOCONNECT | pw::stream::StreamFlags::MAP_BUFFERS | pw::stream::StreamFlags::RT_PROCESS, &mut params, )?; // and wait while we let things run mainloop.run(); Ok(()) } pipewire-0.8.0/examples/create-delete-remote-objects.rs000064400000000000000000000072001046102023000212630ustar 00000000000000use std::{cell::Cell, rc::Rc}; use once_cell::unsync::OnceCell; use pipewire as pw; use pw::types::ObjectType; fn main() { // Initialize library and get the basic structures we need. pw::init(); let mainloop = pw::main_loop::MainLoop::new(None).expect("Failed to create Pipewire Mainloop"); let context = pw::context::Context::new(&mainloop).expect("Failed to create Pipewire Context"); let core = context .connect(None) .expect("Failed to connect to Pipewire Core"); let registry = core.get_registry().expect("Failed to get Registry"); // Setup a registry listener that will obtain the name of a link factory and write it into `factory`. let factory: Rc> = Rc::new(OnceCell::new()); let factory_clone = factory.clone(); let mainloop_clone = mainloop.clone(); let reg_listener = registry .add_listener_local() .global(move |global| { if let Some(props) = global.props { // Check that the global is a factory that creates the right type. if props.get("factory.type.name") == Some(ObjectType::Link.to_str()) { let factory_name = props.get("factory.name").expect("Factory has no name"); factory_clone .set(factory_name.to_owned()) .expect("Factory name already set"); // We found the factory we needed, so quit the loop. mainloop_clone.quit(); } } }) .register(); // Process all pending events to get the factory. do_roundtrip(&mainloop, &core); // Now that we have our factory, we are no longer interested in any globals from the registry, // so we unregister the listener by dropping it. std::mem::drop(reg_listener); // Now that we have the name of a link factory, we can create an object with it! let link = core .create_object::( factory.get().expect("No link factory found"), &pw::properties::properties! { "link.output.port" => "1", "link.input.port" => "2", "link.output.node" => "3", "link.input.node" => "4", // Don't remove the object on the remote when we destroy our proxy. "object.linger" => "1" }, ) .expect("Failed to create object"); // Do another roundtrip so that the link gets created on the server side. do_roundtrip(&mainloop, &core); // We have our object, now manually destroy it on the remote again. core.destroy_object(link).expect("destroy object failed"); // Do a final roundtrip to destroy the link on the server side again. do_roundtrip(&mainloop, &core); } /// Do a single roundtrip to process all events. /// See the example in roundtrip.rs for more details on this. fn do_roundtrip(mainloop: &pw::main_loop::MainLoop, core: &pw::core::Core) { let done = Rc::new(Cell::new(false)); let done_clone = done.clone(); let loop_clone = mainloop.clone(); // Trigger the sync event. The server's answer won't be processed until we start the main loop, // so we can safely do this before setting up a callback. This lets us avoid using a Cell. let pending = core.sync(0).expect("sync failed"); let _listener_core = core .add_listener_local() .done(move |id, seq| { if id == pw::core::PW_ID_CORE && seq == pending { done_clone.set(true); loop_clone.quit(); } }) .register(); while !done.get() { mainloop.run(); } } pipewire-0.8.0/examples/pw-mon.rs000064400000000000000000000165621046102023000150700ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use anyhow::Result; use clap::Parser; use pipewire as pw; use spa::pod::Pod; use std::rc::Rc; use std::{cell::RefCell, collections::HashMap}; use pw::{ link::Link, loop_::Signal, metadata::Metadata, node::Node, port::Port, properties::properties, proxy::{Listener, ProxyListener, ProxyT}, types::ObjectType, }; struct Proxies { proxies_t: HashMap>, listeners: HashMap>>, } impl Proxies { fn new() -> Self { Self { proxies_t: HashMap::new(), listeners: HashMap::new(), } } fn add_proxy_t(&mut self, proxy_t: Box, listener: Box) { let proxy_id = { let proxy = proxy_t.upcast_ref(); proxy.id() }; self.proxies_t.insert(proxy_id, proxy_t); let v = self.listeners.entry(proxy_id).or_insert_with(Vec::new); v.push(listener); } fn add_proxy_listener(&mut self, proxy_id: u32, listener: ProxyListener) { let v = self.listeners.entry(proxy_id).or_insert_with(Vec::new); v.push(Box::new(listener)); } fn remove(&mut self, proxy_id: u32) { self.proxies_t.remove(&proxy_id); self.listeners.remove(&proxy_id); } } fn monitor(remote: Option) -> Result<()> { let main_loop = pw::main_loop::MainLoop::new(None)?; let main_loop_weak = main_loop.downgrade(); let _sig_int = main_loop.loop_().add_signal_local(Signal::SIGINT, move || { if let Some(main_loop) = main_loop_weak.upgrade() { main_loop.quit(); } }); let main_loop_weak = main_loop.downgrade(); let _sig_term = main_loop .loop_() .add_signal_local(Signal::SIGTERM, move || { if let Some(main_loop) = main_loop_weak.upgrade() { main_loop.quit(); } }); let context = pw::context::Context::new(&main_loop)?; let props = remote.map(|remote| { properties! { *pw::keys::REMOTE_NAME => remote } }); let core = context.connect(props)?; let main_loop_weak = main_loop.downgrade(); let _listener = core .add_listener_local() .info(|info| { dbg!(info); }) .done(|_id, _seq| { // TODO }) .error(move |id, seq, res, message| { eprintln!("error id:{} seq:{} res:{}: {}", id, seq, res, message); if id == 0 { if let Some(main_loop) = main_loop_weak.upgrade() { main_loop.quit(); } } }) .register(); let registry = Rc::new(core.get_registry()?); let registry_weak = Rc::downgrade(®istry); // Proxies and their listeners need to stay alive so store them here let proxies = Rc::new(RefCell::new(Proxies::new())); let _registry_listener = registry .add_listener_local() .global(move |obj| { if let Some(registry) = registry_weak.upgrade() { let p: Option<(Box, Box)> = match obj.type_ { ObjectType::Node => { let node: Node = registry.bind(obj).unwrap(); let obj_listener = node .add_listener_local() .info(|info| { dbg!(info); }) .param(|seq, id, index, next, param| { dbg!((seq, id, index, next, param.map(Pod::as_bytes))); }) .register(); Some((Box::new(node), Box::new(obj_listener))) } ObjectType::Port => { let port: Port = registry.bind(obj).unwrap(); let obj_listener = port .add_listener_local() .info(|info| { dbg!(info); }) .param(|seq, id, index, next, param| { dbg!((seq, id, index, next, param.map(Pod::as_bytes))); }) .register(); Some((Box::new(port), Box::new(obj_listener))) } ObjectType::Link => { let link: Link = registry.bind(obj).unwrap(); let obj_listener = link .add_listener_local() .info(|info| { dbg!(info); }) .register(); Some((Box::new(link), Box::new(obj_listener))) } ObjectType::Metadata => { let metadata: Metadata = registry.bind(obj).unwrap(); dbg!(&obj.props); let obj_listener = metadata .add_listener_local() .property(|subject, key, type_, value| { dbg!((subject, key, type_, value)); 0 }) .register(); Some((Box::new(metadata), Box::new(obj_listener))) } ObjectType::Module | ObjectType::Device | ObjectType::Factory | ObjectType::Client => { // TODO None } _ => { dbg!(obj); None } }; if let Some((proxy_spe, listener_spe)) = p { let proxy = proxy_spe.upcast_ref(); let proxy_id = proxy.id(); // Use a weak ref to prevent references cycle between Proxy and proxies: // - ref on proxies in the closure, bound to the Proxy lifetime // - proxies owning a ref on Proxy as well let proxies_weak = Rc::downgrade(&proxies); let listener = proxy .add_listener_local() .removed(move || { if let Some(proxies) = proxies_weak.upgrade() { proxies.borrow_mut().remove(proxy_id); } }) .register(); proxies.borrow_mut().add_proxy_t(proxy_spe, listener_spe); proxies.borrow_mut().add_proxy_listener(proxy_id, listener); } } }) .global_remove(|id| { println!("removed:"); println!("\tid: {}", id); }) .register(); main_loop.run(); Ok(()) } #[derive(Parser)] #[clap(name = "pw-mon", about = "PipeWire monitor")] struct Opt { #[clap(short, long, help = "The name of the remote to connect to")] remote: Option, } fn main() -> Result<()> { pw::init(); let opt = Opt::parse(); monitor(opt.remote)?; unsafe { pw::deinit(); } Ok(()) } pipewire-0.8.0/examples/roundtrip.rs000064400000000000000000000033141046102023000156700ustar 00000000000000//! This program is the rust equivalent of https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/doc/tutorial3.md. use pipewire as pw; use std::{cell::Cell, rc::Rc}; fn main() { pw::init(); roundtrip(); unsafe { pw::deinit() }; } fn roundtrip() { let mainloop = pw::main_loop::MainLoop::new(None).expect("Failed to create main loop"); let context = pw::context::Context::new(&mainloop).expect("Failed to create context"); let core = context.connect(None).expect("Failed to connect to core"); let registry = core.get_registry().expect("Failed to get Registry"); // To comply with Rust's safety rules, we wrap this variable in an `Rc` and a `Cell`. let done = Rc::new(Cell::new(false)); // Create new reference for each variable so that they can be moved into the closure. let done_clone = done.clone(); let loop_clone = mainloop.clone(); // Trigger the sync event. The server's answer won't be processed until we start the main loop, // so we can safely do this before setting up a callback. This lets us avoid using a Cell. let pending = core.sync(0).expect("sync failed"); let _listener_core = core .add_listener_local() .done(move |id, seq| { if id == pw::core::PW_ID_CORE && seq == pending { done_clone.set(true); loop_clone.quit(); } }) .register(); let _listener_reg = registry .add_listener_local() .global(|global| { println!( "object: id:{} type:{}/{}", global.id, global.type_, global.version ) }) .register(); while !done.get() { mainloop.run(); } } pipewire-0.8.0/examples/streams.rs000064400000000000000000000135101046102023000153170ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT //! This file is a rustic interpretation of the the [PipeWire Tutorial 5][tut] //! //! tut: https://docs.pipewire.org/page_tutorial5.html use pipewire as pw; use pw::{properties::properties, spa}; use clap::Parser; use spa::pod::Pod; struct UserData { format: spa::param::video::VideoInfoRaw, } #[derive(Parser)] #[clap(name = "streams", about = "Stream example")] struct Opt { #[clap(short, long, help = "The target object id to connect to")] target: Option, } pub fn main() -> Result<(), pw::Error> { pw::init(); let opt = Opt::parse(); let mainloop = pw::main_loop::MainLoop::new(None)?; let context = pw::context::Context::new(&mainloop)?; let core = context.connect(None)?; let data = UserData { format: Default::default(), }; let stream = pw::stream::Stream::new( &core, "video-test", properties! { *pw::keys::MEDIA_TYPE => "Video", *pw::keys::MEDIA_CATEGORY => "Capture", *pw::keys::MEDIA_ROLE => "Camera", }, )?; /*let stream = pw::stream::Stream::::with_user_data( &mainloop, "video-test", , data, )*/ let _listener = stream .add_local_listener_with_user_data(data) .state_changed(|_, _, old, new| { println!("State changed: {:?} -> {:?}", old, new); }) .param_changed(|_, user_data, id, param| { let Some(param) = param else { return; }; if id != pw::spa::param::ParamType::Format.as_raw() { return; } let (media_type, media_subtype) = match pw::spa::param::format_utils::parse_format(param) { Ok(v) => v, Err(_) => return, }; if media_type != pw::spa::param::format::MediaType::Video || media_subtype != pw::spa::param::format::MediaSubtype::Raw { return; } user_data .format .parse(param) .expect("Failed to parse param changed to VideoInfoRaw"); println!("got video format:"); println!( " format: {} ({:?})", user_data.format.format().as_raw(), user_data.format.format() ); println!( " size: {}x{}", user_data.format.size().width, user_data.format.size().height ); println!( " framerate: {}/{}", user_data.format.framerate().num, user_data.format.framerate().denom ); // prepare to render video of this size }) .process(|stream, _| { match stream.dequeue_buffer() { None => println!("out of buffers"), Some(mut buffer) => { let datas = buffer.datas_mut(); if datas.is_empty() { return; } // copy frame data to screen let data = &mut datas[0]; println!("got a frame of size {}", data.chunk().size()); } } }) .register()?; println!("Created stream {:#?}", stream); let obj = pw::spa::pod::object!( pw::spa::utils::SpaTypes::ObjectParamFormat, pw::spa::param::ParamType::EnumFormat, pw::spa::pod::property!( pw::spa::param::format::FormatProperties::MediaType, Id, pw::spa::param::format::MediaType::Video ), pw::spa::pod::property!( pw::spa::param::format::FormatProperties::MediaSubtype, Id, pw::spa::param::format::MediaSubtype::Raw ), pw::spa::pod::property!( pw::spa::param::format::FormatProperties::VideoFormat, Choice, Enum, Id, pw::spa::param::video::VideoFormat::RGB, pw::spa::param::video::VideoFormat::RGB, pw::spa::param::video::VideoFormat::RGBA, pw::spa::param::video::VideoFormat::RGBx, pw::spa::param::video::VideoFormat::BGRx, pw::spa::param::video::VideoFormat::YUY2, pw::spa::param::video::VideoFormat::I420, ), pw::spa::pod::property!( pw::spa::param::format::FormatProperties::VideoSize, Choice, Range, Rectangle, pw::spa::utils::Rectangle { width: 320, height: 240 }, pw::spa::utils::Rectangle { width: 1, height: 1 }, pw::spa::utils::Rectangle { width: 4096, height: 4096 } ), pw::spa::pod::property!( pw::spa::param::format::FormatProperties::VideoFramerate, Choice, Range, Fraction, pw::spa::utils::Fraction { num: 25, denom: 1 }, pw::spa::utils::Fraction { num: 0, denom: 1 }, pw::spa::utils::Fraction { num: 1000, denom: 1 } ), ); let values: Vec = pw::spa::pod::serialize::PodSerializer::serialize( std::io::Cursor::new(Vec::new()), &pw::spa::pod::Value::Object(obj), ) .unwrap() .0 .into_inner(); let mut params = [Pod::from_bytes(&values).unwrap()]; stream.connect( spa::utils::Direction::Input, opt.target, pw::stream::StreamFlags::AUTOCONNECT | pw::stream::StreamFlags::MAP_BUFFERS, &mut params, )?; println!("Connected stream"); mainloop.run(); Ok(()) } pipewire-0.8.0/examples/tone.rs000064400000000000000000000066571046102023000146240ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT //! This file is a rustic interpretation of the the [PipeWire Tutorial 4][tut] //! //! tut: https://docs.pipewire.org/page_tutorial4.html use pipewire as pw; use pw::{properties::properties, spa}; use spa::pod::Pod; pub const DEFAULT_RATE: u32 = 44100; pub const DEFAULT_CHANNELS: u32 = 2; pub const DEFAULT_VOLUME: f64 = 0.7; pub const PI_2: f64 = std::f64::consts::PI + std::f64::consts::PI; pub const CHAN_SIZE: usize = std::mem::size_of::(); pub fn main() -> Result<(), pw::Error> { pw::init(); let mainloop = pw::main_loop::MainLoop::new(None)?; let context = pw::context::Context::new(&mainloop)?; let core = context.connect(None)?; let data: f64 = 0.0; let stream = pw::stream::Stream::new( &core, "audio-src", properties! { *pw::keys::MEDIA_TYPE => "Audio", *pw::keys::MEDIA_ROLE => "Music", *pw::keys::MEDIA_CATEGORY => "Playback", }, )?; let _listener = stream .add_local_listener_with_user_data(data) .process(|stream, acc| match stream.dequeue_buffer() { None => println!("No buffer received"), Some(mut buffer) => { let datas = buffer.datas_mut(); let stride = CHAN_SIZE * DEFAULT_CHANNELS as usize; let data = &mut datas[0]; let n_frames = if let Some(slice) = data.data() { let n_frames = slice.len() / stride; for i in 0..n_frames { *acc += PI_2 * 440.0 / DEFAULT_RATE as f64; if *acc >= PI_2 { *acc -= PI_2 } let val = (f64::sin(*acc) * DEFAULT_VOLUME * 16767.0) as i16; for c in 0..DEFAULT_CHANNELS { let start = i * stride + (c as usize * CHAN_SIZE); let end = start + CHAN_SIZE; let chan = &mut slice[start..end]; chan.copy_from_slice(&i16::to_le_bytes(val)); } } n_frames } else { 0 }; let chunk = data.chunk_mut(); *chunk.offset_mut() = 0; *chunk.stride_mut() = stride as _; *chunk.size_mut() = (stride * n_frames) as _; } }) .register()?; let mut audio_info = spa::param::audio::AudioInfoRaw::new(); audio_info.set_format(spa::param::audio::AudioFormat::S16LE); audio_info.set_rate(DEFAULT_RATE); audio_info.set_channels(DEFAULT_CHANNELS); let values: Vec = pw::spa::pod::serialize::PodSerializer::serialize( std::io::Cursor::new(Vec::new()), &pw::spa::pod::Value::Object(pw::spa::pod::Object { type_: spa_sys::SPA_TYPE_OBJECT_Format, id: spa_sys::SPA_PARAM_EnumFormat, properties: audio_info.into(), }), ) .unwrap() .0 .into_inner(); let mut params = [Pod::from_bytes(&values).unwrap()]; stream.connect( spa::utils::Direction::Output, None, pw::stream::StreamFlags::AUTOCONNECT | pw::stream::StreamFlags::MAP_BUFFERS | pw::stream::StreamFlags::RT_PROCESS, &mut params, )?; mainloop.run(); Ok(()) } pipewire-0.8.0/src/buffer.rs000064400000000000000000000025201046102023000140620ustar 00000000000000use super::stream::StreamRef; use spa::buffer::Data; use std::convert::TryFrom; use std::ptr::NonNull; pub struct Buffer<'s> { buf: NonNull, /// In Pipewire, buffers are owned by the stream that generated them. /// This reference ensures that this rule is respected. stream: &'s StreamRef, } impl Buffer<'_> { pub(crate) unsafe fn from_raw( buf: *mut pw_sys::pw_buffer, stream: &StreamRef, ) -> Option> { NonNull::new(buf).map(|buf| Buffer { buf, stream }) } pub fn datas_mut(&mut self) -> &mut [Data] { let buffer: *mut spa_sys::spa_buffer = unsafe { self.buf.as_ref().buffer }; let slice_of_data = if !buffer.is_null() && unsafe { (*buffer).n_datas > 0 && !(*buffer).datas.is_null() } { unsafe { let datas = (*buffer).datas as *mut Data; std::slice::from_raw_parts_mut(datas, usize::try_from((*buffer).n_datas).unwrap()) } } else { &mut [] }; slice_of_data } #[cfg(feature = "v0_3_49")] pub fn requested(&self) -> u64 { unsafe { self.buf.as_ref().requested } } } impl Drop for Buffer<'_> { fn drop(&mut self) { unsafe { self.stream.queue_raw_buffer(self.buf.as_ptr()); } } } pipewire-0.8.0/src/channel.rs000064400000000000000000000156471046102023000142370ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT //! This module provides a channel for communicating with a thread running a pipewire loop. //! The channel is split into two types, [`Sender`] and [`Receiver`]. //! //! It can be created using the [`channel`] function. //! The returned receiver can then be attached to a pipewire loop, and the sender can be used to send messages to //! the receiver. //! //! # Examples //! This program will print "Hello" three times before terminating, using two threads: // ignored because https://gitlab.freedesktop.org/pipewire/pipewire-rs/-/issues/19 //! ```no_run //! use std::{time::Duration, sync::mpsc, thread}; //! use pipewire::main_loop::MainLoop; //! //! // Our message to the pipewire loop, this tells it to terminate. //! struct Terminate; //! //! fn main() { //! let (main_sender, main_receiver) = mpsc::channel(); //! let (pw_sender, pw_receiver) = pipewire::channel::channel(); //! //! let pw_thread = thread::spawn(move || pw_thread(main_sender, pw_receiver)); //! //! // Count up to three "Hello"'s. //! let mut n = 0; //! while n < 3 { //! println!("{}", main_receiver.recv().unwrap()); //! n += 1; //! } //! //! // We printed hello 3 times, terminate the pipewire thread now. //! pw_sender.send(Terminate); //! //! pw_thread.join(); //! } //! //! // This is the code that will run in the pipewire thread. //! fn pw_thread( //! main_sender: mpsc::Sender, //! pw_receiver: pipewire::channel::Receiver //! ) { //! let mainloop = MainLoop::new(None).expect("Failed to create main loop"); //! //! // When we receive a `Terminate` message, quit the main loop. //! let _receiver = pw_receiver.attach(mainloop.loop_(), { //! let mainloop = mainloop.clone(); //! move |_| mainloop.quit() //! }); //! //! // Every 100ms, send `"Hello"` to the main thread. //! let timer = mainloop.loop_().add_timer(move |_| { //! main_sender.send(String::from("Hello")); //! }); //! timer.update_timer( //! Some(Duration::from_millis(1)), // Send the first message immediately //! Some(Duration::from_millis(100)) //! ); //! //! mainloop.run(); //! } //! ``` use std::{ collections::VecDeque, os::unix::prelude::*, sync::{Arc, Mutex}, }; use crate::loop_::{IoSource, LoopRef}; use spa::support::system::IoFlags; /// A receiver that has not been attached to a loop. /// /// Use its [`attach`](`Self::attach`) function to receive messages by attaching it to a loop. pub struct Receiver { channel: Arc>>, } impl Receiver { /// Attach the receiver to a loop with a callback. /// /// This will make the loop call the callback with any messages that get sent to the receiver. #[must_use] pub fn attach(self, loop_: &LoopRef, callback: F) -> AttachedReceiver where F: Fn(T) + 'static, { let channel = self.channel.clone(); let readfd = channel.lock().expect("Channel mutex lock poisoned").readfd; // Attach the pipe as an IO source to the loop. // Whenever the pipe is written to, call the users callback with each message in the queue. let iosource = loop_.add_io(readfd, IoFlags::IN, move |_| { let mut channel = channel.lock().expect("Channel mutex lock poisoned"); // Read from the pipe to make it block until written to again. let _ = nix::unistd::read(channel.readfd, &mut [0]); channel.queue.drain(..).for_each(&callback); }); AttachedReceiver { _source: iosource, receiver: self, } } } /// A [`Receiver`] that has been attached to a loop. /// /// Dropping this will cause it to be detached from the loop, so no more messages will be received. pub struct AttachedReceiver<'l, T> where T: 'static, { _source: IoSource<'l, RawFd>, receiver: Receiver, } impl<'l, T> AttachedReceiver<'l, T> where T: 'static, { /// Detach the receiver from the loop. /// /// No more messages will be received until you attach it to a loop again. #[must_use] pub fn deattach(self) -> Receiver { self.receiver } } /// A `Sender` can be used to send messages to its associated [`Receiver`]. /// /// It can be freely cloned, so you can send messages from multiple places. pub struct Sender { channel: Arc>>, } impl Clone for Sender { fn clone(&self) -> Self { Self { channel: self.channel.clone(), } } } impl Sender { /// Send a message to the associated receiver. /// /// On any errors, this returns the message back to the caller. pub fn send(&self, t: T) -> Result<(), T> { // Lock the channel. let mut channel = match self.channel.lock() { Ok(chan) => chan, Err(_) => return Err(t), }; // If no messages are waiting already, signal the receiver to read some. // Because the channel mutex is locked, it is alright to do this before pushing the message. if channel.queue.is_empty() { match nix::unistd::write(channel.writefd, &[1u8]) { Ok(_) => (), Err(_) => return Err(t), } } // Push the new message into the queue. channel.queue.push_back(t); Ok(()) } } /// Shared state between the [`Sender`]s and the [`Receiver`]. struct Channel { /// A pipe used to signal the loop the receiver is attached to that messages are waiting. readfd: RawFd, writefd: RawFd, /// Queue of any messages waiting to be received. queue: VecDeque, } impl Drop for Channel { fn drop(&mut self) { // We do not error check here, because the pipe does not contain any data that might be lost, // and because there is no way to handle an error in a `Drop` implementation anyways. let _ = nix::unistd::close(self.readfd); let _ = nix::unistd::close(self.writefd); } } /// Create a Sender-Receiver pair, where the sender can be used to send messages to the receiver. /// /// This functions similar to [`std::sync::mpsc`], but with a receiver that can be attached to any /// [`LoopRef`](`crate::loop_::LoopRef`) to have the loop invoke a callback with any new messages. /// /// This can be used for inter-thread communication without shared state and where [`std::sync::mpsc`] can not be used /// because the receiving thread is running the pipewire loop. pub fn channel() -> (Sender, Receiver) where T: 'static, { let fds = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC).unwrap(); let channel: Arc>> = Arc::new(Mutex::new(Channel { readfd: fds.0, writefd: fds.1, queue: VecDeque::new(), })); ( Sender { channel: channel.clone(), }, Receiver { channel }, ) } pipewire-0.8.0/src/client.rs000064400000000000000000000201021046102023000140630ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use bitflags::bitflags; use libc::c_void; use std::ops::Deref; use std::pin::Pin; use std::{ffi::CString, ptr}; use std::{fmt, mem}; use crate::{ permissions::Permission, proxy::{Listener, Proxy, ProxyT}, types::ObjectType, }; use spa::spa_interface_call_method; #[derive(Debug)] pub struct Client { proxy: Proxy, } impl ProxyT for Client { fn type_() -> ObjectType { ObjectType::Client } fn upcast(self) -> Proxy { self.proxy } fn upcast_ref(&self) -> &Proxy { &self.proxy } unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self where Self: Sized, { Self { proxy } } } impl Client { // TODO: add non-local version when we'll bind pw_thread_loop_start() #[must_use] pub fn add_listener_local(&self) -> ClientListenerLocalBuilder { ClientListenerLocalBuilder { client: self, cbs: ListenerLocalCallbacks::default(), } } pub fn error(&self, id: u32, res: i32, message: &str) { let message = CString::new(message).expect("Null byte in message parameter"); unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_client_methods, error, id, res, message.as_ptr() as *const _ ); }; } pub fn update_properties(&self, properties: &spa::utils::dict::DictRef) { unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_client_methods, update_properties, properties.as_raw_ptr() ); } } pub fn get_permissions(&self, index: u32, num: u32) { unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_client_methods, get_permissions, index, num ); } } pub fn update_permissions(&self, permissions: &[Permission]) { unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_client_methods, update_permissions, permissions.len() as u32, permissions.as_ptr().cast() ); } } } #[derive(Default)] struct ListenerLocalCallbacks { #[allow(clippy::type_complexity)] info: Option>, #[allow(clippy::type_complexity)] permissions: Option>, } pub struct ClientListenerLocalBuilder<'a> { client: &'a Client, cbs: ListenerLocalCallbacks, } #[repr(transparent)] pub struct ClientInfoRef(pw_sys::pw_client_info); impl ClientInfoRef { pub fn as_raw(&self) -> &pw_sys::pw_client_info { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_client_info { std::ptr::addr_of!(self.0).cast_mut() } pub fn id(&self) -> u32 { self.0.id } pub fn change_mask(&self) -> ClientChangeMask { ClientChangeMask::from_bits(self.0.change_mask).expect("invalid change_mask") } pub fn props(&self) -> Option<&spa::utils::dict::DictRef> { let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast(); ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() }) } } impl fmt::Debug for ClientInfoRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ClientInfoRef") .field("id", &self.id()) .field("change-mask", &self.change_mask()) .field("props", &self.props()) .finish() } } pub struct ClientInfo { ptr: ptr::NonNull, } impl ClientInfo { pub fn new(ptr: ptr::NonNull) -> Self { Self { ptr } } pub fn from_raw(raw: *mut pw_sys::pw_client_info) -> Self { Self { ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"), } } pub fn into_raw(self) -> *mut pw_sys::pw_client_info { std::mem::ManuallyDrop::new(self).ptr.as_ptr() } } impl Drop for ClientInfo { fn drop(&mut self) { unsafe { pw_sys::pw_client_info_free(self.ptr.as_ptr()) } } } impl std::ops::Deref for ClientInfo { type Target = ClientInfoRef; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for ClientInfo { fn as_ref(&self) -> &ClientInfoRef { self.deref() } } bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct ClientChangeMask: u64 { const PROPS = pw_sys::PW_CLIENT_CHANGE_MASK_PROPS as u64; } } impl fmt::Debug for ClientInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ClientInfo") .field("id", &self.id()) .field("change-mask", &self.change_mask()) .field("props", &self.props()) .finish() } } pub struct ClientListener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener for ClientListener {} impl Drop for ClientListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } impl<'a> ClientListenerLocalBuilder<'a> { #[must_use] pub fn info(mut self, info: F) -> Self where F: Fn(&ClientInfoRef) + 'static, { self.cbs.info = Some(Box::new(info)); self } pub fn permissions(mut self, permissions: F) -> Self where F: Fn(u32, &[Permission]) + 'static, { self.cbs.permissions = Some(Box::new(permissions)); self } #[must_use] pub fn register(self) -> ClientListener { unsafe extern "C" fn client_events_info( data: *mut c_void, info: *const pw_sys::pw_client_info, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let info = ptr::NonNull::new(info as *mut pw_sys::pw_client_info).expect("info is NULL"); let info = info.cast::().as_ref(); callbacks.info.as_ref().unwrap()(info); } unsafe extern "C" fn client_events_permissions( data: *mut c_void, index: u32, n_permissions: u32, permissions: *const pw_sys::pw_permission, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let permissions = std::slice::from_raw_parts(permissions.cast(), n_permissions as usize); callbacks.permissions.as_ref().unwrap()(index, permissions); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_CLIENT_EVENTS; if self.cbs.info.is_some() { e.info = Some(client_events_info); } if self.cbs.permissions.is_some() { e.permissions = Some(client_events_permissions); } e }; let (listener, data) = unsafe { let client = &self.client.proxy.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa_interface_call_method!( client, pw_sys::pw_client_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; ClientListener { events: e, listener, data, } } } pipewire-0.8.0/src/constants.rs000064400000000000000000000003131046102023000146230ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT //! Pipewire constants. /// Invalid ID that matches any object when used for permissions. pub const ID_ANY: u32 = 0xffffffff; pipewire-0.8.0/src/context.rs000064400000000000000000000100071046102023000142740ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ fmt, ops::Deref, os::unix::prelude::{IntoRawFd, OwnedFd}, ptr, rc::Rc, }; use crate::core::Core; use crate::error::Error; use crate::loop_::{IsLoopRc, LoopRef}; use crate::properties::{Properties, PropertiesRef}; #[repr(transparent)] pub struct ContextRef(pw_sys::pw_context); impl ContextRef { pub fn as_raw(&self) -> &pw_sys::pw_context { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_context { std::ptr::addr_of!(self.0).cast_mut() } pub fn properties(&self) -> &PropertiesRef { unsafe { let props = pw_sys::pw_context_get_properties(self.as_raw_ptr()); let props = ptr::NonNull::new(props.cast_mut()).expect("context properties is NULL"); props.cast().as_ref() } } pub fn update_properties(&self, properties: &spa::utils::dict::DictRef) { unsafe { pw_sys::pw_context_update_properties(self.as_raw_ptr(), properties.as_raw_ptr()); } } } #[derive(Clone, Debug)] pub struct Context { inner: Rc, } pub struct ContextInner { ptr: ptr::NonNull, /// Store the loop here, so that the loop is not dropped before the context, which may lead to /// undefined behaviour. _loop: Box>, } impl fmt::Debug for ContextInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ContextInner") .field("ptr", &self.ptr) .finish() } } impl Context { fn new_internal(loop_: &T, properties: Option) -> Result { let loop_: Box> = Box::new(loop_.clone()); let props = properties.map_or(ptr::null(), |props| props.into_raw()) as *mut _; let context = unsafe { pw_sys::pw_context_new((*loop_).as_ref().as_raw() as *const _ as *mut _, props, 0) }; let context = ptr::NonNull::new(context).ok_or(Error::CreationFailed)?; Ok(Context { inner: Rc::new(ContextInner { ptr: context, _loop: loop_, }), }) } pub fn new(loop_: &T) -> Result { Self::new_internal(loop_, None) } pub fn with_properties(loop_: &T, properties: Properties) -> Result { Self::new_internal(loop_, Some(properties)) } pub fn connect(&self, properties: Option) -> Result { let properties = properties.map_or(ptr::null_mut(), |p| p.into_raw()); unsafe { let core = pw_sys::pw_context_connect(self.as_raw_ptr(), properties, 0); let ptr = ptr::NonNull::new(core).ok_or(Error::CreationFailed)?; Ok(Core::from_ptr(ptr, self.clone())) } } pub fn connect_fd(&self, fd: OwnedFd, properties: Option) -> Result { let properties = properties.map_or(ptr::null_mut(), |p| p.into_raw()); unsafe { let raw_fd = fd.into_raw_fd(); let core = pw_sys::pw_context_connect_fd(self.as_raw_ptr(), raw_fd, properties, 0); let ptr = ptr::NonNull::new(core).ok_or(Error::CreationFailed)?; Ok(Core::from_ptr(ptr, self.clone())) } } } impl std::convert::AsRef for Context { fn as_ref(&self) -> &ContextRef { self.deref() } } impl std::ops::Deref for Context { type Target = ContextInner; fn deref(&self) -> &Self::Target { &self.inner } } impl std::convert::AsRef for ContextInner { fn as_ref(&self) -> &ContextRef { self.deref() } } impl std::ops::Deref for ContextInner { type Target = ContextRef; fn deref(&self) -> &Self::Target { unsafe { &*(self.ptr.as_ptr() as *mut ContextRef) } } } impl Drop for ContextInner { fn drop(&mut self) { unsafe { pw_sys::pw_context_destroy(self.ptr.as_ptr()) } } } pipewire-0.8.0/src/core.rs000064400000000000000000000303411046102023000135430ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use bitflags::bitflags; use libc::{c_char, c_void}; use std::{ ffi::{CStr, CString}, rc::Rc, }; use std::{fmt, mem, ptr}; use std::{ops::Deref, pin::Pin}; use crate::{ proxy::{Proxy, ProxyT}, registry::Registry, Error, }; use spa::{ spa_interface_call_method, utils::result::{AsyncSeq, SpaResult}, }; pub const PW_ID_CORE: u32 = pw_sys::PW_ID_CORE; #[repr(transparent)] pub struct CoreRef(pw_sys::pw_core); impl CoreRef { pub fn as_raw(&self) -> &pw_sys::pw_core { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_core { std::ptr::addr_of!(self.0).cast_mut() } // TODO: add non-local version when we'll bind pw_thread_loop_start() #[must_use] pub fn add_listener_local(&self) -> ListenerLocalBuilder { ListenerLocalBuilder { core: self, cbs: ListenerLocalCallbacks::default(), } } pub fn get_registry(&self) -> Result { let registry = unsafe { spa_interface_call_method!( self.as_raw_ptr(), pw_sys::pw_core_methods, get_registry, pw_sys::PW_VERSION_REGISTRY, 0 ) }; let registry = ptr::NonNull::new(registry).ok_or(Error::CreationFailed)?; Ok(Registry::new(registry)) } pub fn sync(&self, seq: i32) -> Result { let res = unsafe { spa_interface_call_method!( self.as_raw_ptr(), pw_sys::pw_core_methods, sync, PW_ID_CORE, seq ) }; let res = SpaResult::from_c(res).into_async_result()?; Ok(res) } /// Create a new object on the PipeWire server from a factory. /// /// You will need specify what type you are expecting to be constructed by either using type inference or the /// turbofish syntax. /// /// # Parameters /// - `factory_name` the name of the factory to use /// - `properties` extra properties that the new object will have /// /// # Panics /// If `factory_name` contains a null byte. /// /// # Returns /// One of: /// - `Ok(P)` on success, where `P` is the newly created object /// - `Err(Error::CreationFailed)` if the object could not be created /// - `Err(Error::WrongProxyType)` if the created type does not match the type `P` that the user is trying to create /// /// # Examples /// Creating a new link: // Doctest ignored, as the factory name is hardcoded, but may be different on different systems. /// ```ignore /// use pipewire as pw; /// /// pw::init(); /// /// let mainloop = pw::MainLoop::new().expect("Failed to create Pipewire Mainloop"); /// let context = pw::Context::new(&mainloop).expect("Failed to create Pipewire Context"); /// let core = context /// .connect(None) /// .expect("Failed to connect to Pipewire Core"); /// /// // This call uses turbofish syntax to specify that we want a link. /// let link = core.create_object::( /// // The actual name for a link factory might be different for your system, /// // you should probably obtain a factory from the registry. /// "link-factory", /// &pw::properties! { /// "link.output.port" => "1", /// "link.input.port" => "2", /// "link.output.node" => "3", /// "link.input.node" => "4" /// }, /// ) /// .expect("Failed to create object"); /// ``` /// /// See `pipewire/examples/create-delete-remote-objects.rs` in the crates repository for a more detailed example. pub fn create_object( &self, factory_name: &str, properties: &impl AsRef, ) -> Result { let type_ = P::type_(); let factory_name = CString::new(factory_name).expect("Null byte in factory_name parameter"); let type_str = CString::new(type_.to_string()) .expect("Null byte in string representation of type_ parameter"); let res = unsafe { spa_interface_call_method!( self.as_raw_ptr(), pw_sys::pw_core_methods, create_object, factory_name.as_ptr(), type_str.as_ptr(), type_.client_version(), properties.as_ref().as_raw_ptr(), 0 ) }; let ptr = ptr::NonNull::new(res.cast()).ok_or(Error::CreationFailed)?; Proxy::new(ptr).downcast().map_err(|(_, e)| e) } /// Destroy the object on the remote server represented by the provided proxy. /// /// The proxy will be destroyed alongside the server side resource, as it is no longer needed. pub fn destroy_object(&self, proxy: P) -> Result { let res = unsafe { spa_interface_call_method!( self.as_raw_ptr(), pw_sys::pw_core_methods, destroy, proxy.upcast_ref().as_ptr() as *mut c_void ) }; let res = SpaResult::from_c(res).into_async_result()?; Ok(res) } } #[derive(Debug, Clone)] pub struct Core { inner: Rc, } impl Core { pub(crate) fn from_ptr( ptr: ptr::NonNull, _context: crate::context::Context, ) -> Self { let inner = CoreInner::from_ptr(ptr, _context); Self { inner: Rc::new(inner), } } } impl Deref for Core { type Target = CoreRef; fn deref(&self) -> &Self::Target { unsafe { self.inner.deref().ptr.cast::().as_ref() } } } impl AsRef for Core { fn as_ref(&self) -> &CoreRef { self.deref() } } #[derive(Debug)] struct CoreInner { ptr: ptr::NonNull, _context: crate::context::Context, } impl CoreInner { fn from_ptr(ptr: ptr::NonNull, _context: crate::context::Context) -> Self { Self { ptr, _context } } } #[derive(Default)] struct ListenerLocalCallbacks { #[allow(clippy::type_complexity)] info: Option>, done: Option>, #[allow(clippy::type_complexity)] error: Option>, // TODO: return a proper Error enum? // TODO: ping, remove_id, bound_id, add_mem, remove_mem } pub struct ListenerLocalBuilder<'a> { core: &'a CoreRef, cbs: ListenerLocalCallbacks, } pub struct Listener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener { pub fn unregister(self) { // Consuming the listener will call drop() } } impl Drop for Listener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } impl<'a> ListenerLocalBuilder<'a> { #[must_use] pub fn info(mut self, info: F) -> Self where F: Fn(&Info) + 'static, { self.cbs.info = Some(Box::new(info)); self } #[must_use] pub fn done(mut self, done: F) -> Self where F: Fn(u32, AsyncSeq) + 'static, { self.cbs.done = Some(Box::new(done)); self } #[must_use] pub fn error(mut self, error: F) -> Self where F: Fn(u32, i32, i32, &str) + 'static, { self.cbs.error = Some(Box::new(error)); self } #[must_use] pub fn register(self) -> Listener { unsafe extern "C" fn core_events_info( data: *mut c_void, info: *const pw_sys::pw_core_info, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let info = Info::new(ptr::NonNull::new(info as *mut _).expect("info is NULL")); callbacks.info.as_ref().unwrap()(&info); } unsafe extern "C" fn core_events_done(data: *mut c_void, id: u32, seq: i32) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); callbacks.done.as_ref().unwrap()(id, AsyncSeq::from_raw(seq)); } unsafe extern "C" fn core_events_error( data: *mut c_void, id: u32, seq: i32, res: i32, message: *const c_char, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let message = CStr::from_ptr(message).to_str().unwrap(); callbacks.error.as_ref().unwrap()(id, seq, res, message); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_CORE_EVENTS; if self.cbs.info.is_some() { e.info = Some(core_events_info); } if self.cbs.done.is_some() { e.done = Some(core_events_done); } if self.cbs.error.is_some() { e.error = Some(core_events_error); } e }; let (listener, data) = unsafe { let ptr = self.core.as_raw_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); // Have to cast from pw-sys namespaced type to the equivalent spa-sys type // as bindgen does not allow us to generate bindings dependings of another // sys crate, see https://github.com/rust-lang/rust-bindgen/issues/1929 let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa_interface_call_method!( ptr, pw_sys::pw_core_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; Listener { events: e, listener, data, } } } pub struct Info { ptr: ptr::NonNull, } impl Info { fn new(info: ptr::NonNull) -> Self { Self { ptr: info } } pub fn id(&self) -> u32 { unsafe { self.ptr.as_ref().id } } pub fn cookie(&self) -> u32 { unsafe { self.ptr.as_ref().cookie } } pub fn user_name(&self) -> &str { unsafe { CStr::from_ptr(self.ptr.as_ref().user_name) .to_str() .unwrap() } } pub fn host_name(&self) -> &str { unsafe { CStr::from_ptr(self.ptr.as_ref().host_name) .to_str() .unwrap() } } pub fn version(&self) -> &str { unsafe { CStr::from_ptr(self.ptr.as_ref().version).to_str().unwrap() } } pub fn name(&self) -> &str { unsafe { CStr::from_ptr(self.ptr.as_ref().name).to_str().unwrap() } } pub fn change_mask(&self) -> ChangeMask { let mask = unsafe { self.ptr.as_ref().change_mask }; ChangeMask::from_bits_retain(mask) } pub fn props(&self) -> Option<&spa::utils::dict::DictRef> { let props_ptr: *mut spa::utils::dict::DictRef = unsafe { self.ptr.as_ref().props.cast() }; ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() }) } } impl fmt::Debug for Info { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CoreInfo") .field("id", &self.id()) .field("cookie", &self.cookie()) .field("user-name", &self.user_name()) .field("host-name", &self.host_name()) .field("version", &self.version()) .field("name", &self.name()) .field("change-mask", &self.change_mask()) .field("props", &self.props()) .finish() } } bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct ChangeMask: u64 { const PROPS = pw_sys::PW_CORE_CHANGE_MASK_PROPS as u64; } } pipewire-0.8.0/src/device.rs000064400000000000000000000222361046102023000140560ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use bitflags::bitflags; use libc::c_void; use std::ops::Deref; use std::{fmt, mem}; use std::{pin::Pin, ptr}; use crate::{ proxy::{Listener, Proxy, ProxyT}, types::ObjectType, }; use spa::{pod::Pod, spa_interface_call_method}; #[derive(Debug)] pub struct Device { proxy: Proxy, } impl Device { // TODO: add non-local version when we'll bind pw_thread_loop_start() #[must_use] pub fn add_listener_local(&self) -> DeviceListenerLocalBuilder { DeviceListenerLocalBuilder { device: self, cbs: ListenerLocalCallbacks::default(), } } /// Subscribe to parameter changes /// /// Automatically emit `param` events for the given ids when they are changed // FIXME: Return result? pub fn subscribe_params(&self, ids: &[spa::param::ParamType]) { unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_device_methods, subscribe_params, ids.as_ptr() as *mut _, ids.len().try_into().unwrap() ); } } /// Enumerate device parameters /// /// Start enumeration of device parameters. For each param, a /// param event will be emitted. /// /// # Parameters /// `seq`: a sequence number to place in the reply \ /// `id`: the parameter id to enum, or [`None`] to allow any id \ /// `start`: the start index or 0 for the first param \ /// `num`: the maximum number of params to retrieve ([`u32::MAX`] may be used to retrieve all params) // FIXME: Add filter parameter // FIXME: Return result? pub fn enum_params(&self, seq: i32, id: Option, start: u32, num: u32) { let id = id.map(|id| id.as_raw()).unwrap_or(crate::constants::ID_ANY); unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_device_methods, enum_params, seq, id, start, num, std::ptr::null() ); } } pub fn set_param(&self, id: spa::param::ParamType, flags: u32, param: &Pod) { unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_device_methods, set_param, id.as_raw(), flags, param.as_raw_ptr() ); } } } impl ProxyT for Device { fn type_() -> ObjectType { ObjectType::Device } fn upcast(self) -> Proxy { self.proxy } fn upcast_ref(&self) -> &Proxy { &self.proxy } unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self where Self: Sized, { Self { proxy } } } #[derive(Default)] struct ListenerLocalCallbacks { #[allow(clippy::type_complexity)] info: Option>, #[allow(clippy::type_complexity)] param: Option)>>, } pub struct DeviceListenerLocalBuilder<'a> { device: &'a Device, cbs: ListenerLocalCallbacks, } #[repr(transparent)] pub struct DeviceInfoRef(pw_sys::pw_device_info); impl DeviceInfoRef { pub fn as_raw(&self) -> &pw_sys::pw_device_info { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_device_info { std::ptr::addr_of!(self.0).cast_mut() } pub fn id(&self) -> u32 { self.0.id } pub fn change_mask(&self) -> DeviceChangeMask { DeviceChangeMask::from_bits(self.0.change_mask).expect("invalid change_mask") } pub fn props(&self) -> Option<&spa::utils::dict::DictRef> { let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast(); ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() }) } /// Get the param infos for the device. pub fn params(&self) -> &[spa::param::ParamInfo] { let params = self.0.params; if params.is_null() { &[] } else { unsafe { std::slice::from_raw_parts(params as *const _, self.0.n_params.try_into().unwrap()) } } } } impl fmt::Debug for DeviceInfoRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DeviceInfoRef") .field("id", &self.id()) .field("change-mask", &self.change_mask()) .field("props", &self.props()) .field("params", &self.params()) .finish() } } pub struct DeviceInfo { ptr: ptr::NonNull, } impl DeviceInfo { pub fn new(ptr: ptr::NonNull) -> Self { Self { ptr } } pub fn from_raw(raw: *mut pw_sys::pw_device_info) -> Self { Self { ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"), } } pub fn into_raw(self) -> *mut pw_sys::pw_device_info { std::mem::ManuallyDrop::new(self).ptr.as_ptr() } } impl Drop for DeviceInfo { fn drop(&mut self) { unsafe { pw_sys::pw_device_info_free(self.ptr.as_ptr()) } } } impl std::ops::Deref for DeviceInfo { type Target = DeviceInfoRef; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for DeviceInfo { fn as_ref(&self) -> &DeviceInfoRef { self.deref() } } bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct DeviceChangeMask: u64 { const PROPS = pw_sys::PW_DEVICE_CHANGE_MASK_PROPS as u64; const PARAMS = pw_sys::PW_DEVICE_CHANGE_MASK_PARAMS as u64; } } impl fmt::Debug for DeviceInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DeviceInfo") .field("id", &self.id()) .field("change-mask", &self.change_mask()) .field("props", &self.props()) .field("params", &self.params()) .finish() } } pub struct DeviceListener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener for DeviceListener {} impl Drop for DeviceListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } impl<'a> DeviceListenerLocalBuilder<'a> { #[must_use] pub fn info(mut self, info: F) -> Self where F: Fn(&DeviceInfoRef) + 'static, { self.cbs.info = Some(Box::new(info)); self } #[must_use] pub fn param(mut self, param: F) -> Self where F: Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>) + 'static, { self.cbs.param = Some(Box::new(param)); self } #[must_use] pub fn register(self) -> DeviceListener { unsafe extern "C" fn device_events_info( data: *mut c_void, info: *const pw_sys::pw_device_info, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let info = ptr::NonNull::new(info as *mut pw_sys::pw_device_info).expect("info is NULL"); let info = info.cast::().as_ref(); callbacks.info.as_ref().unwrap()(info); } unsafe extern "C" fn device_events_param( data: *mut c_void, seq: i32, id: u32, index: u32, next: u32, param: *const spa_sys::spa_pod, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let id = spa::param::ParamType::from_raw(id); let param = if !param.is_null() { unsafe { Some(Pod::from_raw(param)) } } else { None }; callbacks.param.as_ref().unwrap()(seq, id, index, next, param); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_DEVICE_EVENTS; if self.cbs.info.is_some() { e.info = Some(device_events_info); } if self.cbs.param.is_some() { e.param = Some(device_events_param); } e }; let (listener, data) = unsafe { let device = &self.device.proxy.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa_interface_call_method!( device, pw_sys::pw_device_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; DeviceListener { events: e, listener, data, } } } pipewire-0.8.0/src/error.rs000064400000000000000000000005511046102023000137440ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use thiserror::Error; #[derive(Error, Debug)] pub enum Error { #[error("Creation failed")] CreationFailed, #[error("No memory")] NoMemory, #[error("Wrong proxy type")] WrongProxyType, #[error(transparent)] SpaError(#[from] spa::utils::result::Error), } pipewire-0.8.0/src/factory.rs000064400000000000000000000142721046102023000142670ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use bitflags::bitflags; use libc::c_void; use std::ops::Deref; use std::pin::Pin; use std::{ffi::CStr, ptr}; use std::{fmt, mem}; use crate::{ proxy::{Listener, Proxy, ProxyT}, types::ObjectType, }; use spa::spa_interface_call_method; #[derive(Debug)] pub struct Factory { proxy: Proxy, } impl ProxyT for Factory { fn type_() -> ObjectType { ObjectType::Factory } fn upcast(self) -> Proxy { self.proxy } fn upcast_ref(&self) -> &Proxy { &self.proxy } unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self where Self: Sized, { Self { proxy } } } impl Factory { // TODO: add non-local version when we'll bind pw_thread_loop_start() #[must_use] pub fn add_listener_local(&self) -> FactoryListenerLocalBuilder { FactoryListenerLocalBuilder { factory: self, cbs: ListenerLocalCallbacks::default(), } } } #[derive(Default)] struct ListenerLocalCallbacks { #[allow(clippy::type_complexity)] info: Option>, } pub struct FactoryListenerLocalBuilder<'a> { factory: &'a Factory, cbs: ListenerLocalCallbacks, } #[repr(transparent)] pub struct FactoryInfoRef(pw_sys::pw_factory_info); impl FactoryInfoRef { pub fn as_raw(&self) -> &pw_sys::pw_factory_info { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_factory_info { std::ptr::addr_of!(self.0).cast_mut() } pub fn id(&self) -> u32 { self.0.id } pub fn type_(&self) -> ObjectType { ObjectType::from_str(unsafe { CStr::from_ptr(self.0.type_).to_str().unwrap() }) } pub fn version(&self) -> u32 { self.0.version } pub fn change_mask(&self) -> FactoryChangeMask { FactoryChangeMask::from_bits(self.0.change_mask).expect("invalid change_mask") } pub fn props(&self) -> Option<&spa::utils::dict::DictRef> { let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast(); ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() }) } } impl fmt::Debug for FactoryInfoRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FactoryInfoRef") .field("id", &self.id()) .field("type", &self.type_()) .field("version", &self.version()) .field("change_mask", &self.change_mask()) .field("props", &self.props()) .finish() } } pub struct FactoryInfo { ptr: ptr::NonNull, } impl FactoryInfo { pub fn new(ptr: ptr::NonNull) -> Self { Self { ptr } } pub fn from_raw(raw: *mut pw_sys::pw_factory_info) -> Self { Self { ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"), } } pub fn into_raw(self) -> *mut pw_sys::pw_factory_info { std::mem::ManuallyDrop::new(self).ptr.as_ptr() } } impl Drop for FactoryInfo { fn drop(&mut self) { unsafe { pw_sys::pw_factory_info_free(self.ptr.as_ptr()) } } } impl std::ops::Deref for FactoryInfo { type Target = FactoryInfoRef; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for FactoryInfo { fn as_ref(&self) -> &FactoryInfoRef { self.deref() } } bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct FactoryChangeMask: u64 { const PROPS = pw_sys::PW_FACTORY_CHANGE_MASK_PROPS as u64; } } impl fmt::Debug for FactoryInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FactoryInfo") .field("id", &self.id()) .field("type", &self.type_()) .field("version", &self.version()) .field("change_mask", &self.change_mask()) .field("props", &self.props()) .finish() } } pub struct FactoryListener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener for FactoryListener {} impl Drop for FactoryListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } impl<'a> FactoryListenerLocalBuilder<'a> { #[must_use] pub fn info(mut self, info: F) -> Self where F: Fn(&FactoryInfoRef) + 'static, { self.cbs.info = Some(Box::new(info)); self } #[must_use] pub fn register(self) -> FactoryListener { unsafe extern "C" fn factory_events_info( data: *mut c_void, info: *const pw_sys::pw_factory_info, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let info = ptr::NonNull::new(info as *mut pw_sys::pw_factory_info).expect("info is NULL"); let info = info.cast::().as_ref(); callbacks.info.as_ref().unwrap()(info); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_FACTORY_EVENTS; if self.cbs.info.is_some() { e.info = Some(factory_events_info); } e }; let (listener, data) = unsafe { let factory = &self.factory.proxy.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa_interface_call_method!( factory, pw_sys::pw_factory_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; FactoryListener { events: e, listener, data, } } } pipewire-0.8.0/src/keys.rs000064400000000000000000000441441046102023000135740ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT //! A collection of keys that are used to add extra information on objects. //! //! ``` //! use pipewire::properties::properties; //! //! let props = properties! { //! *pipewire::keys::REMOTE_NAME => "pipewire-0" //! }; //! ``` use std::ffi::CStr; use once_cell::sync::Lazy; // unfortunately we have to take two args as concat_idents! is in experimental macro_rules! key_constant { ($name:ident, $pw_symbol:ident, #[doc = $doc:expr]) => { #[doc = $doc] pub static $name: Lazy<&'static str> = Lazy::new(|| unsafe { CStr::from_bytes_with_nul_unchecked(pw_sys::$pw_symbol) .to_str() .unwrap() }); }; } key_constant!(PROTOCOL, PW_KEY_PROTOCOL, /// protocol used for connection ); key_constant!(ACCESS, PW_KEY_ACCESS, /// how the client access is controlled ); key_constant!(CLIENT_ACCESS, PW_KEY_CLIENT_ACCESS, /// how the client wants to be access controlled Must be obtained from trusted sources by the protocol and placed as read-only properties. ); key_constant!(SEC_PID, PW_KEY_SEC_PID, /// Client pid, set by protocol ); key_constant!(SEC_UID, PW_KEY_SEC_UID, /// Client uid, set by protocol ); key_constant!(SEC_GID, PW_KEY_SEC_GID, /// client gid, set by protocol ); key_constant!(SEC_LABEL, PW_KEY_SEC_LABEL, /// client security label, set by protocol ); key_constant!(LIBRARY_NAME_SYSTEM, PW_KEY_LIBRARY_NAME_SYSTEM, /// name of the system library to use ); key_constant!(LIBRARY_NAME_LOOP, PW_KEY_LIBRARY_NAME_LOOP, /// name of the loop library to use ); key_constant!(LIBRARY_NAME_DBUS, PW_KEY_LIBRARY_NAME_DBUS, /// name of the dbus library to use ); key_constant!(OBJECT_PATH, PW_KEY_OBJECT_PATH, /// unique path to construct the object ); key_constant!(OBJECT_ID, PW_KEY_OBJECT_ID, /// a global object id ); #[cfg(feature = "v0_3_41")] key_constant!(OBJECT_SERIAL, PW_KEY_OBJECT_SERIAL, /// a 64 bit object serial number. This is a number incremented for each object that is created. The lower 32 bits are guaranteed to never be SPA_ID_INVALID. ); key_constant!(OBJECT_LINGER, PW_KEY_OBJECT_LINGER, /// the object lives on even after the client that created it has been destroyed ); #[cfg(feature = "v0_3_32")] key_constant!(OBJECT_REGISTER, PW_KEY_OBJECT_REGISTER, /// If the object should be registered. ); key_constant!(CONFIG_PREFIX, PW_KEY_CONFIG_PREFIX, /// a config prefix directory ); key_constant!(CONFIG_NAME, PW_KEY_CONFIG_NAME, /// a config file name ); #[cfg(feature = "v0_3_57")] key_constant!(CONFIG_OVERRIDE_PREFIX, PW_KEY_CONFIG_OVERRIDE_PREFIX, /// a config override prefix directory ); #[cfg(feature = "v0_3_57")] key_constant!(CONFIG_OVERRIDE_NAME, PW_KEY_CONFIG_OVERRIDE_NAME, /// a config override file name ); key_constant!(CONTEXT_PROFILE_MODULES, PW_KEY_CONTEXT_PROFILE_MODULES, /// a context profile for modules, deprecated ); key_constant!(USER_NAME, PW_KEY_USER_NAME, /// The user name that runs pipewire ); key_constant!(HOST_NAME, PW_KEY_HOST_NAME, /// The host name of the machine ); key_constant!(CORE_NAME, PW_KEY_CORE_NAME, /// The name of the core. Default is `pipewire--`, overwritten by env(PIPEWIRE_CORE) ); key_constant!(CORE_VERSION, PW_KEY_CORE_VERSION, /// The version of the core. ); key_constant!(CORE_DAEMON, PW_KEY_CORE_DAEMON, /// If the core is listening for connections. ); key_constant!(CORE_ID, PW_KEY_CORE_ID, /// the core id ); key_constant!(CORE_MONITORS, PW_KEY_CORE_MONITORS, /// the apis monitored by core. ); key_constant!(CPU_MAX_ALIGN, PW_KEY_CPU_MAX_ALIGN, /// maximum alignment needed to support all CPU optimizations ); key_constant!(CPU_CORES, PW_KEY_CPU_CORES, /// number of cores ); key_constant!(PRIORITY_SESSION, PW_KEY_PRIORITY_SESSION, /// priority in session manager ); key_constant!(PRIORITY_DRIVER, PW_KEY_PRIORITY_DRIVER, /// priority to be a driver ); key_constant!(REMOTE_NAME, PW_KEY_REMOTE_NAME, /// The name of the remote to connect to, default pipewire-0, overwritten by env(PIPEWIRE_REMOTE) ); key_constant!(REMOTE_INTENTION, PW_KEY_REMOTE_INTENTION, /// The intention of the remote connection, "generic", "screencast" ); key_constant!(APP_NAME, PW_KEY_APP_NAME, /// application name. Ex: "Totem Music Player" ); key_constant!(APP_ID, PW_KEY_APP_ID, /// a textual id for identifying an application logically. Ex: "org.gnome.Totem" ); key_constant!(APP_VERSION, PW_KEY_APP_VERSION, /// application version. Ex: "1.2.0" ); key_constant!(APP_ICON, PW_KEY_APP_ICON, /// aa base64 blob with PNG image data ); key_constant!(APP_ICON_NAME, PW_KEY_APP_ICON_NAME, /// an XDG icon name for the application. Ex: "totem" ); key_constant!(APP_LANGUAGE, PW_KEY_APP_LANGUAGE, /// application language if applicable, in standard POSIX format. Ex: "en_GB" ); key_constant!(APP_PROCESS_ID, PW_KEY_APP_PROCESS_ID, /// process id (pid) ); key_constant!(APP_PROCESS_BINARY, PW_KEY_APP_PROCESS_BINARY, /// binary name ); key_constant!(APP_PROCESS_USER, PW_KEY_APP_PROCESS_USER, /// user name ); key_constant!(APP_PROCESS_HOST, PW_KEY_APP_PROCESS_HOST, /// host name ); key_constant!(APP_PROCESS_MACHINE_ID, PW_KEY_APP_PROCESS_MACHINE_ID, /// the D-Bus host id the application runs on ); key_constant!(APP_PROCESS_SESSION_ID, PW_KEY_APP_PROCESS_SESSION_ID, /// login session of the application, on Unix the value of $XDG_SESSION_ID. ); key_constant!(WINDOW_X11_DISPLAY, PW_KEY_WINDOW_X11_DISPLAY, /// the X11 display string. Ex. ":0.0" ); key_constant!(CLIENT_ID, PW_KEY_CLIENT_ID, /// a client id ); key_constant!(CLIENT_NAME, PW_KEY_CLIENT_NAME, /// the client name ); key_constant!(CLIENT_API, PW_KEY_CLIENT_API, /// the client api used to access PipeWire ); key_constant!(NODE_ID, PW_KEY_NODE_ID, /// node id ); key_constant!(NODE_NAME, PW_KEY_NODE_NAME, /// node name ); key_constant!(NODE_NICK, PW_KEY_NODE_NICK, /// short node name ); key_constant!(NODE_DESCRIPTION, PW_KEY_NODE_DESCRIPTION, /// localized human readable node one-line description. Ex. "Foobar USB Headset" ); key_constant!(NODE_PLUGGED, PW_KEY_NODE_PLUGGED, /// when the node was created. As a uint64 in nanoseconds. ); key_constant!(NODE_SESSION, PW_KEY_NODE_SESSION, /// the session id this node is part of ); key_constant!(NODE_GROUP, PW_KEY_NODE_GROUP, /// the group id this node is part of. Nodes in the same group are always scheduled with the same driver. ); key_constant!(NODE_EXCLUSIVE, PW_KEY_NODE_EXCLUSIVE, /// node wants exclusive access to resources ); key_constant!(NODE_AUTOCONNECT, PW_KEY_NODE_AUTOCONNECT, /// node wants to be automatically connected to a compatible node ); key_constant!(NODE_LATENCY, PW_KEY_NODE_LATENCY, /// the requested latency of the node as a fraction. Ex: 128/48000 ); key_constant!(NODE_MAX_LATENCY, PW_KEY_NODE_MAX_LATENCY, /// the maximum supported latency of the node as a fraction. Ex: 1024/48000 ); #[cfg(feature = "v0_3_33")] key_constant!(NODE_LOCK_QUANTUM, PW_KEY_NODE_LOCK_QUANTUM, /// don't change quantum when this node is active ); #[cfg(feature = "v0_3_45")] key_constant!(NODE_FORCE_QUANTUM, PW_KEY_NODE_FORCE_QUANTUM, /// force a quantum while the node is active ); #[cfg(feature = "v0_3_33")] key_constant!(NODE_RATE, PW_KEY_NODE_RATE, /// the requested rate of the graph as a fraction. Ex: 1/48000 ); #[cfg(feature = "v0_3_33")] key_constant!(NODE_LOCK_RATE, PW_KEY_NODE_LOCK_RATE, /// don't change rate when this node is active ); #[cfg(feature = "v0_3_45")] key_constant!(NODE_FORCE_RATE, PW_KEY_NODE_FORCE_RATE, /// force a rate while the node is active ); key_constant!(NODE_DONT_RECONNECT, PW_KEY_NODE_DONT_RECONNECT, /// don't reconnect this node. The node is initially linked to target.object or the default node. If the target is removed, the node is destroyed ); key_constant!(NODE_ALWAYS_PROCESS, PW_KEY_NODE_ALWAYS_PROCESS, /// process even when unlinked ); #[cfg(feature = "v0_3_33")] key_constant!(NODE_WANT_DRIVER, PW_KEY_NODE_WANT_DRIVER, /// the node wants to be grouped with a driver node in order to schedule the graph. ); key_constant!(NODE_PAUSE_ON_IDLE, PW_KEY_NODE_PAUSE_ON_IDLE, /// pause the node when idle ); #[cfg(feature = "v0_3_44")] key_constant!(NODE_SUSPEND_ON_IDLE, PW_KEY_NODE_SUSPEND_ON_IDLE, /// suspend the node when idle ); key_constant!(NODE_CACHE_PARAMS, PW_KEY_NODE_CACHE_PARAMS, /// cache the node params ); #[cfg(feature = "v0_3_44")] key_constant!(NODE_TRANSPORT_SYNC, PW_KEY_NODE_TRANSPORT_SYNC, /// the node handles transport sync ); key_constant!(NODE_DRIVER, PW_KEY_NODE_DRIVER, /// node can drive the graph ); key_constant!(NODE_STREAM, PW_KEY_NODE_STREAM, /// node is a stream, the server side should add a converter ); key_constant!(NODE_VIRTUAL, PW_KEY_NODE_VIRTUAL, /// the node is some sort of virtual object ); key_constant!(NODE_PASSIVE, PW_KEY_NODE_PASSIVE, /// indicate that a node wants passive links on output/input/all ports when the value is "out"/"in"/"true" respectively ); #[cfg(feature = "v0_3_32")] key_constant!(NODE_LINK_GROUP, PW_KEY_NODE_LINK_GROUP, /// the node is internally linked to nodes with the same link-group ); #[cfg(feature = "v0_3_39")] key_constant!(NODE_NETWORK, PW_KEY_NODE_NETWORK, /// the node is on a network ); #[cfg(feature = "v0_3_41")] key_constant!(NODE_TRIGGER, PW_KEY_NODE_TRIGGER, /// the node is not scheduled automatically based on the dependencies in the graph but it will be triggered explicitly. ); #[cfg(feature = "v0_3_64")] key_constant!(NODE_CHANNELNAMES, PW_KEY_NODE_CHANNELNAMES, /// names of node's channels (unrelated to positions) ); key_constant!(PORT_ID, PW_KEY_PORT_ID, /// port id ); key_constant!(PORT_NAME, PW_KEY_PORT_NAME, /// port name ); key_constant!(PORT_DIRECTION, PW_KEY_PORT_DIRECTION, /// the port direction, one of "in" or "out" or "control" and "notify" for control ports ); key_constant!(PORT_ALIAS, PW_KEY_PORT_ALIAS, /// port alias ); key_constant!(PORT_PHYSICAL, PW_KEY_PORT_PHYSICAL, /// if this is a physical port ); key_constant!(PORT_TERMINAL, PW_KEY_PORT_TERMINAL, /// if this port consumes the data ); key_constant!(PORT_CONTROL, PW_KEY_PORT_CONTROL, /// if this port is a control port ); key_constant!(PORT_MONITOR, PW_KEY_PORT_MONITOR, /// if this port is a monitor port ); key_constant!(PORT_CACHE_PARAMS, PW_KEY_PORT_CACHE_PARAMS, /// cache the node port params ); key_constant!(PORT_EXTRA, PW_KEY_PORT_EXTRA, /// api specific extra port info, API name should be prefixed. "jack:flags:56" ); key_constant!(LINK_ID, PW_KEY_LINK_ID, /// a link id ); key_constant!(LINK_INPUT_NODE, PW_KEY_LINK_INPUT_NODE, /// input node id of a link ); key_constant!(LINK_INPUT_PORT, PW_KEY_LINK_INPUT_PORT, /// input port id of a link ); key_constant!(LINK_OUTPUT_NODE, PW_KEY_LINK_OUTPUT_NODE, /// output node id of a link ); key_constant!(LINK_OUTPUT_PORT, PW_KEY_LINK_OUTPUT_PORT, /// output port id of a link ); key_constant!(LINK_PASSIVE, PW_KEY_LINK_PASSIVE, /// indicate that a link is passive and does not cause the graph to be runnable. ); key_constant!(LINK_FEEDBACK, PW_KEY_LINK_FEEDBACK, /// indicate that a link is a feedback link and the target will receive data in the next cycle ); key_constant!(DEVICE_ID, PW_KEY_DEVICE_ID, /// device id ); key_constant!(DEVICE_NAME, PW_KEY_DEVICE_NAME, /// device name ); key_constant!(DEVICE_PLUGGED, PW_KEY_DEVICE_PLUGGED, /// when the device was created. As a uint64 in nanoseconds. ); key_constant!(DEVICE_NICK, PW_KEY_DEVICE_NICK, /// a short device nickname ); key_constant!(DEVICE_STRING, PW_KEY_DEVICE_STRING, /// device string in the underlying layer's format. Ex. "surround51:0" ); key_constant!(DEVICE_API, PW_KEY_DEVICE_API, /// API this device is accessed with. Ex. "alsa", "v4l2" ); key_constant!(DEVICE_DESCRIPTION, PW_KEY_DEVICE_DESCRIPTION, /// localized human readable device one-line description. Ex. "Foobar USB Headset" ); key_constant!(DEVICE_BUS_PATH, PW_KEY_DEVICE_BUS_PATH, /// bus path to the device in the OS' format. Ex. "pci-0000:00:14.0-usb-0:3.2:1.0" ); key_constant!(DEVICE_SERIAL, PW_KEY_DEVICE_SERIAL, /// Serial number if applicable ); key_constant!(DEVICE_VENDOR_ID, PW_KEY_DEVICE_VENDOR_ID, /// vendor ID if applicable ); key_constant!(DEVICE_VENDOR_NAME, PW_KEY_DEVICE_VENDOR_NAME, /// vendor name if applicable ); key_constant!(DEVICE_PRODUCT_ID, PW_KEY_DEVICE_PRODUCT_ID, /// product ID if applicable ); key_constant!(DEVICE_PRODUCT_NAME, PW_KEY_DEVICE_PRODUCT_NAME, /// product name if applicable ); key_constant!(DEVICE_CLASS, PW_KEY_DEVICE_CLASS, /// device class ); key_constant!(DEVICE_FORM_FACTOR, PW_KEY_DEVICE_FORM_FACTOR, /// form factor if applicable. One of "internal", "speaker", "handset", "tv", "webcam", "microphone", "headset", "headphone", "hands-free", "car", "hifi", "computer", "portable" ); key_constant!(DEVICE_BUS, PW_KEY_DEVICE_BUS, /// bus of the device if applicable. One of "isa", "pci", "usb", "firewire", "bluetooth" ); key_constant!(DEVICE_SUBSYSTEM, PW_KEY_DEVICE_SUBSYSTEM, /// device subsystem ); #[cfg(feature = "v0_3_53")] key_constant!(DEVICE_SYSFS_PATH, PW_KEY_DEVICE_SYSFS_PATH, /// device sysfs path ); key_constant!(DEVICE_ICON, PW_KEY_DEVICE_ICON, /// icon for the device. A base64 blob containing PNG image data ); key_constant!(DEVICE_ICON_NAME, PW_KEY_DEVICE_ICON_NAME, /// an XDG icon name for the device. Ex. "sound-card-speakers-usb" ); key_constant!(DEVICE_INTENDED_ROLES, PW_KEY_DEVICE_INTENDED_ROLES, /// intended use. A space separated list of roles (see PW_KEY_MEDIA_ROLE) this device is particularly well suited for, due to latency, quality or form factor. ); key_constant!(DEVICE_CACHE_PARAMS, PW_KEY_DEVICE_CACHE_PARAMS, /// cache the device spa params ); key_constant!(MODULE_ID, PW_KEY_MODULE_ID, /// the module id ); key_constant!(MODULE_NAME, PW_KEY_MODULE_NAME, /// the name of the module ); key_constant!(MODULE_AUTHOR, PW_KEY_MODULE_AUTHOR, /// the author's name ); key_constant!(MODULE_DESCRIPTION, PW_KEY_MODULE_DESCRIPTION, /// a human readable one-line description of the module's purpose. ); key_constant!(MODULE_USAGE, PW_KEY_MODULE_USAGE, /// a human readable usage description of the module's arguments. ); key_constant!(MODULE_VERSION, PW_KEY_MODULE_VERSION, /// a version string for the module. ); key_constant!(FACTORY_ID, PW_KEY_FACTORY_ID, /// the factory id ); key_constant!(FACTORY_NAME, PW_KEY_FACTORY_NAME, /// the name of the factory ); key_constant!(FACTORY_USAGE, PW_KEY_FACTORY_USAGE, /// the usage of the factory ); key_constant!(FACTORY_TYPE_NAME, PW_KEY_FACTORY_TYPE_NAME, /// the name of the type created by a factory ); key_constant!(FACTORY_TYPE_VERSION, PW_KEY_FACTORY_TYPE_VERSION, /// the version of the type created by a factory ); key_constant!(STREAM_IS_LIVE, PW_KEY_STREAM_IS_LIVE, /// Indicates that the stream is live. ); key_constant!(STREAM_LATENCY_MIN, PW_KEY_STREAM_LATENCY_MIN, /// The minimum latency of the stream. ); key_constant!(STREAM_LATENCY_MAX, PW_KEY_STREAM_LATENCY_MAX, /// The maximum latency of the stream ); key_constant!(STREAM_MONITOR, PW_KEY_STREAM_MONITOR, /// Indicates that the stream is monitoring and might select a less accurate but faster conversion algorithm. ); key_constant!(STREAM_DONT_REMIX, PW_KEY_STREAM_DONT_REMIX, /// don't remix channels ); key_constant!(STREAM_CAPTURE_SINK, PW_KEY_STREAM_CAPTURE_SINK, /// Try to capture the sink output instead of source output ); key_constant!(MEDIA_TYPE, PW_KEY_MEDIA_TYPE, /// Media type, one of Audio, Video, Midi ); key_constant!(MEDIA_CATEGORY, PW_KEY_MEDIA_CATEGORY, /// Media Category: Playback, Capture, Duplex, Monitor, Manager ); key_constant!(MEDIA_ROLE, PW_KEY_MEDIA_ROLE, /// Role: Movie, Music, Camera, Screen, Communication, Game, Notification, DSP, Production, Accessibility, Test ); key_constant!(MEDIA_CLASS, PW_KEY_MEDIA_CLASS, /// class Ex: "Video/Source" ); key_constant!(MEDIA_NAME, PW_KEY_MEDIA_NAME, /// media name. Ex: "Pink Floyd: Time" ); key_constant!(MEDIA_TITLE, PW_KEY_MEDIA_TITLE, /// title. Ex: "Time" ); key_constant!(MEDIA_ARTIST, PW_KEY_MEDIA_ARTIST, /// artist. Ex: "Pink Floyd" ); key_constant!(MEDIA_COPYRIGHT, PW_KEY_MEDIA_COPYRIGHT, /// copyright string ); key_constant!(MEDIA_SOFTWARE, PW_KEY_MEDIA_SOFTWARE, /// generator software ); key_constant!(MEDIA_LANGUAGE, PW_KEY_MEDIA_LANGUAGE, /// language in POSIX format. Ex: en_GB ); key_constant!(MEDIA_FILENAME, PW_KEY_MEDIA_FILENAME, /// filename ); key_constant!(MEDIA_ICON, PW_KEY_MEDIA_ICON, /// icon for the media, a base64 blob with PNG image data ); key_constant!(MEDIA_ICON_NAME, PW_KEY_MEDIA_ICON_NAME, /// an XDG icon name for the media. Ex: "audio-x-mp3" ); key_constant!(MEDIA_COMMENT, PW_KEY_MEDIA_COMMENT, /// extra comment ); key_constant!(MEDIA_DATE, PW_KEY_MEDIA_DATE, /// date of the media ); key_constant!(MEDIA_FORMAT, PW_KEY_MEDIA_FORMAT, /// format of the media ); key_constant!(FORMAT_DSP, PW_KEY_FORMAT_DSP, /// a dsp format. Ex: "32 bit float mono audio" ); key_constant!(AUDIO_CHANNEL, PW_KEY_AUDIO_CHANNEL, /// an audio channel. Ex: "FL" ); #[cfg(feature = "v0_3_32")] key_constant!(AUDIO_RATE, PW_KEY_AUDIO_RATE, /// an audio samplerate ); key_constant!(AUDIO_CHANNELS, PW_KEY_AUDIO_CHANNELS, /// number of audio channels ); key_constant!(AUDIO_FORMAT, PW_KEY_AUDIO_FORMAT, /// an audio format. Ex: "S16LE" ); #[cfg(feature = "v0_3_43")] key_constant!(AUDIO_ALLOWED_RATES, PW_KEY_AUDIO_ALLOWED_RATES, /// a list of allowed samplerates ex. "[ 44100 48000 ]" ); key_constant!(VIDEO_RATE, PW_KEY_VIDEO_RATE, /// a video framerate ); key_constant!(VIDEO_FORMAT, PW_KEY_VIDEO_FORMAT, /// a video format ); key_constant!(VIDEO_SIZE, PW_KEY_VIDEO_SIZE, /// a video size as "\x\" ); #[cfg(feature = "v0_3_44")] key_constant!(TARGET_OBJECT, PW_KEY_TARGET_OBJECT, /// a target object to link to. This can be and object name or object.serial PIPEWIRE_KEYS_H ); #[cfg(test)] mod tests { use super::*; #[test] fn keys() { assert_eq!(*REMOTE_NAME, "remote.name"); } } pipewire-0.8.0/src/lib.rs000064400000000000000000000145231046102023000133650ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT //! # Rust bindings for pipewire //! `pipewire` is a crate offering a rustic bindings for `libpipewire`, the library for interacting //! with the pipewire server. //! //! Programs that interact with pipewire usually react to events from the server by registering callbacks //! and invoke methods on objects on the server by calling methods on local proxy objects. //! //! ## Getting started //! Most programs that interact with pipewire will need the same few basic objects: //! - A [`MainLoop`](`main_loop::MainLoop`) that drives the program, reacting to any incoming events and dispatching method calls. //! Most of a time, the program/thread will sit idle in this loop, waiting on events to occur. //! - A [`Context`](`context::Context`) that keeps track of any pipewire resources. //! - A [`Core`](`core::Core`) that is a proxy for the remote pipewire instance, used to send messages to and receive events from the //! remote server. //! - Optionally, a [`Registry`](`registry::Registry`) that can be used to manage and track available objects on the server. //! //! This is how they can be created: // ignored because https://gitlab.freedesktop.org/pipewire/pipewire-rs/-/issues/19 //! ```no_run //! use pipewire::{main_loop::MainLoop, context::Context}; //! //! fn main() -> Result<(), Box> { //! let mainloop = MainLoop::new(None)?; //! let context = Context::new(&mainloop)?; //! let core = context.connect(None)?; //! let registry = core.get_registry()?; //! //! Ok(()) //! } //! ``` //! //! Now you can start hooking up different kinds of callbacks to the objects to react to events, and call methods //! on objects to change the state of the remote. //! ```no_run //! use pipewire::{main_loop::MainLoop, context::Context}; //! //! fn main() -> Result<(), Box> { //! let mainloop = MainLoop::new(None)?; //! let context = Context::new(&mainloop)?; //! let core = context.connect(None)?; //! let registry = core.get_registry()?; //! //! // Register a callback to the `global` event on the registry, which notifies of any new global objects //! // appearing on the remote. //! // The callback will only get called as long as we keep the returned listener alive. //! let _listener = registry //! .add_listener_local() //! .global(|global| println!("New global: {:?}", global)) //! .register(); //! //! // Calling the `destroy_global` method on the registry will destroy the object with the specified id on the remote. //! // We don't have a specific object to destroy now, so this is commented out. //! # // FIXME: Find a better method for this example we can actually call. //! // registry.destroy_global(313).into_result()?; //! //! mainloop.run(); //! //! Ok(()) //! } //! ``` //! Note that registering any callback requires the closure to have the `'static` lifetime, so if you need to capture //! any variables, use `move ||` closures, and use `std::rc::Rc`s to access shared variables //! and some `std::cell` variant if you need to mutate them. //! //! Also note that we called `mainloop.run()` at the end. //! This will enter the loop, and won't return until we call `mainloop.quit()` from some event. //! If we didn't run the loop, events and method invocations would not be processed, so the program would terminate //! without doing much. //! //! ## The main loop //! Sometimes, other stuff needs to be done even though we are waiting inside the main loop. \ //! This can be done by adding sources to the loop. //! //! For example, we can call a function on an interval: //! //! ```no_run //! use pipewire::main_loop::MainLoop; //! use std::time::Duration; //! //! fn main() -> Result<(), Box> { //! let mainloop = MainLoop::new(None)?; //! //! let timer = mainloop.loop_().add_timer(|_| println!("Hello")); //! // Call the first time in half a second, and then in a one second interval. //! timer.update_timer(Some(Duration::from_millis(500)), Some(Duration::from_secs(1))).into_result()?; //! //! mainloop.run(); //! //! Ok(()) //! } //! ``` //! This program will print out "Hello" every second forever. //! //! Using similar methods, you can also react to IO or Signals, or call a callback whenever the loop is idle. //! //! ## Multithreading //! The pipewire library is not really thread-safe, so pipewire objects do not implement [`Send`](`std::marker::Send`) //! or [`Sync`](`std::marker::Sync`). //! //! However, you can spawn a [`MainLoop`](`main_loop::MainLoop`) in another thread and do bidirectional communication using two channels. //! //! To send messages to the main thread, we can easily use a [`std::sync::mpsc`]. //! Because we are stuck in the main loop in the pipewire thread and can't just block on receiving a message, //! we use a [`pipewire::channel`](`crate::channel`) instead. //! //! See the [`pipewire::channel`](`crate::channel`) module for details. pub mod buffer; pub mod channel; pub mod client; pub mod constants; pub mod context; pub mod core; pub mod device; pub mod factory; pub mod keys; pub mod link; pub mod loop_; pub mod main_loop; pub mod metadata; pub mod module; pub mod node; pub mod permissions; pub mod port; pub mod properties; pub mod proxy; pub mod registry; pub mod stream; pub mod thread_loop; pub mod types; mod error; pub use error::*; mod utils; pub use pw_sys as sys; pub use spa; // Re-export all the traits in a prelude module, so that applications // can always "use pipewire::prelude::*" without getting conflicts pub mod prelude { pub use spa::prelude::*; } use std::ptr; /// Initialize PipeWire /// /// Initialize the PipeWire system and set up debugging /// through the environment variable `PIPEWIRE_DEBUG`. pub fn init() { use once_cell::sync::OnceCell; static INITIALIZED: OnceCell<()> = OnceCell::new(); INITIALIZED.get_or_init(|| unsafe { pw_sys::pw_init(ptr::null_mut(), ptr::null_mut()) }); } /// Deinitialize PipeWire /// /// # Safety /// This must only be called once during the lifetime of the process, once no PipeWire threads /// are running anymore and all PipeWire resources are released. pub unsafe fn deinit() { pw_sys::pw_deinit() } #[cfg(test)] mod tests { use super::*; #[test] fn test_init() { init(); unsafe { deinit(); } } } pipewire-0.8.0/src/link.rs000064400000000000000000000174041046102023000135550ustar 00000000000000use std::{ ffi::{c_void, CStr}, fmt, mem, ops::Deref, pin::Pin, ptr, }; use bitflags::bitflags; use spa::spa_interface_call_method; use crate::{ proxy::{Listener, Proxy, ProxyT}, types::ObjectType, }; #[derive(Debug)] pub struct Link { proxy: Proxy, } impl ProxyT for Link { fn type_() -> ObjectType { ObjectType::Link } fn upcast(self) -> Proxy { self.proxy } fn upcast_ref(&self) -> &Proxy { &self.proxy } unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self where Self: Sized, { Self { proxy } } } impl Link { #[must_use] pub fn add_listener_local(&self) -> LinkListenerLocalBuilder { LinkListenerLocalBuilder { link: self, cbs: ListenerLocalCallbacks::default(), } } } pub struct LinkListener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener for LinkListener {} impl Drop for LinkListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } #[derive(Default)] struct ListenerLocalCallbacks { #[allow(clippy::type_complexity)] info: Option>, } pub struct LinkListenerLocalBuilder<'link> { link: &'link Link, cbs: ListenerLocalCallbacks, } impl<'a> LinkListenerLocalBuilder<'a> { #[must_use] pub fn info(mut self, info: F) -> Self where F: Fn(&LinkInfoRef) + 'static, { self.cbs.info = Some(Box::new(info)); self } #[must_use] pub fn register(self) -> LinkListener { unsafe extern "C" fn link_events_info( data: *mut c_void, info: *const pw_sys::pw_link_info, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let info = ptr::NonNull::new(info as *mut pw_sys::pw_link_info).expect("info is NULL"); let info = info.cast::().as_ref(); callbacks.info.as_ref().unwrap()(info); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_LINK_EVENTS; if self.cbs.info.is_some() { e.info = Some(link_events_info); } e }; let (listener, data) = unsafe { let link = &self.link.proxy.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa_interface_call_method!( link, pw_sys::pw_link_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; LinkListener { events: e, listener, data, } } } #[repr(transparent)] pub struct LinkInfoRef(pw_sys::pw_link_info); impl LinkInfoRef { pub fn as_raw(&self) -> &pw_sys::pw_link_info { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_link_info { std::ptr::addr_of!(self.0).cast_mut() } pub fn id(&self) -> u32 { self.0.id } pub fn output_node_id(&self) -> u32 { self.0.output_node_id } pub fn output_port_id(&self) -> u32 { self.0.output_port_id } pub fn input_node_id(&self) -> u32 { self.0.input_node_id } pub fn input_port_id(&self) -> u32 { self.0.input_port_id } pub fn state(&self) -> LinkState { let raw_state = self.0.state; match raw_state { pw_sys::pw_link_state_PW_LINK_STATE_ERROR => { let error = unsafe { CStr::from_ptr(self.0.error).to_str().unwrap() }; LinkState::Error(error) } pw_sys::pw_link_state_PW_LINK_STATE_UNLINKED => LinkState::Unlinked, pw_sys::pw_link_state_PW_LINK_STATE_INIT => LinkState::Init, pw_sys::pw_link_state_PW_LINK_STATE_NEGOTIATING => LinkState::Negotiating, pw_sys::pw_link_state_PW_LINK_STATE_ALLOCATING => LinkState::Allocating, pw_sys::pw_link_state_PW_LINK_STATE_PAUSED => LinkState::Paused, pw_sys::pw_link_state_PW_LINK_STATE_ACTIVE => LinkState::Active, _ => panic!("Invalid link state: {}", raw_state), } } pub fn change_mask(&self) -> LinkChangeMask { LinkChangeMask::from_bits_retain(self.0.change_mask) } pub fn format(&self) -> Option<&spa::pod::Pod> { let format = self.0.format; if format.is_null() { None } else { Some(unsafe { spa::pod::Pod::from_raw(format) }) } } pub fn props(&self) -> Option<&spa::utils::dict::DictRef> { let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast(); ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() }) } } impl fmt::Debug for LinkInfoRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("LinkInfoRef") .field("id", &self.id()) .field("output_node_id", &self.output_node_id()) .field("output_port_id", &self.output_port_id()) .field("input_node_id", &self.input_node_id()) .field("input_port_id", &self.input_port_id()) .field("change-mask", &self.change_mask()) .field("state", &self.state()) .field("props", &self.props()) // TODO: .field("format", &self.format()) .finish() } } pub struct LinkInfo { ptr: ptr::NonNull, } impl LinkInfo { pub fn new(ptr: ptr::NonNull) -> Self { Self { ptr } } pub fn from_raw(raw: *mut pw_sys::pw_link_info) -> Self { Self { ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"), } } pub fn into_raw(self) -> *mut pw_sys::pw_link_info { std::mem::ManuallyDrop::new(self).ptr.as_ptr() } } impl Drop for LinkInfo { fn drop(&mut self) { unsafe { pw_sys::pw_link_info_free(self.ptr.as_ptr()) } } } impl std::ops::Deref for LinkInfo { type Target = LinkInfoRef; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for LinkInfo { fn as_ref(&self) -> &LinkInfoRef { self.deref() } } bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct LinkChangeMask: u64 { const STATE = pw_sys::PW_LINK_CHANGE_MASK_STATE as u64; const FORMAT = pw_sys::PW_LINK_CHANGE_MASK_FORMAT as u64; const PROPS = pw_sys::PW_LINK_CHANGE_MASK_PROPS as u64; } } impl fmt::Debug for LinkInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("LinkInfo") .field("id", &self.id()) .field("output_node_id", &self.output_node_id()) .field("output_port_id", &self.output_port_id()) .field("input_node_id", &self.input_node_id()) .field("input_port_id", &self.input_port_id()) .field("change-mask", &self.change_mask()) .field("state", &self.state()) .field("props", &self.props()) // TODO: .field("format", &self.format()) .finish() } } #[derive(Debug)] pub enum LinkState<'a> { Error(&'a str), Unlinked, Init, Negotiating, Allocating, Paused, Active, } pipewire-0.8.0/src/loop_.rs000064400000000000000000000500251046102023000137240ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ convert::TryInto, ops::Deref, os::unix::prelude::*, ptr::{self, NonNull}, rc::{Rc, Weak}, time::Duration, }; use libc::{c_int, c_void}; pub use nix::sys::signal::Signal; use spa::{spa_interface_call_method, support::system::IoFlags, utils::result::SpaResult}; use crate::{utils::assert_main_thread, Error}; /// A transparent wrapper around a raw [`pw_loop`](`pw_sys::pw_loop`). /// It is usually only seen in a reference (`&LoopRef`). /// /// An owned version, [`Loop`], is available, /// which lets you create and own a [`pw_loop`](`pw_sys::pw_loop`), /// but other objects, such as [`MainLoop`](`crate::main_loop::MainLoop`), also contain them. #[repr(transparent)] pub struct LoopRef(pw_sys::pw_loop); impl LoopRef { pub fn as_raw(&self) -> &pw_sys::pw_loop { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_loop { std::ptr::addr_of!(self.0).cast_mut() } /// Get the file descriptor backing this loop. pub fn fd(&self) -> BorrowedFd<'_> { unsafe { let mut iface = self.as_raw().control.as_ref().unwrap().iface; let raw_fd = spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_control_methods, get_fd, ); BorrowedFd::borrow_raw(raw_fd) } } /// Enter a loop /// /// Start an iteration of the loop. This function should be called /// before calling iterate and is typically used to capture the thread /// that this loop will run in. /// /// # Safety /// Each call of `enter` must be paired with a call of `leave`. pub unsafe fn enter(&self) { let mut iface = self.as_raw().control.as_ref().unwrap().iface; spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_control_methods, enter, ) } /// Leave a loop /// /// Ends the iteration of a loop. This should be called after calling /// iterate. /// /// # Safety /// Each call of `leave` must be paired with a call of `enter`. pub unsafe fn leave(&self) { let mut iface = self.as_raw().control.as_ref().unwrap().iface; spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_control_methods, leave, ) } /// Perform one iteration of the loop. /// /// An optional timeout can be provided. /// 0 for no timeout, -1 for infinite timeout. /// /// This function will block /// up to the provided timeout and then dispatch the fds with activity. /// The number of dispatched fds is returned. /// /// This will automatically call [`Self::enter()`] on the loop before iterating, and [`Self::leave()`] afterwards. /// /// # Panics /// This function will panic if the provided timeout as milliseconds does not fit inside a /// `c_int` integer. pub fn iterate(&self, timeout: std::time::Duration) -> i32 { unsafe { self.enter(); let res = self.iterate_unguarded(timeout); self.leave(); res } } /// A variant of [`iterate()`](`Self::iterate()`) that does not call [`Self::enter()`] and [`Self::leave()`] on the loop. /// /// # Safety /// Before calling this, [`Self::enter()`] must be called, and [`Self::leave()`] must be called afterwards. pub unsafe fn iterate_unguarded(&self, timeout: std::time::Duration) -> i32 { let mut iface = self.as_raw().control.as_ref().unwrap().iface; let timeout: c_int = timeout .as_millis() .try_into() .expect("Provided timeout does not fit in a c_int"); spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_control_methods, iterate, timeout ) } /// Register some type of IO object with a callback that is called when reading/writing on the IO object /// is available. /// /// The specified `event_mask` determines whether to trigger when either input, output, or any of the two is available. /// /// The returned IoSource needs to take ownership of the IO object, but will provide a reference to the callback when called. #[must_use] pub fn add_io(&self, io: I, event_mask: IoFlags, callback: F) -> IoSource where I: AsRawFd, F: Fn(&mut I) + 'static, Self: Sized, { unsafe extern "C" fn call_closure(data: *mut c_void, _fd: RawFd, _mask: u32) where I: AsRawFd, { let (io, callback) = (data as *mut IoSourceData).as_mut().unwrap(); callback(io); } let fd = io.as_raw_fd(); let data = Box::into_raw(Box::new((io, Box::new(callback) as Box))); let (source, data) = unsafe { let mut iface = self.as_raw().utils.as_ref().unwrap().iface; let source = spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_utils_methods, add_io, fd, event_mask.bits(), // Never let the loop close the fd, this should be handled via `Drop` implementations. false, Some(call_closure::), data as *mut _ ); (source, Box::from_raw(data)) }; let ptr = ptr::NonNull::new(source).expect("source is NULL"); IoSource { ptr, loop_: self, _data: data, } } /// Register a callback to be called whenever the loop is idle. /// /// This can be enabled and disabled as needed with the `enabled` parameter, /// and also with the `enable` method on the returned source. #[must_use] pub fn add_idle(&self, enabled: bool, callback: F) -> IdleSource where F: Fn() + 'static, { unsafe extern "C" fn call_closure(data: *mut c_void) where F: Fn(), { let callback = (data as *mut F).as_ref().unwrap(); callback(); } let data = Box::into_raw(Box::new(callback)); let (source, data) = unsafe { let mut iface = self.as_raw().utils.as_ref().unwrap().iface; let source = spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_utils_methods, add_idle, enabled, Some(call_closure::), data as *mut _ ); (source, Box::from_raw(data)) }; let ptr = ptr::NonNull::new(source).expect("source is NULL"); IdleSource { ptr, loop_: self, _data: data, } } /// Register a signal with a callback that is called when the signal is sent. /// /// For example, this can be used to quit the loop when the process receives the `SIGTERM` signal. #[must_use] pub fn add_signal_local(&self, signal: Signal, callback: F) -> SignalSource where F: Fn() + 'static, Self: Sized, { assert_main_thread(); unsafe extern "C" fn call_closure(data: *mut c_void, _signal: c_int) where F: Fn(), { let callback = (data as *mut F).as_ref().unwrap(); callback(); } let data = Box::into_raw(Box::new(callback)); let (source, data) = unsafe { let mut iface = self.as_raw().utils.as_ref().unwrap().iface; let source = spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_utils_methods, add_signal, signal as c_int, Some(call_closure::), data as *mut _ ); (source, Box::from_raw(data)) }; let ptr = ptr::NonNull::new(source).expect("source is NULL"); SignalSource { ptr, loop_: self, _data: data, } } /// Register a new event with a callback that is called when the event happens. /// /// The returned [`EventSource`] can be used to trigger the event. #[must_use] pub fn add_event(&self, callback: F) -> EventSource where F: Fn() + 'static, Self: Sized, { unsafe extern "C" fn call_closure(data: *mut c_void, _count: u64) where F: Fn(), { let callback = (data as *mut F).as_ref().unwrap(); callback(); } let data = Box::into_raw(Box::new(callback)); let (source, data) = unsafe { let mut iface = self.as_raw().utils.as_ref().unwrap().iface; let source = spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_utils_methods, add_event, Some(call_closure::), data as *mut _ ); (source, Box::from_raw(data)) }; let ptr = ptr::NonNull::new(source).expect("source is NULL"); EventSource { ptr, loop_: self, _data: data, } } /// Register a timer with the loop with a callback that is called after the timer expired. /// /// The timer will start out inactive, and the returned [`TimerSource`] can be used to arm the timer, or disarm it again. /// /// The callback will be provided with the number of timer expirations since the callback was last called. #[must_use] pub fn add_timer(&self, callback: F) -> TimerSource where F: Fn(u64) + 'static, Self: Sized, { unsafe extern "C" fn call_closure(data: *mut c_void, expirations: u64) where F: Fn(u64), { let callback = (data as *mut F).as_ref().unwrap(); callback(expirations); } let data = Box::into_raw(Box::new(callback)); let (source, data) = unsafe { let mut iface = self.as_raw().utils.as_ref().unwrap().iface; let source = spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_utils_methods, add_timer, Some(call_closure::), data as *mut _ ); (source, Box::from_raw(data)) }; let ptr = ptr::NonNull::new(source).expect("source is NULL"); TimerSource { ptr, loop_: self, _data: data, } } /// Destroy a source that belongs to this loop. /// /// # Safety /// The provided source must belong to this loop. unsafe fn destroy_source(&self, source: &S) where S: IsSource, Self: Sized, { let mut iface = self.as_raw().utils.as_ref().unwrap().iface; spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_utils_methods, destroy_source, source.as_ptr() ) } } /// Trait implemented by objects that implement a `pw_loop` and are reference counted in some way. /// /// # Safety /// /// The `LoopRef` returned by the implementation of `AsRef` must remain valid as long as any clone /// of the trait implementor is still alive. \ pub unsafe trait IsLoopRc: Clone + AsRef + 'static {} #[derive(Clone, Debug)] pub struct Loop { inner: Rc, } impl Loop { /// Create a new [`Loop`]. pub fn new(properties: Option<&spa::utils::dict::DictRef>) -> Result { // This is a potential "entry point" to the library, so we need to ensure it is initialized. crate::init(); unsafe { let props = properties .map_or(ptr::null(), |props| props.as_raw()) .cast_mut(); let l = pw_sys::pw_loop_new(props); let ptr = ptr::NonNull::new(l).ok_or(Error::CreationFailed)?; Ok(Self::from_raw(ptr)) } } /// Create a new loop from a raw [`pw_loop`](`pw_sys::pw_loop`), taking ownership of it. /// /// # Safety /// The provided pointer must point to a valid, well aligned [`pw_loop`](`pw_sys::pw_loop`). /// /// The raw loop should not be manually destroyed or moved, as the new [`Loop`] takes ownership of it. pub unsafe fn from_raw(ptr: NonNull) -> Self { Self { inner: Rc::new(LoopInner::from_raw(ptr)), } } pub fn downgrade(&self) -> WeakLoop { let weak = Rc::downgrade(&self.inner); WeakLoop { weak } } } // Safety: The inner pw_loop is guaranteed to remain valid while any clone of the `Loop` is held, // because we use an internal Rc to keep it alive. unsafe impl IsLoopRc for Loop {} impl std::ops::Deref for Loop { type Target = LoopRef; fn deref(&self) -> &Self::Target { let loop_ = self.inner.ptr.as_ptr(); unsafe { &*(loop_.cast::()) } } } impl std::convert::AsRef for Loop { fn as_ref(&self) -> &LoopRef { self.deref() } } pub struct WeakLoop { weak: Weak, } impl WeakLoop { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| Loop { inner }) } } #[derive(Debug)] struct LoopInner { ptr: ptr::NonNull, } impl LoopInner { pub unsafe fn from_raw(ptr: NonNull) -> Self { Self { ptr } } } impl Drop for LoopInner { fn drop(&mut self) { unsafe { pw_sys::pw_loop_destroy(self.ptr.as_ptr()) } } } pub trait IsSource { /// Return a valid pointer to a raw `spa_source`. fn as_ptr(&self) -> *mut spa_sys::spa_source; } type IoSourceData = (I, Box); /// A source that can be used to react to IO events. /// /// This source can be obtained by calling [`add_io`](`LoopRef::add_io`) on a loop, registering a callback to it. pub struct IoSource<'l, I> where I: AsRawFd, { ptr: ptr::NonNull, loop_: &'l LoopRef, // Store data wrapper to prevent leak _data: Box>, } impl<'l, I> IsSource for IoSource<'l, I> where I: AsRawFd, { fn as_ptr(&self) -> *mut spa_sys::spa_source { self.ptr.as_ptr() } } impl<'l, I> Drop for IoSource<'l, I> where I: AsRawFd, { fn drop(&mut self) { unsafe { self.loop_.destroy_source(self) } } } /// A source that can be used to have a callback called when the loop is idle. /// /// This source can be obtained by calling [`add_idle`](`LoopRef::add_idle`) on a loop, registering a callback to it. pub struct IdleSource<'l> { ptr: ptr::NonNull, loop_: &'l LoopRef, // Store data wrapper to prevent leak _data: Box, } impl<'l> IdleSource<'l> { /// Set the source as enabled or disabled, allowing or preventing the callback from being called. pub fn enable(&self, enable: bool) { unsafe { let mut iface = self.loop_.as_raw().utils.as_ref().unwrap().iface; spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_utils_methods, enable_idle, self.as_ptr(), enable ); } } } impl<'l> IsSource for IdleSource<'l> { fn as_ptr(&self) -> *mut spa_sys::spa_source { self.ptr.as_ptr() } } impl<'l> Drop for IdleSource<'l> { fn drop(&mut self) { unsafe { self.loop_.destroy_source(self) } } } /// A source that can be used to react to signals. /// /// This source can be obtained by calling [`add_signal_local`](`LoopRef::add_signal_local`) on a loop, registering a callback to it. pub struct SignalSource<'l> { ptr: ptr::NonNull, loop_: &'l LoopRef, // Store data wrapper to prevent leak _data: Box, } impl<'l> IsSource for SignalSource<'l> { fn as_ptr(&self) -> *mut spa_sys::spa_source { self.ptr.as_ptr() } } impl<'l> Drop for SignalSource<'l> { fn drop(&mut self) { unsafe { self.loop_.destroy_source(self) } } } /// A source that can be used to signal to a loop that an event has occurred. /// /// This source can be obtained by calling [`add_event`](`LoopRef::add_event`) on a loop, registering a callback to it. /// /// By calling [`signal`](`EventSource::signal`) on the `EventSource`, the loop is signaled that the event has occurred. /// It will then call the callback at the next possible occasion. pub struct EventSource<'l> { ptr: ptr::NonNull, loop_: &'l LoopRef, // Store data wrapper to prevent leak _data: Box, } impl<'l> IsSource for EventSource<'l> { fn as_ptr(&self) -> *mut spa_sys::spa_source { self.ptr.as_ptr() } } impl<'l> EventSource<'l> { /// Signal the loop associated with this source that the event has occurred, /// to make the loop call the callback at the next possible occasion. pub fn signal(&self) -> SpaResult { let res = unsafe { let mut iface = self.loop_.as_raw().utils.as_ref().unwrap().iface; spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_utils_methods, signal_event, self.as_ptr() ) }; SpaResult::from_c(res) } } impl<'l> Drop for EventSource<'l> { fn drop(&mut self) { unsafe { self.loop_.destroy_source(self) } } } /// A source that can be used to have a callback called on a timer. /// /// This source can be obtained by calling [`add_timer`](`LoopRef::add_timer`) on a loop, registering a callback to it. /// /// The timer starts out inactive. /// You can arm or disarm the timer by calling [`update_timer`](`Self::update_timer`). pub struct TimerSource<'l> { ptr: ptr::NonNull, loop_: &'l LoopRef, // Store data wrapper to prevent leak _data: Box, } impl<'l> TimerSource<'l> { /// Arm or disarm the timer. /// /// The timer will be called the next time after the provided `value` duration. /// After that, the timer will be repeatedly called again at the the specified `interval`. /// /// If `interval` is `None` or zero, the timer will only be called once. \ /// If `value` is `None` or zero, the timer will be disabled. /// /// # Panics /// The provided durations seconds must fit in an i64. Otherwise, this function will panic. pub fn update_timer(&self, value: Option, interval: Option) -> SpaResult { fn duration_to_timespec(duration: Duration) -> spa_sys::timespec { spa_sys::timespec { tv_sec: duration.as_secs().try_into().expect("Duration too long"), tv_nsec: duration.subsec_nanos().try_into().unwrap(), } } let value = duration_to_timespec(value.unwrap_or_default()); let interval = duration_to_timespec(interval.unwrap_or_default()); let res = unsafe { let mut iface = self.loop_.as_raw().utils.as_ref().unwrap().iface; spa_interface_call_method!( &mut iface as *mut spa_sys::spa_interface, spa_sys::spa_loop_utils_methods, update_timer, self.as_ptr(), &value as *const _ as *mut _, &interval as *const _ as *mut _, false ) }; SpaResult::from_c(res) } } impl<'l> IsSource for TimerSource<'l> { fn as_ptr(&self) -> *mut spa_sys::spa_source { self.ptr.as_ptr() } } impl<'l> Drop for TimerSource<'l> { fn drop(&mut self) { unsafe { self.loop_.destroy_source(self) } } } pipewire-0.8.0/src/main_loop.rs000064400000000000000000000055201046102023000145710ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::ptr::{self, NonNull}; use std::rc::{Rc, Weak}; use crate::{ error::Error, loop_::{IsLoopRc, LoopRef}, }; #[derive(Debug, Clone)] pub struct MainLoop { inner: Rc, } impl MainLoop { /// Initialize Pipewire and create a new `MainLoop` pub fn new(properties: Option<&spa::utils::dict::DictRef>) -> Result { super::init(); unsafe { let props = properties .map_or(ptr::null(), |props| props.as_raw()) .cast_mut(); let l = pw_sys::pw_main_loop_new(props); let ptr = ptr::NonNull::new(l).ok_or(Error::CreationFailed)?; Ok(Self::from_raw(ptr)) } } /// Create a new main loop from a raw [`pw_main_loop`](`pw_sys::pw_main_loop`), taking ownership of it. /// /// # Safety /// The provided pointer must point to a valid, well aligned [`pw_main_loop`](`pw_sys::pw_main_loop`). /// /// The raw loop should not be manually destroyed or moved, as the new [`MainLoop`] takes ownership of it. pub unsafe fn from_raw(ptr: NonNull) -> Self { Self { inner: Rc::new(MainLoopInner::from_raw(ptr)), } } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_main_loop { self.inner.ptr.as_ptr() } pub fn downgrade(&self) -> WeakMainLoop { let weak = Rc::downgrade(&self.inner); WeakMainLoop { weak } } pub fn loop_(&self) -> &LoopRef { unsafe { let pw_loop = pw_sys::pw_main_loop_get_loop(self.as_raw_ptr()); // FIXME: Make sure pw_loop is not null &*(pw_loop.cast::()) } } pub fn run(&self) { unsafe { pw_sys::pw_main_loop_run(self.as_raw_ptr()); } } pub fn quit(&self) { unsafe { pw_sys::pw_main_loop_quit(self.as_raw_ptr()); } } } // Safety: The pw_loop is guaranteed to remain valid while any clone of the `MainLoop` is held, // because we use an internal Rc to keep the pw_main_loop containing the pw_loop alive. unsafe impl IsLoopRc for MainLoop {} impl std::convert::AsRef for MainLoop { fn as_ref(&self) -> &LoopRef { self.loop_() } } pub struct WeakMainLoop { weak: Weak, } impl WeakMainLoop { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| MainLoop { inner }) } } #[derive(Debug)] struct MainLoopInner { ptr: ptr::NonNull, } impl MainLoopInner { pub unsafe fn from_raw(ptr: NonNull) -> Self { Self { ptr } } } impl Drop for MainLoopInner { fn drop(&mut self) { unsafe { pw_sys::pw_main_loop_destroy(self.ptr.as_ptr()) } } } pipewire-0.8.0/src/metadata.rs000064400000000000000000000124661046102023000144030ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::ffi::CString; use std::os::raw::c_char; use std::{ ffi::{c_void, CStr}, mem, pin::Pin, ptr, }; use crate::{ proxy::{Listener, Proxy, ProxyT}, types::ObjectType, }; use spa::spa_interface_call_method; #[derive(Debug)] pub struct Metadata { proxy: Proxy, } impl ProxyT for Metadata { fn type_() -> ObjectType { ObjectType::Metadata } fn upcast(self) -> Proxy { self.proxy } fn upcast_ref(&self) -> &Proxy { &self.proxy } unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self where Self: Sized, { Self { proxy } } } impl Metadata { pub fn add_listener_local(&self) -> MetadataListenerLocalBuilder { MetadataListenerLocalBuilder { metadata: self, cbs: ListenerLocalCallbacks::default(), } } pub fn set_property(&self, subject: u32, key: &str, type_: Option<&str>, value: Option<&str>) { // Keep CStrings allocated here in order for pointers to remain valid. let key = CString::new(key).expect("Invalid byte in metadata key"); let type_ = type_.map(|t| CString::new(t).expect("Invalid byte in metadata type")); let value = value.map(|v| CString::new(v).expect("Invalid byte in metadata value")); unsafe { spa::spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_metadata_methods, set_property, subject, key.as_ptr() as *const _, type_.as_deref().map_or_else(ptr::null, CStr::as_ptr) as *const _, value.as_deref().map_or_else(ptr::null, CStr::as_ptr) as *const _ ); } } pub fn clear(&self) { unsafe { spa::spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_metadata_methods, clear, ); } } } pub struct MetadataListener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener for MetadataListener {} impl Drop for MetadataListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } #[derive(Default)] struct ListenerLocalCallbacks { #[allow(clippy::type_complexity)] property: Option, Option<&str>, Option<&str>) -> i32>>, } #[must_use] pub struct MetadataListenerLocalBuilder<'meta> { metadata: &'meta Metadata, cbs: ListenerLocalCallbacks, } impl<'meta> MetadataListenerLocalBuilder<'meta> { /// Add property changed callback. /// /// Callback parameters: subject, key, type, value. /// /// `None` for `value` means removal of property. /// `None` for `key` means removal of all properties. pub fn property(mut self, property: F) -> Self where F: Fn(u32, Option<&str>, Option<&str>, Option<&str>) -> i32 + 'static, { self.cbs.property = Some(Box::new(property)); self } #[must_use] pub fn register(self) -> MetadataListener { unsafe extern "C" fn metadata_events_property( data: *mut c_void, subject: u32, key: *const c_char, type_: *const c_char, value: *const c_char, ) -> i32 { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let key = if !key.is_null() { Some(CStr::from_ptr(key).to_string_lossy()) } else { None }; let type_ = if !type_.is_null() { Some(CStr::from_ptr(type_).to_string_lossy()) } else { None }; let value = if !value.is_null() { Some(CStr::from_ptr(value).to_string_lossy()) } else { None }; callbacks.property.as_ref().unwrap()( subject, key.as_deref(), type_.as_deref(), value.as_deref(), ) } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_METADATA_EVENTS; if self.cbs.property.is_some() { e.property = Some(metadata_events_property); } e }; let (listener, data) = unsafe { let metadata = &self.metadata.proxy.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa_interface_call_method!( metadata, pw_sys::pw_metadata_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; MetadataListener { events: e, listener, data, } } } pipewire-0.8.0/src/module.rs000064400000000000000000000145551046102023000141110ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use bitflags::bitflags; use libc::c_void; use std::ops::Deref; use std::pin::Pin; use std::{ffi::CStr, ptr}; use std::{fmt, mem}; use crate::{ proxy::{Listener, Proxy, ProxyT}, types::ObjectType, }; use spa::spa_interface_call_method; #[derive(Debug)] pub struct Module { proxy: Proxy, } impl ProxyT for Module { fn type_() -> ObjectType { ObjectType::Module } fn upcast(self) -> Proxy { self.proxy } fn upcast_ref(&self) -> &Proxy { &self.proxy } unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self where Self: Sized, { Self { proxy } } } impl Module { // TODO: add non-local version when we'll bind pw_thread_loop_start() #[must_use] pub fn add_listener_local(&self) -> ModuleListenerLocalBuilder { ModuleListenerLocalBuilder { module: self, cbs: ListenerLocalCallbacks::default(), } } } #[derive(Default)] struct ListenerLocalCallbacks { #[allow(clippy::type_complexity)] info: Option>, } pub struct ModuleListenerLocalBuilder<'a> { module: &'a Module, cbs: ListenerLocalCallbacks, } #[repr(transparent)] pub struct ModuleInfoRef(pw_sys::pw_module_info); impl ModuleInfoRef { pub fn as_raw(&self) -> &pw_sys::pw_module_info { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_module_info { std::ptr::addr_of!(self.0).cast_mut() } pub fn id(&self) -> u32 { self.0.id } pub fn name(&self) -> &str { unsafe { CStr::from_ptr(self.0.name).to_str().unwrap() } } pub fn filename(&self) -> &str { unsafe { CStr::from_ptr(self.0.name).to_str().unwrap() } } pub fn args(&self) -> Option<&str> { let args = self.0.args; if args.is_null() { None } else { Some(unsafe { CStr::from_ptr(args).to_str().unwrap() }) } } pub fn change_mask(&self) -> ModuleChangeMask { ModuleChangeMask::from_bits(self.0.change_mask).expect("invalid change_mask") } pub fn props(&self) -> Option<&spa::utils::dict::DictRef> { let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast(); ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() }) } } impl fmt::Debug for ModuleInfoRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ModuleInfoRef") .field("id", &self.id()) .field("filename", &self.filename()) .field("args", &self.args()) .field("change_mask", &self.change_mask()) .field("props", &self.props()) .finish() } } pub struct ModuleInfo { ptr: ptr::NonNull, } impl ModuleInfo { pub fn new(ptr: ptr::NonNull) -> Self { Self { ptr } } pub fn from_raw(raw: *mut pw_sys::pw_module_info) -> Self { Self { ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"), } } pub fn into_raw(self) -> *mut pw_sys::pw_module_info { std::mem::ManuallyDrop::new(self).ptr.as_ptr() } } impl Drop for ModuleInfo { fn drop(&mut self) { unsafe { pw_sys::pw_module_info_free(self.ptr.as_ptr()) } } } impl std::ops::Deref for ModuleInfo { type Target = ModuleInfoRef; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for ModuleInfo { fn as_ref(&self) -> &ModuleInfoRef { self.deref() } } bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct ModuleChangeMask: u64 { const PROPS = pw_sys::PW_MODULE_CHANGE_MASK_PROPS as u64; } } impl fmt::Debug for ModuleInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ModuleInfo") .field("id", &self.id()) .field("filename", &self.filename()) .field("args", &self.args()) .field("change_mask", &self.change_mask()) .field("props", &self.props()) .finish() } } pub struct ModuleListener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener for ModuleListener {} impl Drop for ModuleListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } impl<'a> ModuleListenerLocalBuilder<'a> { #[must_use] pub fn info(mut self, info: F) -> Self where F: Fn(&ModuleInfoRef) + 'static, { self.cbs.info = Some(Box::new(info)); self } #[must_use] pub fn register(self) -> ModuleListener { unsafe extern "C" fn module_events_info( data: *mut c_void, info: *const pw_sys::pw_module_info, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let info = ptr::NonNull::new(info as *mut pw_sys::pw_module_info).expect("info is NULL"); let info = info.cast::().as_ref(); callbacks.info.as_ref().unwrap()(info); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_MODULE_EVENTS; if self.cbs.info.is_some() { e.info = Some(module_events_info); } e }; let (listener, data) = unsafe { let module = &self.module.proxy.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa_interface_call_method!( module, pw_sys::pw_module_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; ModuleListener { events: e, listener, data, } } } pipewire-0.8.0/src/node.rs000064400000000000000000000263301046102023000135430ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use bitflags::bitflags; use libc::c_void; use std::ops::Deref; use std::pin::Pin; use std::{ffi::CStr, ptr}; use std::{fmt, mem}; use crate::{ proxy::{Listener, Proxy, ProxyT}, types::ObjectType, }; use spa::{pod::Pod, spa_interface_call_method}; #[derive(Debug)] pub struct Node { proxy: Proxy, } impl Node { // TODO: add non-local version when we'll bind pw_thread_loop_start() #[must_use] pub fn add_listener_local(&self) -> NodeListenerLocalBuilder { NodeListenerLocalBuilder { node: self, cbs: ListenerLocalCallbacks::default(), } } /// Subscribe to parameter changes /// /// Automatically emit `param` events for the given ids when they are changed // FIXME: Return result? pub fn subscribe_params(&self, ids: &[spa::param::ParamType]) { unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_node_methods, subscribe_params, ids.as_ptr() as *mut _, ids.len().try_into().unwrap() ); } } /// Enumerate node parameters /// /// Start enumeration of node parameters. For each param, a /// param event will be emitted. /// /// # Parameters /// `seq`: a sequence number to place in the reply \ /// `id`: the parameter id to enum, or [`None`] to allow any id \ /// `start`: the start index or 0 for the first param \ /// `num`: the maximum number of params to retrieve ([`u32::MAX`] may be used to retrieve all params) // FIXME: Add filter parameter // FIXME: Return result? pub fn enum_params(&self, seq: i32, id: Option, start: u32, num: u32) { let id = id.map(|id| id.as_raw()).unwrap_or(crate::constants::ID_ANY); unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_node_methods, enum_params, seq, id, start, num, std::ptr::null() ); } } pub fn set_param(&self, id: spa::param::ParamType, flags: u32, param: &Pod) { unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_node_methods, set_param, id.as_raw(), flags, param.as_raw_ptr() ); } } } impl ProxyT for Node { fn type_() -> ObjectType { ObjectType::Node } fn upcast(self) -> Proxy { self.proxy } fn upcast_ref(&self) -> &Proxy { &self.proxy } unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self where Self: Sized, { Self { proxy } } } #[derive(Default)] struct ListenerLocalCallbacks { #[allow(clippy::type_complexity)] info: Option>, #[allow(clippy::type_complexity)] param: Option)>>, } pub struct NodeListenerLocalBuilder<'a> { node: &'a Node, cbs: ListenerLocalCallbacks, } #[repr(transparent)] pub struct NodeInfoRef(pw_sys::pw_node_info); impl NodeInfoRef { pub fn as_raw(&self) -> &pw_sys::pw_node_info { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_node_info { std::ptr::addr_of!(self.0).cast_mut() } pub fn id(&self) -> u32 { self.0.id } pub fn max_input_ports(&self) -> u32 { self.0.max_input_ports } pub fn max_output_ports(&self) -> u32 { self.0.max_output_ports } pub fn change_mask(&self) -> NodeChangeMask { NodeChangeMask::from_bits_retain(self.0.change_mask) } pub fn n_input_ports(&self) -> u32 { self.0.n_input_ports } pub fn n_output_ports(&self) -> u32 { self.0.n_output_ports } pub fn state(&self) -> NodeState { let raw_state = self.0.state; match raw_state { pw_sys::pw_node_state_PW_NODE_STATE_ERROR => { let error = unsafe { let error = self.0.error; CStr::from_ptr(error).to_str().unwrap() }; NodeState::Error(error) } pw_sys::pw_node_state_PW_NODE_STATE_CREATING => NodeState::Creating, pw_sys::pw_node_state_PW_NODE_STATE_SUSPENDED => NodeState::Suspended, pw_sys::pw_node_state_PW_NODE_STATE_IDLE => NodeState::Idle, pw_sys::pw_node_state_PW_NODE_STATE_RUNNING => NodeState::Running, _ => panic!("Invalid node state: {}", raw_state), } } pub fn props(&self) -> Option<&spa::utils::dict::DictRef> { let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast(); ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() }) } /// Get the param infos for the node. pub fn params(&self) -> &[spa::param::ParamInfo] { unsafe { let params_ptr = self.0.params; if params_ptr.is_null() { &[] } else { std::slice::from_raw_parts( params_ptr as *const _, self.0.n_params.try_into().unwrap(), ) } } } } impl fmt::Debug for NodeInfoRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("NodeInfoRef") .field("id", &self.id()) .field("max-input-ports", &self.max_input_ports()) .field("max-output-ports", &self.max_output_ports()) .field("change-mask", &self.change_mask()) .field("n-input-ports", &self.n_input_ports()) .field("n-output-ports", &self.n_output_ports()) .field("state", &self.state()) .field("props", &self.props()) .field("params", &self.params()) .finish() } } pub struct NodeInfo { ptr: ptr::NonNull, } impl NodeInfo { pub fn new(ptr: ptr::NonNull) -> Self { Self { ptr } } /// Create a `NodeInfo` from a raw `pw_sys::pw_node_info`. /// /// # Safety /// `ptr` must point to a valid, well aligned `pw_sys::pw_node_info`. pub fn from_raw(raw: *mut pw_sys::pw_node_info) -> Self { Self { ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"), } } pub fn into_raw(self) -> *mut pw_sys::pw_node_info { std::mem::ManuallyDrop::new(self).ptr.as_ptr() } } impl Drop for NodeInfo { fn drop(&mut self) { unsafe { pw_sys::pw_node_info_free(self.ptr.as_ptr()) } } } impl std::ops::Deref for NodeInfo { type Target = NodeInfoRef; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for NodeInfo { fn as_ref(&self) -> &NodeInfoRef { self.deref() } } bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct NodeChangeMask: u64 { const INPUT_PORTS = pw_sys::PW_NODE_CHANGE_MASK_INPUT_PORTS as u64; const OUTPUT_PORTS = pw_sys::PW_NODE_CHANGE_MASK_OUTPUT_PORTS as u64; const STATE = pw_sys::PW_NODE_CHANGE_MASK_STATE as u64; const PROPS = pw_sys::PW_NODE_CHANGE_MASK_PROPS as u64; const PARAMS = pw_sys::PW_NODE_CHANGE_MASK_PARAMS as u64; } } #[derive(Debug)] pub enum NodeState<'a> { Error(&'a str), Creating, Suspended, Idle, Running, } impl fmt::Debug for NodeInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("NodeInfo") .field("id", &self.id()) .field("max-input-ports", &self.max_input_ports()) .field("max-output-ports", &self.max_output_ports()) .field("change-mask", &self.change_mask()) .field("n-input-ports", &self.n_input_ports()) .field("n-output-ports", &self.n_output_ports()) .field("state", &self.state()) .field("props", &self.props()) .field("params", &self.params()) .finish() } } pub struct NodeListener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener for NodeListener {} impl Drop for NodeListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } impl<'a> NodeListenerLocalBuilder<'a> { #[must_use] pub fn info(mut self, info: F) -> Self where F: Fn(&NodeInfoRef) + 'static, { self.cbs.info = Some(Box::new(info)); self } #[must_use] pub fn param(mut self, param: F) -> Self where F: Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>) + 'static, { self.cbs.param = Some(Box::new(param)); self } #[must_use] pub fn register(self) -> NodeListener { unsafe extern "C" fn node_events_info( data: *mut c_void, info: *const pw_sys::pw_node_info, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let info = ptr::NonNull::new(info as *mut pw_sys::pw_node_info).expect("info is NULL"); let info = info.cast::().as_ref(); callbacks.info.as_ref().unwrap()(info); } unsafe extern "C" fn node_events_param( data: *mut c_void, seq: i32, id: u32, index: u32, next: u32, param: *const spa_sys::spa_pod, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let id = spa::param::ParamType::from_raw(id); let param = if !param.is_null() { unsafe { Some(Pod::from_raw(param)) } } else { None }; callbacks.param.as_ref().unwrap()(seq, id, index, next, param); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_NODE_EVENTS; if self.cbs.info.is_some() { e.info = Some(node_events_info); } if self.cbs.param.is_some() { e.param = Some(node_events_param); } e }; let (listener, data) = unsafe { let node = &self.node.proxy.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa_interface_call_method!( node, pw_sys::pw_node_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; NodeListener { events: e, listener, data, } } } pipewire-0.8.0/src/permissions.rs000064400000000000000000000017211046102023000151660ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use bitflags::bitflags; use std::fmt; bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct PermissionFlags: u32 { const R = pw_sys::PW_PERM_R; const W = pw_sys::PW_PERM_W; const X = pw_sys::PW_PERM_X; const M = pw_sys::PW_PERM_M; #[cfg(feature = "v0_3_77")] const L = pw_sys::PW_PERM_L; } } #[repr(transparent)] pub struct Permission(pw_sys::pw_permission); impl Permission { pub fn id(&self) -> u32 { self.0.id } pub fn permission_flags(&self) -> PermissionFlags { PermissionFlags::from_bits_retain(self.0.permissions) } } impl fmt::Debug for Permission { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Permission") .field("id", &self.id()) .field("permission_flags", &self.permission_flags()) .finish() } } pipewire-0.8.0/src/port.rs000064400000000000000000000215521046102023000136030ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use bitflags::bitflags; use libc::c_void; use std::ops::Deref; use std::{fmt, mem}; use std::{pin::Pin, ptr}; use crate::{ proxy::{Listener, Proxy, ProxyT}, spa::utils::Direction, types::ObjectType, }; use spa::{pod::Pod, spa_interface_call_method}; #[derive(Debug)] pub struct Port { proxy: Proxy, } impl Port { // TODO: add non-local version when we'll bind pw_thread_loop_start() #[must_use] pub fn add_listener_local(&self) -> PortListenerLocalBuilder { PortListenerLocalBuilder { port: self, cbs: ListenerLocalCallbacks::default(), } } /// Subscribe to parameter changes /// /// Automatically emit `param` events for the given ids when they are changed // FIXME: Return result? pub fn subscribe_params(&self, ids: &[spa::param::ParamType]) { unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_port_methods, subscribe_params, ids.as_ptr() as *mut _, ids.len().try_into().unwrap() ); } } /// Enumerate node parameters /// /// Start enumeration of node parameters. For each param, a /// param event will be emitted. /// /// # Parameters /// `seq`: a sequence number to place in the reply \ /// `id`: the parameter id to enum, or [`None`] to allow any id \ /// `start`: the start index or 0 for the first param \ /// `num`: the maximum number of params to retrieve ([`u32::MAX`] may be used to retrieve all params) // FIXME: Add filter parameter // FIXME: Return result? pub fn enum_params(&self, seq: i32, id: Option, start: u32, num: u32) { let id = id.map(|id| id.as_raw()).unwrap_or(crate::constants::ID_ANY); unsafe { spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_node_methods, enum_params, seq, id, start, num, std::ptr::null() ); } } } impl ProxyT for Port { fn type_() -> ObjectType { ObjectType::Port } fn upcast(self) -> Proxy { self.proxy } fn upcast_ref(&self) -> &Proxy { &self.proxy } unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self where Self: Sized, { Self { proxy } } } #[derive(Default)] struct ListenerLocalCallbacks { #[allow(clippy::type_complexity)] info: Option>, #[allow(clippy::type_complexity)] param: Option)>>, } pub struct PortListenerLocalBuilder<'a> { port: &'a Port, cbs: ListenerLocalCallbacks, } #[repr(transparent)] pub struct PortInfoRef(pw_sys::pw_port_info); impl PortInfoRef { pub fn as_raw(&self) -> &pw_sys::pw_port_info { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_port_info { std::ptr::addr_of!(self.0).cast_mut() } pub fn id(&self) -> u32 { self.0.id } pub fn direction(&self) -> Direction { Direction::from_raw(self.0.direction) } pub fn change_mask(&self) -> PortChangeMask { PortChangeMask::from_bits_retain(self.0.change_mask) } pub fn props(&self) -> Option<&spa::utils::dict::DictRef> { let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast(); ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() }) } /// Get the param infos for the port. pub fn params(&self) -> &[spa::param::ParamInfo] { let params = self.0.params; if params.is_null() { &[] } else { unsafe { std::slice::from_raw_parts(params as *const _, self.0.n_params.try_into().unwrap()) } } } } impl fmt::Debug for PortInfoRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PortInfoRef") .field("id", &self.id()) .field("direction", &self.direction()) .field("change-mask", &self.change_mask()) .field("props", &self.props()) .field("params", &self.params()) .finish() } } pub struct PortInfo { ptr: ptr::NonNull, } impl PortInfo { pub fn new(ptr: ptr::NonNull) -> Self { Self { ptr } } pub fn from_raw(raw: *mut pw_sys::pw_port_info) -> Self { Self { ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"), } } pub fn into_raw(self) -> *mut pw_sys::pw_port_info { std::mem::ManuallyDrop::new(self).ptr.as_ptr() } } impl Drop for PortInfo { fn drop(&mut self) { unsafe { pw_sys::pw_port_info_free(self.ptr.as_ptr()) } } } impl std::ops::Deref for PortInfo { type Target = PortInfoRef; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for PortInfo { fn as_ref(&self) -> &PortInfoRef { self.deref() } } bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct PortChangeMask: u64 { const PROPS = pw_sys::PW_PORT_CHANGE_MASK_PROPS as u64; const PARAMS = pw_sys::PW_PORT_CHANGE_MASK_PARAMS as u64; } } impl fmt::Debug for PortInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PortInfo") .field("id", &self.id()) .field("direction", &self.direction()) .field("change-mask", &self.change_mask()) .field("props", &self.props()) .field("params", &self.params()) .finish() } } pub struct PortListener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener for PortListener {} impl Drop for PortListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } impl<'a> PortListenerLocalBuilder<'a> { #[must_use] pub fn info(mut self, info: F) -> Self where F: Fn(&PortInfoRef) + 'static, { self.cbs.info = Some(Box::new(info)); self } #[must_use] pub fn param(mut self, param: F) -> Self where F: Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>) + 'static, { self.cbs.param = Some(Box::new(param)); self } #[must_use] pub fn register(self) -> PortListener { unsafe extern "C" fn port_events_info( data: *mut c_void, info: *const pw_sys::pw_port_info, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let info = ptr::NonNull::new(info as *mut pw_sys::pw_port_info).expect("info is NULL"); let info = info.cast::().as_ref(); callbacks.info.as_ref().unwrap()(info); } unsafe extern "C" fn port_events_param( data: *mut c_void, seq: i32, id: u32, index: u32, next: u32, param: *const spa_sys::spa_pod, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let id = spa::param::ParamType::from_raw(id); let param = if !param.is_null() { unsafe { Some(Pod::from_raw(param)) } } else { None }; callbacks.param.as_ref().unwrap()(seq, id, index, next, param); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_PORT_EVENTS; if self.cbs.info.is_some() { e.info = Some(port_events_info); } if self.cbs.param.is_some() { e.param = Some(port_events_param); } e }; let (listener, data) = unsafe { let port = &self.port.proxy.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa_interface_call_method!( port, pw_sys::pw_port_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; PortListener { events: e, listener, data, } } } pipewire-0.8.0/src/properties.rs000064400000000000000000000223201046102023000150050ustar 00000000000000use std::{ffi::CString, fmt, mem::ManuallyDrop, ops::Deref, ptr}; /// A collection of key/value pairs. /// /// # Examples /// Create a `Properties` struct and access the stored values by key: /// ```rust /// use pipewire::{properties::{properties, Properties}}; /// /// let props = properties!{ /// "Key" => "Value", /// "OtherKey" => "OtherValue" /// }; /// /// assert_eq!(Some("Value"), props.get("Key")); /// assert_eq!(Some("OtherValue"), props.get("OtherKey")); /// ``` pub struct Properties { ptr: ptr::NonNull, } /// A macro for creating a new `Properties` struct with predefined key-value pairs. /// /// The macro accepts a list of `Key => Value` pairs, separated by commas. /// /// # Examples: /// Create a `Properties` struct from literals. /// ```rust /// use pipewire::properties::properties; /// /// let props = properties!{ /// "Key1" => "Value1", /// "Key2" => "Value2", /// }; /// ``` /// /// Any expression that evaluates to a `impl Into>` can be used for both keys and values. /// ```rust /// use pipewire::prelude::*; /// use pipewire::properties::properties; /// /// let key = String::from("Key"); /// let value = vec![86, 97, 108, 117, 101]; // "Value" as an ASCII u8 vector. /// let props = properties!{ /// key => value /// }; /// /// assert_eq!(Some("Value"), props.get("Key")); /// ``` #[macro_export] macro_rules! __properties__ { {$($k:expr => $v:expr),+ $(,)?} => {{ let mut properties = $crate::properties::Properties::new(); $( properties.insert($k, $v); )* properties }}; } pub use __properties__ as properties; impl Properties { /// Create a new, initially empty `Properties` struct. pub fn new() -> Self { unsafe { let raw = std::ptr::NonNull::new(pw_sys::pw_properties_new(std::ptr::null())) .expect("Newly created pw_properties should not be null"); Self::from_ptr(raw) } } /// Create a `Properties` struct from an existing raw `pw_properties` pointer. /// /// # Safety /// - The provided pointer must point to a valid, well-aligned `pw_properties` struct. /// - After this call, the generated `Properties` struct will assume ownership of the data pointed to, /// so that data must not be freed elsewhere. pub unsafe fn from_ptr(ptr: ptr::NonNull) -> Self { Self { ptr } } /// Consume the `Properties` struct, returning a pointer to the raw `pw_properties` struct. /// /// After this function, the caller is responsible for `pw_properties` struct, /// and should make sure it is freed when it is no longer needed. pub fn into_raw(self) -> *mut pw_sys::pw_properties { let this = ManuallyDrop::new(self); this.ptr.as_ptr() } // TODO: `fn from_string` that calls `pw_sys::pw_properties_new_string` // TODO: bindings for pw_properties_update_keys, pw_properties_update, pw_properties_add, pw_properties_add_keys /// Create a new `Properties` from a given dictionary. /// /// All the keys and values from `dict` are copied. pub fn from_dict(dict: &spa::utils::dict::DictRef) -> Self { let ptr = dict.as_raw(); unsafe { let copy = pw_sys::pw_properties_new_dict(ptr); Self::from_ptr(ptr::NonNull::new(copy).expect("pw_properties_new_dict() returned NULL")) } } } impl AsRef for Properties { fn as_ref(&self) -> &PropertiesRef { self.deref() } } impl AsRef for Properties { fn as_ref(&self) -> &spa::utils::dict::DictRef { self.deref().as_ref() } } impl std::ops::Deref for Properties { type Target = PropertiesRef; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast().as_ref() } } } impl std::ops::DerefMut for Properties { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { self.ptr.cast().as_mut() } } } impl Default for Properties { fn default() -> Self { Self::new() } } impl Clone for Properties { fn clone(&self) -> Self { unsafe { let ptr = pw_sys::pw_properties_copy(self.as_raw_ptr()); let ptr = ptr::NonNull::new_unchecked(ptr); Self { ptr } } } } impl Drop for Properties { fn drop(&mut self) { unsafe { pw_sys::pw_properties_free(self.ptr.as_ptr()) } } } impl fmt::Debug for Properties { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let dict: &spa::utils::dict::DictRef = self.as_ref(); // FIXME: Debug-print dict keys and values directly f.debug_tuple("Properties").field(dict).finish() } } #[repr(transparent)] pub struct PropertiesRef(pw_sys::pw_properties); impl PropertiesRef { pub fn as_raw(&self) -> &pw_sys::pw_properties { &self.0 } /// Obtain a pointer to the underlying `pw_properties` struct. /// /// The pointer is only valid for the lifetime of the [`PropertiesRef`] struct the pointer was obtained from, /// and must not be dereferenced after it is dropped. /// /// Ownership of the `pw_properties` struct is not transferred to the caller and must not be manually freed. pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_properties { std::ptr::addr_of!(self.0).cast_mut() } pub fn dict(&self) -> &spa::utils::dict::DictRef { unsafe { &*(&self.0.dict as *const spa_sys::spa_dict as *const spa::utils::dict::DictRef) } } // TODO: Impl as trait? pub fn to_owned(&self) -> Properties { unsafe { let ptr = pw_sys::pw_properties_copy(self.as_raw_ptr()); Properties::from_ptr(ptr::NonNull::new_unchecked(ptr)) } } pub fn get(&self, key: &str) -> Option<&str> { let key = CString::new(key).expect("key contains null byte"); let res = unsafe { pw_sys::pw_properties_get(self.as_raw_ptr().cast_const(), key.as_ptr()) }; let res = if !res.is_null() { unsafe { Some(std::ffi::CStr::from_ptr(res)) } } else { None }; // FIXME: Don't return `None` if result is non-utf8 res.and_then(|res| res.to_str().ok()) } pub fn insert(&mut self, key: K, value: V) where K: Into>, V: Into>, { let k = CString::new(key).unwrap(); let v = CString::new(value).unwrap(); unsafe { pw_sys::pw_properties_set(self.as_raw_ptr(), k.as_ptr(), v.as_ptr()) }; } pub fn remove(&mut self, key: T) where T: Into>, { let key = CString::new(key).unwrap(); unsafe { pw_sys::pw_properties_set(self.as_raw_ptr(), key.as_ptr(), std::ptr::null()) }; } pub fn clear(&mut self) { unsafe { pw_sys::pw_properties_clear(self.as_raw_ptr()) } } } impl AsRef for PropertiesRef { fn as_ref(&self) -> &spa::utils::dict::DictRef { self.dict() } } impl fmt::Debug for PropertiesRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // FIXME: Debug-print dict key and values directly f.debug_tuple("PropertiesRef").field(self.as_ref()).finish() } } #[cfg(test)] mod tests { use super::*; #[test] fn new() { let props = properties! { "K0" => "V0" }; let mut iter = props.dict().iter(); assert_eq!(("K0", "V0"), iter.next().unwrap()); assert_eq!(None, iter.next()); } #[test] fn remove() { let mut props = properties! { "K0" => "V0" }; assert_eq!(Some("V0"), props.dict().get("K0")); props.remove("K0"); assert_eq!(None, props.dict().get("K0")); } #[test] fn insert() { let mut props = properties! { "K0" => "V0" }; assert_eq!(None, props.dict().get("K1")); props.insert("K1", "V1"); assert_eq!(Some("V1"), props.dict().get("K1")); } #[test] fn clone() { let props1 = properties! { "K0" => "V0" }; let mut props2 = props1.clone(); props2.insert("K1", "V1"); // Now, props2 should contain ("K1", "V1"), but props1 should not. assert_eq!(None, props1.dict().get("K1")); assert_eq!(Some("V1"), props2.dict().get("K1")); } #[test] fn from_dict() { use spa::static_dict; let mut props = { let dict = static_dict! { "K0" => "V0" }; Properties::from_dict(&dict) }; assert_eq!(props.dict().len(), 1); assert_eq!(props.dict().get("K0"), Some("V0")); props.insert("K1", "V1"); assert_eq!(props.dict().len(), 2); assert_eq!(props.dict().get("K1"), Some("V1")); } #[test] fn properties_ref() { let props = properties! { "K0" => "V0" }; println!("{:?}", &props); let props_ref: &PropertiesRef = props.deref(); assert_eq!(props_ref.dict().len(), 1); assert_eq!(props_ref.dict().get("K0"), Some("V0")); dbg!(&props_ref); let props_copy = props_ref.to_owned(); assert_eq!(props_copy.dict().len(), 1); assert_eq!(props_copy.dict().get("K0"), Some("V0")); } } pipewire-0.8.0/src/proxy.rs000064400000000000000000000172251046102023000140020ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use libc::{c_char, c_void}; use std::fmt; use std::mem; use std::pin::Pin; use std::{ffi::CStr, ptr}; use crate::{types::ObjectType, Error}; pub struct Proxy { ptr: ptr::NonNull, } // Wrapper around a proxy pointer impl Proxy { pub(crate) fn new(ptr: ptr::NonNull) -> Self { Proxy { ptr } } pub(crate) fn as_ptr(&self) -> *mut pw_sys::pw_proxy { self.ptr.as_ptr() } pub fn add_listener_local(&self) -> ProxyListenerLocalBuilder { ProxyListenerLocalBuilder { proxy: self, cbs: ListenerLocalCallbacks::default(), } } pub fn id(&self) -> u32 { unsafe { pw_sys::pw_proxy_get_id(self.as_ptr()) } } /// Get the type of the proxy as well as it's version. pub fn get_type(&self) -> (ObjectType, u32) { unsafe { let mut version = 0; let proxy_type = pw_sys::pw_proxy_get_type(self.as_ptr(), &mut version); let proxy_type = CStr::from_ptr(proxy_type); ( ObjectType::from_str(proxy_type.to_str().expect("invalid proxy type")), version, ) } } /// Attempt to downcast the proxy to the provided type. /// /// The downcast will fail if the type that the proxy represents does not match the provided type. \ /// In that case, the function returns `(self, Error::WrongProxyType)` so that the proxy is not lost. pub(crate) fn downcast(self) -> Result { // Make sure the proxy we got has the type that is requested if P::type_() == self.get_type().0 { unsafe { Ok(P::from_proxy_unchecked(self)) } } else { Err((self, Error::WrongProxyType)) } } } impl Drop for Proxy { fn drop(&mut self) { unsafe { pw_sys::pw_proxy_destroy(self.as_ptr()); } } } impl fmt::Debug for Proxy { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (proxy_type, version) = self.get_type(); f.debug_struct("Proxy") .field("id", &self.id()) .field("type", &proxy_type) .field("version", &version) .finish() } } // Trait implemented by high level proxy wrappers pub trait ProxyT { // Add Sized restriction on those methods so it can be used as a // trait object, see E0038 fn type_() -> ObjectType where Self: Sized; fn upcast(self) -> Proxy; fn upcast_ref(&self) -> &Proxy; /// Downcast the provided proxy to `Self` without checking that the type matches. /// /// This function should not be used by applications. /// If you really do need a way to downcast a proxy to it's type, please open an issue. /// /// # Safety /// It must be manually ensured that the provided proxy is actually a proxy representing the created type. \ /// Otherwise, undefined behaviour may occur. unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self where Self: Sized; } // Trait implemented by listener on high level proxy wrappers. pub trait Listener {} pub struct ProxyListener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Listener for ProxyListener {} impl Drop for ProxyListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } #[derive(Default)] struct ListenerLocalCallbacks { destroy: Option>, bound: Option>, removed: Option>, done: Option>, #[allow(clippy::type_complexity)] error: Option>, // TODO: return a proper Error enum? } pub struct ProxyListenerLocalBuilder<'a> { proxy: &'a Proxy, cbs: ListenerLocalCallbacks, } impl<'a> ProxyListenerLocalBuilder<'a> { #[must_use] pub fn destroy(mut self, destroy: F) -> Self where F: Fn() + 'static, { self.cbs.destroy = Some(Box::new(destroy)); self } #[must_use] pub fn bound(mut self, bound: F) -> Self where F: Fn(u32) + 'static, { self.cbs.bound = Some(Box::new(bound)); self } #[must_use] pub fn removed(mut self, removed: F) -> Self where F: Fn() + 'static, { self.cbs.removed = Some(Box::new(removed)); self } #[must_use] pub fn done(mut self, done: F) -> Self where F: Fn(i32) + 'static, { self.cbs.done = Some(Box::new(done)); self } #[must_use] pub fn error(mut self, error: F) -> Self where F: Fn(i32, i32, &str) + 'static, { self.cbs.error = Some(Box::new(error)); self } #[must_use] pub fn register(self) -> ProxyListener { unsafe extern "C" fn proxy_destroy(data: *mut c_void) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); callbacks.destroy.as_ref().unwrap()(); } unsafe extern "C" fn proxy_bound(data: *mut c_void, global_id: u32) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); callbacks.bound.as_ref().unwrap()(global_id); } unsafe extern "C" fn proxy_removed(data: *mut c_void) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); callbacks.removed.as_ref().unwrap()(); } unsafe extern "C" fn proxy_done(data: *mut c_void, seq: i32) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); callbacks.done.as_ref().unwrap()(seq); } unsafe extern "C" fn proxy_error( data: *mut c_void, seq: i32, res: i32, message: *const c_char, ) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); let message = CStr::from_ptr(message).to_str().unwrap(); callbacks.error.as_ref().unwrap()(seq, res, message); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_PROXY_EVENTS; if self.cbs.destroy.is_some() { e.destroy = Some(proxy_destroy); } if self.cbs.bound.is_some() { e.bound = Some(proxy_bound); } if self.cbs.removed.is_some() { e.removed = Some(proxy_removed); } if self.cbs.done.is_some() { e.done = Some(proxy_done); } if self.cbs.error.is_some() { e.error = Some(proxy_error); } e }; let (listener, data) = unsafe { let proxy = &self.proxy.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); let funcs: *const pw_sys::pw_proxy_events = e.as_ref().get_ref(); pw_sys::pw_proxy_add_listener( proxy.cast(), listener_ptr.cast(), funcs.cast(), data as *mut _, ); (listener, Box::from_raw(data)) }; ProxyListener { events: e, listener, data, } } } pipewire-0.8.0/src/registry.rs000064400000000000000000000165521046102023000144730ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use libc::{c_char, c_void}; use std::{ ffi::{CStr, CString}, mem, pin::Pin, ptr, }; use crate::{ permissions::PermissionFlags, properties::Properties, proxy::{Proxy, ProxyT}, types::ObjectType, Error, }; #[derive(Debug)] pub struct Registry { ptr: ptr::NonNull, } impl Registry { pub(crate) fn new(ptr: ptr::NonNull) -> Self { Registry { ptr } } fn as_ptr(&self) -> *mut pw_sys::pw_registry { self.ptr.as_ptr() } // TODO: add non-local version when we'll bind pw_thread_loop_start() #[must_use] pub fn add_listener_local(&self) -> ListenerLocalBuilder { ListenerLocalBuilder { registry: self, cbs: ListenerLocalCallbacks::default(), } } pub fn bind>( &self, object: &GlobalObject

, ) -> Result { let proxy = unsafe { let type_ = CString::new(object.type_.to_str()).unwrap(); let version = object.type_.client_version(); let proxy = spa::spa_interface_call_method!( self.as_ptr(), pw_sys::pw_registry_methods, bind, object.id, type_.as_ptr(), version, 0 ); proxy }; let proxy = ptr::NonNull::new(proxy.cast()).ok_or(Error::NoMemory)?; Proxy::new(proxy).downcast().map_err(|(_, e)| e) } /// Attempt to destroy the global object with the specified id on the remote. pub fn destroy_global(&self, global_id: u32) -> spa::utils::result::SpaResult { let result = unsafe { spa::spa_interface_call_method!( self.as_ptr(), pw_sys::pw_registry_methods, destroy, global_id ) }; spa::utils::result::SpaResult::from_c(result) } } impl Drop for Registry { fn drop(&mut self) { unsafe { pw_sys::pw_proxy_destroy(self.as_ptr().cast()); } } } type GlobalCallback = dyn Fn(&GlobalObject<&spa::utils::dict::DictRef>); type GlobalRemoveCallback = dyn Fn(u32); #[derive(Default)] struct ListenerLocalCallbacks { global: Option>, global_remove: Option>, } pub struct ListenerLocalBuilder<'a> { registry: &'a Registry, cbs: ListenerLocalCallbacks, } pub struct Listener { // Need to stay allocated while the listener is registered #[allow(dead_code)] events: Pin>, listener: Pin>, #[allow(dead_code)] data: Box, } impl Drop for Listener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } impl<'a> ListenerLocalBuilder<'a> { #[must_use] pub fn global(mut self, global: F) -> Self where F: Fn(&GlobalObject<&spa::utils::dict::DictRef>) + 'static, { self.cbs.global = Some(Box::new(global)); self } #[must_use] pub fn global_remove(mut self, global_remove: F) -> Self where F: Fn(u32) + 'static, { self.cbs.global_remove = Some(Box::new(global_remove)); self } #[must_use] pub fn register(self) -> Listener { unsafe extern "C" fn registry_events_global( data: *mut c_void, id: u32, permissions: u32, type_: *const c_char, version: u32, props: *const spa_sys::spa_dict, ) { let type_ = CStr::from_ptr(type_).to_str().unwrap(); let obj = GlobalObject::new(id, permissions, type_, version, props); let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); callbacks.global.as_ref().unwrap()(&obj); } unsafe extern "C" fn registry_events_global_remove(data: *mut c_void, id: u32) { let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap(); callbacks.global_remove.as_ref().unwrap()(id); } let e = unsafe { let mut e: Pin> = Box::pin(mem::zeroed()); e.version = pw_sys::PW_VERSION_REGISTRY_EVENTS; if self.cbs.global.is_some() { e.global = Some(registry_events_global); } if self.cbs.global_remove.is_some() { e.global_remove = Some(registry_events_global_remove); } e }; let (listener, data) = unsafe { let ptr = self.registry.as_ptr(); let data = Box::into_raw(Box::new(self.cbs)); let mut listener: Pin> = Box::pin(mem::zeroed()); let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut(); spa::spa_interface_call_method!( ptr, pw_sys::pw_registry_methods, add_listener, listener_ptr.cast(), e.as_ref().get_ref(), data as *mut _ ); (listener, Box::from_raw(data)) }; Listener { events: e, listener, data, } } } #[derive(Debug)] pub struct GlobalObject> { pub id: u32, pub permissions: PermissionFlags, pub type_: ObjectType, pub version: u32, pub props: Option

, } impl GlobalObject<&spa::utils::dict::DictRef> { unsafe fn new( id: u32, permissions: u32, type_: &str, version: u32, props: *const spa_sys::spa_dict, ) -> Self { let type_ = ObjectType::from_str(type_); let permissions = PermissionFlags::from_bits_retain(permissions); let props = ptr::NonNull::new(props.cast_mut()) .map(|ptr| ptr.cast::().as_ref()); Self { id, permissions, type_, version, props, } } } impl> GlobalObject

{ pub fn to_owned(&self) -> GlobalObject { GlobalObject { id: self.id, permissions: self.permissions, type_: self.type_.clone(), version: self.version, props: self .props .as_ref() .map(|props| Properties::from_dict(props.as_ref())), } } } #[cfg(test)] mod tests { use super::*; #[test] fn set_object_type() { assert_eq!( ObjectType::from_str("PipeWire:Interface:Client"), ObjectType::Client ); assert_eq!(ObjectType::Client.to_str(), "PipeWire:Interface:Client"); assert_eq!(ObjectType::Client.client_version(), 3); let o = ObjectType::Other("PipeWire:Interface:Badger".to_string()); assert_eq!(ObjectType::from_str("PipeWire:Interface:Badger"), o); assert_eq!(o.to_str(), "PipeWire:Interface:Badger"); } #[test] #[should_panic(expected = "Invalid object type")] fn client_version_panic() { let o = ObjectType::Other("PipeWire:Interface:Badger".to_string()); assert_eq!(o.client_version(), 0); } } pipewire-0.8.0/src/stream.rs000064400000000000000000000573041046102023000141160ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT //! Pipewire Stream use crate::buffer::Buffer; use crate::{ core::Core, error::Error, properties::{Properties, PropertiesRef}, }; use bitflags::bitflags; use spa::utils::result::SpaResult; use std::{ ffi::{self, CStr, CString}, fmt::Debug, mem, os, pin::Pin, ptr, }; #[derive(Debug, PartialEq)] pub enum StreamState { Error(String), Unconnected, Connecting, Paused, Streaming, } impl StreamState { pub(crate) fn from_raw(state: pw_sys::pw_stream_state, error: *const os::raw::c_char) -> Self { match state { pw_sys::pw_stream_state_PW_STREAM_STATE_UNCONNECTED => StreamState::Unconnected, pw_sys::pw_stream_state_PW_STREAM_STATE_CONNECTING => StreamState::Connecting, pw_sys::pw_stream_state_PW_STREAM_STATE_PAUSED => StreamState::Paused, pw_sys::pw_stream_state_PW_STREAM_STATE_STREAMING => StreamState::Streaming, _ => { let error = if error.is_null() { "".to_string() } else { unsafe { ffi::CStr::from_ptr(error).to_string_lossy().to_string() } }; StreamState::Error(error) } } } } /// A wrapper around the pipewire stream interface. Streams are a higher /// level abstraction around nodes in the graph. A stream can be used to send or /// receive frames of audio or video data by connecting it to another node. /// `D` is the user data, to allow passing extra context to the callbacks. pub struct Stream { ptr: ptr::NonNull, // objects that need to stay alive while the Stream is _core: Core, } impl Stream { /// Create a [`Stream`] /// /// Initialises a new stream with the given `name` and `properties`. pub fn new(core: &Core, name: &str, properties: Properties) -> Result { let name = CString::new(name).expect("Invalid byte in stream name"); let stream = unsafe { pw_sys::pw_stream_new(core.as_raw_ptr(), name.as_ptr(), properties.into_raw()) }; let stream = ptr::NonNull::new(stream).ok_or(Error::CreationFailed)?; Ok(Stream { ptr: stream, _core: core.clone(), }) } pub fn into_raw(self) -> *mut pw_sys::pw_stream { let mut this = std::mem::ManuallyDrop::new(self); // FIXME: self needs to be wrapped in ManuallyDrop so the raw stream // isn't destroyed. However, the core should still be dropped. // Is there a cleaner and safer way to drop the core than like this? unsafe { ptr::drop_in_place(ptr::addr_of_mut!(this._core)); } this.ptr.as_ptr() } } impl std::ops::Deref for Stream { type Target = StreamRef; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast().as_ref() } } } impl std::fmt::Debug for Stream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Stream") .field("name", &self.name()) .field("state", &self.state()) .field("node-id", &self.node_id()) .field("properties", &self.properties()) .finish() } } impl std::ops::Drop for Stream { fn drop(&mut self) { unsafe { pw_sys::pw_stream_destroy(self.as_raw_ptr()) } } } #[repr(transparent)] pub struct StreamRef(pw_sys::pw_stream); impl StreamRef { pub fn as_raw(&self) -> &pw_sys::pw_stream { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_stream { ptr::addr_of!(self.0).cast_mut() } /// Add a local listener builder #[must_use = "Fluent builder API"] pub fn add_local_listener_with_user_data( &self, user_data: D, ) -> ListenerLocalBuilder<'_, D> { let mut callbacks = ListenerLocalCallbacks::with_user_data(user_data); callbacks.stream = Some(ptr::NonNull::new(self.as_raw_ptr()).expect("Pointer should be nonnull")); ListenerLocalBuilder { stream: self, callbacks, } } /// Add a local listener builder. User data is initialized with its default value #[must_use = "Fluent builder API"] pub fn add_local_listener(&self) -> ListenerLocalBuilder<'_, D> { self.add_local_listener_with_user_data(Default::default()) } /// Connect the stream /// /// Tries to connect to the node `id` in the given `direction`. If no node /// is provided then any suitable node will be used. // FIXME: high-level API for params pub fn connect( &self, direction: spa::utils::Direction, id: Option, flags: StreamFlags, params: &mut [&spa::pod::Pod], ) -> Result<(), Error> { let r = unsafe { pw_sys::pw_stream_connect( self.as_raw_ptr(), direction.as_raw(), id.unwrap_or(crate::constants::ID_ANY), flags.bits(), // We cast from *mut [&spa::pod::Pod] to *mut [*const spa_sys::spa_pod] here, // which is valid because spa::pod::Pod is a transparent wrapper around spa_sys::spa_pod params.as_mut_ptr().cast(), params.len() as u32, ) }; SpaResult::from_c(r).into_sync_result()?; Ok(()) } /// Update Parameters /// /// Call from the `param_changed` callback to negotiate a new set of /// parameters for the stream. // FIXME: high-level API for params pub fn update_params(&self, params: &mut [&spa::pod::Pod]) -> Result<(), Error> { let r = unsafe { pw_sys::pw_stream_update_params( self.as_raw_ptr(), params.as_mut_ptr().cast(), params.len() as u32, ) }; SpaResult::from_c(r).into_sync_result()?; Ok(()) } /// Activate or deactivate the stream pub fn set_active(&self, active: bool) -> Result<(), Error> { let r = unsafe { pw_sys::pw_stream_set_active(self.as_raw_ptr(), active) }; SpaResult::from_c(r).into_sync_result()?; Ok(()) } /// Take a Buffer from the Stream /// /// Removes a buffer from the stream. If this is an input stream the buffer /// will contain data ready to process. If this is an output stream it can /// be filled. /// /// # Safety /// /// The pointer returned could be NULL if no buffer is available. The buffer /// should be returned to the stream once processing is complete. pub unsafe fn dequeue_raw_buffer(&self) -> *mut pw_sys::pw_buffer { pw_sys::pw_stream_dequeue_buffer(self.as_raw_ptr()) } pub fn dequeue_buffer(&self) -> Option { unsafe { Buffer::from_raw(self.dequeue_raw_buffer(), self) } } /// Return a Buffer to the Stream /// /// Give back a buffer once processing is complete. Use this to queue up a /// frame for an output stream, or return the buffer to the pool ready to /// receive new data for an input stream. /// /// # Safety /// /// The buffer pointer should be one obtained from this stream instance by /// a call to [StreamRef::dequeue_raw_buffer()]. pub unsafe fn queue_raw_buffer(&self, buffer: *mut pw_sys::pw_buffer) { pw_sys::pw_stream_queue_buffer(self.as_raw_ptr(), buffer); } /// Disconnect the stream pub fn disconnect(&self) -> Result<(), Error> { let r = unsafe { pw_sys::pw_stream_disconnect(self.as_raw_ptr()) }; SpaResult::from_c(r).into_sync_result()?; Ok(()) } /// Set the stream in error state /// /// # Panics /// Will panic if `error` contains a 0 byte. /// pub fn set_error(&mut self, res: i32, error: &str) { let error = CString::new(error).expect("failed to convert error to CString"); unsafe { pw_sys::pw_stream_set_error(self.as_raw_ptr(), res, error.as_c_str().as_ptr()); } } /// Flush the stream. When `drain` is `true`, the `drained` callback will /// be called when all data is played or recorded. pub fn flush(&self, drain: bool) -> Result<(), Error> { let r = unsafe { pw_sys::pw_stream_flush(self.as_raw_ptr(), drain) }; SpaResult::from_c(r).into_sync_result()?; Ok(()) } pub fn set_control(&self, id: u32, values: &[f32]) -> Result<(), Error> { let r = unsafe { pw_sys::pw_stream_set_control( self.as_raw_ptr(), id, values.len() as u32, values.as_ptr() as *mut f32, ) }; SpaResult::from_c(r).into_sync_result()?; Ok(()) } // getters /// Get the name of the stream. pub fn name(&self) -> String { let name = unsafe { let name = pw_sys::pw_stream_get_name(self.as_raw_ptr()); CStr::from_ptr(name) }; name.to_string_lossy().to_string() } /// Get the current state of the stream. pub fn state(&self) -> StreamState { let mut error: *const std::os::raw::c_char = ptr::null(); let state = unsafe { pw_sys::pw_stream_get_state(self.as_raw_ptr(), (&mut error) as *mut *const _) }; StreamState::from_raw(state, error) } /// Get the properties of the stream. pub fn properties(&self) -> &PropertiesRef { unsafe { let props = pw_sys::pw_stream_get_properties(self.as_raw_ptr()); let props = ptr::NonNull::new(props.cast_mut()).expect("stream properties is NULL"); props.cast().as_ref() } } /// Get the node ID of the stream. pub fn node_id(&self) -> u32 { unsafe { pw_sys::pw_stream_get_node_id(self.as_raw_ptr()) } } #[cfg(feature = "v0_3_34")] pub fn is_driving(&self) -> bool { unsafe { pw_sys::pw_stream_is_driving(self.as_raw_ptr()) } } #[cfg(feature = "v0_3_34")] pub fn trigger_process(&self) -> Result<(), Error> { let r = unsafe { pw_sys::pw_stream_trigger_process(self.as_raw_ptr()) }; SpaResult::from_c(r).into_result()?; Ok(()) } // TODO: pw_stream_get_core() // TODO: pw_stream_get_time() } type ParamChangedCB = dyn FnMut(&StreamRef, &mut D, u32, Option<&spa::pod::Pod>); type ProcessCB = dyn FnMut(&StreamRef, &mut D); #[allow(clippy::type_complexity)] pub struct ListenerLocalCallbacks { pub state_changed: Option>, pub control_info: Option>, pub io_changed: Option>, pub param_changed: Option>>, pub add_buffer: Option>, pub remove_buffer: Option>, pub process: Option>>, pub drained: Option>, #[cfg(feature = "v0_3_39")] pub command: Option>, #[cfg(feature = "v0_3_40")] pub trigger_done: Option>, pub user_data: D, stream: Option>, } unsafe fn unwrap_stream_ptr<'a>(stream: Option>) -> &'a StreamRef { stream .map(|ptr| ptr.cast::().as_ref()) .expect("stream cannot be null") } impl ListenerLocalCallbacks { fn with_user_data(user_data: D) -> Self { ListenerLocalCallbacks { process: Default::default(), stream: Default::default(), drained: Default::default(), add_buffer: Default::default(), control_info: Default::default(), io_changed: Default::default(), param_changed: Default::default(), remove_buffer: Default::default(), state_changed: Default::default(), #[cfg(feature = "v0_3_39")] command: Default::default(), #[cfg(feature = "v0_3_40")] trigger_done: Default::default(), user_data, } } pub(crate) fn into_raw( self, ) -> ( Pin>, Box>, ) { let callbacks = Box::new(self); unsafe extern "C" fn on_state_changed( data: *mut os::raw::c_void, old: pw_sys::pw_stream_state, new: pw_sys::pw_stream_state, error: *const os::raw::c_char, ) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.state_changed { let stream = unwrap_stream_ptr(state.stream); let old = StreamState::from_raw(old, error); let new = StreamState::from_raw(new, error); cb(stream, &mut state.user_data, old, new) }; } } unsafe extern "C" fn on_control_info( data: *mut os::raw::c_void, id: u32, control: *const pw_sys::pw_stream_control, ) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.control_info { let stream = unwrap_stream_ptr(state.stream); cb(stream, &mut state.user_data, id, control); } } } unsafe extern "C" fn on_io_changed( data: *mut os::raw::c_void, id: u32, area: *mut os::raw::c_void, size: u32, ) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.io_changed { let stream = unwrap_stream_ptr(state.stream); cb(stream, &mut state.user_data, id, area, size); } } } unsafe extern "C" fn on_param_changed( data: *mut os::raw::c_void, id: u32, param: *const spa_sys::spa_pod, ) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.param_changed { let stream = unwrap_stream_ptr(state.stream); let param = if !param.is_null() { Some(spa::pod::Pod::from_raw(param)) } else { None }; cb(stream, &mut state.user_data, id, param); } } } unsafe extern "C" fn on_add_buffer( data: *mut ::std::os::raw::c_void, buffer: *mut pw_sys::pw_buffer, ) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.add_buffer { let stream = unwrap_stream_ptr(state.stream); cb(stream, &mut state.user_data, buffer); } } } unsafe extern "C" fn on_remove_buffer( data: *mut ::std::os::raw::c_void, buffer: *mut pw_sys::pw_buffer, ) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.remove_buffer { let stream = unwrap_stream_ptr(state.stream); cb(stream, &mut state.user_data, buffer); } } } unsafe extern "C" fn on_process(data: *mut ::std::os::raw::c_void) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.process { let stream = unwrap_stream_ptr(state.stream); cb(stream, &mut state.user_data); } } } unsafe extern "C" fn on_drained(data: *mut ::std::os::raw::c_void) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.drained { let stream = unwrap_stream_ptr(state.stream); cb(stream, &mut state.user_data); } } } #[cfg(feature = "v0_3_39")] unsafe extern "C" fn on_command( data: *mut ::std::os::raw::c_void, command: *const spa_sys::spa_command, ) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.command { let stream = unwrap_stream_ptr(state.stream); cb(stream, &mut state.user_data, command); } } } #[cfg(feature = "v0_3_40")] unsafe extern "C" fn on_trigger_done(data: *mut ::std::os::raw::c_void) { if let Some(state) = (data as *mut ListenerLocalCallbacks).as_mut() { if let Some(cb) = &mut state.trigger_done { let stream = unwrap_stream_ptr(state.stream); cb(stream, &mut state.user_data); } } } let events = unsafe { let mut events: Pin> = Box::pin(mem::zeroed()); events.version = pw_sys::PW_VERSION_STREAM_EVENTS; if callbacks.state_changed.is_some() { events.state_changed = Some(on_state_changed::); } if callbacks.control_info.is_some() { events.control_info = Some(on_control_info::); } if callbacks.io_changed.is_some() { events.io_changed = Some(on_io_changed::); } if callbacks.param_changed.is_some() { events.param_changed = Some(on_param_changed::); } if callbacks.add_buffer.is_some() { events.add_buffer = Some(on_add_buffer::); } if callbacks.remove_buffer.is_some() { events.remove_buffer = Some(on_remove_buffer::); } if callbacks.process.is_some() { events.process = Some(on_process::); } if callbacks.drained.is_some() { events.drained = Some(on_drained::); } #[cfg(feature = "v0_3_39")] if callbacks.command.is_some() { events.command = Some(on_command::); } #[cfg(feature = "v0_3_40")] if callbacks.trigger_done.is_some() { events.trigger_done = Some(on_trigger_done::); } events }; (events, callbacks) } } #[must_use] pub struct ListenerLocalBuilder<'a, D> { stream: &'a StreamRef, callbacks: ListenerLocalCallbacks, } impl<'a, D> ListenerLocalBuilder<'a, D> { /// Set the callback for the `state_changed` event. pub fn state_changed(mut self, callback: F) -> Self where F: FnMut(&StreamRef, &mut D, StreamState, StreamState) + 'static, { self.callbacks.state_changed = Some(Box::new(callback)); self } /// Set the callback for the `control_info` event. pub fn control_info(mut self, callback: F) -> Self where F: FnMut(&StreamRef, &mut D, u32, *const pw_sys::pw_stream_control) + 'static, { self.callbacks.control_info = Some(Box::new(callback)); self } /// Set the callback for the `io_changed` event. pub fn io_changed(mut self, callback: F) -> Self where F: FnMut(&StreamRef, &mut D, u32, *mut os::raw::c_void, u32) + 'static, { self.callbacks.io_changed = Some(Box::new(callback)); self } /// Set the callback for the `param_changed` event. pub fn param_changed(mut self, callback: F) -> Self where F: FnMut(&StreamRef, &mut D, u32, Option<&spa::pod::Pod>) + 'static, { self.callbacks.param_changed = Some(Box::new(callback)); self } /// Set the callback for the `add_buffer` event. pub fn add_buffer(mut self, callback: F) -> Self where F: FnMut(&StreamRef, &mut D, *mut pw_sys::pw_buffer) + 'static, { self.callbacks.add_buffer = Some(Box::new(callback)); self } /// Set the callback for the `remove_buffer` event. pub fn remove_buffer(mut self, callback: F) -> Self where F: FnMut(&StreamRef, &mut D, *mut pw_sys::pw_buffer) + 'static, { self.callbacks.remove_buffer = Some(Box::new(callback)); self } /// Set the callback for the `process` event. pub fn process(mut self, callback: F) -> Self where F: FnMut(&StreamRef, &mut D) + 'static, { self.callbacks.process = Some(Box::new(callback)); self } /// Set the callback for the `drained` event. pub fn drained(mut self, callback: F) -> Self where F: FnMut(&StreamRef, &mut D) + 'static, { self.callbacks.drained = Some(Box::new(callback)); self } //// Register the Callbacks /// /// Stop building the listener and register it on the stream. Returns a /// `StreamListener` handlle that will un-register the listener on drop. pub fn register(self) -> Result, Error> { let (events, data) = self.callbacks.into_raw(); let (listener, data) = unsafe { let listener: Box = Box::new(mem::zeroed()); let raw_listener = Box::into_raw(listener); let raw_data = Box::into_raw(data); pw_sys::pw_stream_add_listener( self.stream.as_raw_ptr(), raw_listener, events.as_ref().get_ref(), raw_data as *mut _, ); (Box::from_raw(raw_listener), Box::from_raw(raw_data)) }; Ok(StreamListener { listener, _events: events, _data: data, }) } } pub struct StreamListener { listener: Box, // Need to stay allocated while the listener is registered _events: Pin>, _data: Box>, } impl StreamListener { /// Stop the listener from receiving any events /// /// Removes the listener registration and cleans up allocated resources. pub fn unregister(self) { // do nothing, drop will clean up. } } impl std::ops::Drop for StreamListener { fn drop(&mut self) { spa::utils::hook::remove(*self.listener); } } bitflags! { /// Extra flags that can be used in [`Stream::connect()`] #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct StreamFlags: pw_sys::pw_stream_flags { const AUTOCONNECT = pw_sys::pw_stream_flags_PW_STREAM_FLAG_AUTOCONNECT; const INACTIVE = pw_sys::pw_stream_flags_PW_STREAM_FLAG_INACTIVE; const MAP_BUFFERS = pw_sys::pw_stream_flags_PW_STREAM_FLAG_MAP_BUFFERS; const DRIVER = pw_sys::pw_stream_flags_PW_STREAM_FLAG_DRIVER; const RT_PROCESS = pw_sys::pw_stream_flags_PW_STREAM_FLAG_RT_PROCESS; const NO_CONVERT = pw_sys::pw_stream_flags_PW_STREAM_FLAG_NO_CONVERT; const EXCLUSIVE = pw_sys::pw_stream_flags_PW_STREAM_FLAG_EXCLUSIVE; const DONT_RECONNECT = pw_sys::pw_stream_flags_PW_STREAM_FLAG_DONT_RECONNECT; const ALLOC_BUFFERS = pw_sys::pw_stream_flags_PW_STREAM_FLAG_ALLOC_BUFFERS; #[cfg(feature = "v0_3_41")] const TRIGGER = pw_sys::pw_stream_flags_PW_STREAM_FLAG_TRIGGER; } } pipewire-0.8.0/src/thread_loop.rs000064400000000000000000000156171046102023000151240ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ ffi::{CStr, CString}, mem::MaybeUninit, ptr, rc::{Rc, Weak}, }; use crate::{ error::Error, loop_::{IsLoopRc, LoopRef}, }; /// A wrapper around the pipewire threaded loop interface. ThreadLoops are a higher level /// of abstraction around the loop interface. A ThreadLoop can be used to spawn a new thread /// that runs the wrapped loop. #[derive(Debug, Clone)] pub struct ThreadLoop { inner: Rc, } impl ThreadLoop { /// Initialize Pipewire and create a new `ThreadLoop` with the given `name` and optional properties. /// /// # Safety /// TODO pub unsafe fn new( name: Option<&str>, properties: Option<&spa::utils::dict::DictRef>, ) -> Result { let name = name.map(|name| CString::new(name).unwrap()); ThreadLoop::new_cstr(name.as_deref(), properties) } /// Initialize Pipewire and create a new `ThreadLoop` with the given `name` as Cstr /// /// # Safety /// TODO pub unsafe fn new_cstr( name: Option<&CStr>, properties: Option<&spa::utils::dict::DictRef>, ) -> Result { super::init(); unsafe { let props = properties.map_or(ptr::null(), |props| props.as_raw_ptr()); let l = pw_sys::pw_thread_loop_new( name.map_or(ptr::null(), |p| p.as_ptr() as *const _), props, ); let ptr = ptr::NonNull::new(l).ok_or(Error::CreationFailed)?; Ok(Self { inner: Rc::new(ThreadLoopInner::from_raw(ptr)), }) } } pub fn downgrade(&self) -> WeakThreadLoop { let weak = Rc::downgrade(&self.inner); WeakThreadLoop { weak } } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_thread_loop { self.inner.ptr.as_ptr() } pub fn loop_(&self) -> &LoopRef { unsafe { let thread_loop = pw_sys::pw_thread_loop_get_loop(self.as_raw_ptr()); &*(thread_loop.cast::()) } } /// Lock the Loop /// /// This ensures that the loop thread will not access objects associated /// with the loop while the lock is held, `lock()` can be used multiple times /// from the same thread. /// /// The lock needs to be held whenever you call any PipeWire function that /// uses an object associated with this loop. Make sure to not hold /// on to the lock more than necessary though, as the threaded loop stops /// while the lock is held. pub fn lock(&self) -> ThreadLoopLockGuard { ThreadLoopLockGuard::new(self) } /// Start the ThreadLoop pub fn start(&self) { unsafe { pw_sys::pw_thread_loop_start(self.as_raw_ptr()); } } /// Stop the ThreadLoop /// /// Stopping the ThreadLoop must be called without the lock pub fn stop(&self) { unsafe { pw_sys::pw_thread_loop_stop(self.as_raw_ptr()); } } /// Signal all threads waiting with [`wait()`](`Self::wait`) pub fn signal(&self, signal: bool) { unsafe { pw_sys::pw_thread_loop_signal(self.as_raw_ptr(), signal); } } /// Release the lock and wait /// /// Release the lock and wait until some thread calls [`signal()`](`Self::signal`) pub fn wait(&self) { unsafe { pw_sys::pw_thread_loop_wait(self.as_raw_ptr()); } } /// Release the lock and wait a maximum of `wait_max_sec` seconds /// until some thread calls [`signal()`](`Self::signal`) or time out pub fn timed_wait(&self, wait_max_sec: std::time::Duration) { unsafe { let wait_max_sec: i32 = wait_max_sec .as_secs() .try_into() .expect("Provided timeout does not fit in a i32"); pw_sys::pw_thread_loop_timed_wait(self.as_raw_ptr(), wait_max_sec); } } /// Get a timespec suitable for [`timed_wait_full()`](`Self::timed_wait_full`) pub fn get_time(&self, timeout: i64) -> nix::sys::time::TimeSpec { unsafe { let mut abstime: MaybeUninit = std::mem::MaybeUninit::uninit(); pw_sys::pw_thread_loop_get_time(self.as_raw_ptr(), abstime.as_mut_ptr(), timeout); let abstime = abstime.assume_init(); nix::sys::time::TimeSpec::new(abstime.tv_sec, abstime.tv_nsec) } } /// Release the lock and wait up to abs seconds until some /// thread calls [`signal()`](`Self::signal`). Use [`get_time()`](`Self::get_time`) /// to get a suitable timespec pub fn timed_wait_full(&self, abstime: nix::sys::time::TimeSpec) { unsafe { let mut abstime = pw_sys::timespec { tv_sec: abstime.tv_sec(), tv_nsec: abstime.tv_nsec(), }; pw_sys::pw_thread_loop_timed_wait_full( self.as_raw_ptr(), &mut abstime as *mut pw_sys::timespec, ); } } /// Signal all threads executing [`signal()`](`Self::signal`) with `wait_for_accept` pub fn accept(&self) { unsafe { pw_sys::pw_thread_loop_accept(self.as_raw_ptr()); } } /// Check if inside the thread pub fn in_thread(&self) { unsafe { pw_sys::pw_thread_loop_in_thread(self.as_raw_ptr()); } } } // Safety: The pw_loop is guaranteed to remain valid while any clone of the `ThreadLoop` is held, // because we use an internal Rc to keep the pw_thread_loop containing the pw_loop alive. unsafe impl IsLoopRc for ThreadLoop {} impl std::convert::AsRef for ThreadLoop { fn as_ref(&self) -> &LoopRef { self.loop_() } } pub struct WeakThreadLoop { weak: Weak, } impl WeakThreadLoop { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| ThreadLoop { inner }) } } pub struct ThreadLoopLockGuard<'a> { thread_loop: &'a ThreadLoop, } impl<'a> ThreadLoopLockGuard<'a> { fn new(thread_loop: &'a ThreadLoop) -> Self { unsafe { pw_sys::pw_thread_loop_lock(thread_loop.as_raw_ptr()); } ThreadLoopLockGuard { thread_loop } } /// Unlock the loop /// /// Unlocking the loop will call `drop()` pub fn unlock(self) { drop(self); } } impl<'a> Drop for ThreadLoopLockGuard<'a> { fn drop(&mut self) { unsafe { pw_sys::pw_thread_loop_unlock(self.thread_loop.as_raw_ptr()); } } } #[derive(Debug)] struct ThreadLoopInner { ptr: ptr::NonNull, } impl ThreadLoopInner { pub unsafe fn from_raw(ptr: ptr::NonNull) -> Self { Self { ptr } } } impl Drop for ThreadLoopInner { fn drop(&mut self) { unsafe { pw_sys::pw_thread_loop_destroy(self.ptr.as_ptr()) } } } pipewire-0.8.0/src/types.rs000064400000000000000000000040631046102023000137610ustar 00000000000000use std::fmt; // Macro generating the ObjectType enum macro_rules! object_type { ($( ($x:ident, $version:ident) ),*) => { #[derive(Debug, Eq, PartialEq, Clone)] pub enum ObjectType { $($x,)* Other(String), } impl ObjectType { pub(crate) fn from_str(s: &str) -> ObjectType { match s { $( concat!("PipeWire:Interface:", stringify!($x)) => ObjectType::$x, )* s => ObjectType::Other(s.to_string()), } } pub fn to_str(&self) -> &str { match self { $( ObjectType::$x => concat!("PipeWire:Interface:", stringify!($x)), )* ObjectType::Other(s) => s, } } pub(crate) fn client_version(&self) -> u32 { match self { $( ObjectType::$x => pw_sys::$version, )* ObjectType::Other(_) => panic!("Invalid object type"), } } } impl fmt::Display for ObjectType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.to_str()) } } }; } object_type![ // Id, API version (Client, PW_VERSION_CLIENT), (ClientEndpoint, PW_VERSION_CLIENT_ENDPOINT), (ClientNode, PW_VERSION_CLIENT_NODE), (ClientSession, PW_VERSION_CLIENT_SESSION), (Core, PW_VERSION_CORE), (Device, PW_VERSION_DEVICE), (Endpoint, PW_VERSION_ENDPOINT), (EndpointLink, PW_VERSION_ENDPOINT_LINK), (EndpointStream, PW_VERSION_ENDPOINT_STREAM), (Factory, PW_VERSION_FACTORY), (Link, PW_VERSION_LINK), (Metadata, PW_VERSION_METADATA), (Module, PW_VERSION_MODULE), (Node, PW_VERSION_NODE), (Port, PW_VERSION_PORT), (Profiler, PW_VERSION_PROFILER), (Registry, PW_VERSION_REGISTRY), (Session, PW_VERSION_SESSION) ]; pipewire-0.8.0/src/utils.rs000064400000000000000000000002661046102023000137560ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::thread; pub fn assert_main_thread() { assert_eq!(thread::current().name(), Some("main")); }