pipewire-0.9.2/.cargo_vcs_info.json0000644000000001460000000000100126710ustar { "git": { "sha1": "03301bc734c9f1d3fb7a064c072458ad00245133" }, "path_in_vcs": "pipewire" }pipewire-0.9.2/Cargo.lock0000644000000471670000000000100106620ustar # 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.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ "anstyle", "unicode-width", ] [[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.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "annotate-snippets", "bitflags", "cexpr", "clang-sys", "itertools", "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 7.1.3", ] [[package]] name = "cfg-expr" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d458d63f0f0f482c8da9b7c8b76c21bd885a02056cc94c6404d861ca2b8206" 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 = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[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 0.4.1", "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.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ "unicode-segmentation", ] [[package]] name = "cookie-factory" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" [[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 = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[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 = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[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.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4" dependencies = [ "bitflags", "cc", "convert_case", "cookie-factory", "libc", "libspa-sys", "nix", "nom 8.0.0", "system-deps", ] [[package]] name = "libspa-sys" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "901049455d2eb6decf9058235d745237952f4804bc584c5fcb41412e6adcc6e0" 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.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "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 = "nom" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ "memchr", ] [[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.9.2" dependencies = [ "anyhow", "bitflags", "clap", "libc", "libspa", "libspa-sys", "nix", "once_cell", "pipewire-sys", "thiserror", ] [[package]] name = "pipewire-sys" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb028afee0d6ca17020b090e3b8fa2d7de23305aef975c7e5192a5050246ea36" 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.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 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 = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[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.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[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.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "thiserror" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" 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.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[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.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[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", ] pipewire-0.9.2/Cargo.toml0000644000000050220000000000100106650ustar # 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.77" name = "pipewire" version = "0.9.2" authors = [ "Tom Wagner ", "Guillaume Desmottes ", ] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false 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" [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"] [lib] name = "pipewire" path = "src/lib.rs" [[example]] name = "audio-capture" path = "examples/audio-capture.rs" [[example]] name = "create-delete-remote-objects" path = "examples/create-delete-remote-objects.rs" [[example]] name = "pw-mon" path = "examples/pw-mon.rs" [[example]] name = "roundtrip" path = "examples/roundtrip.rs" [[example]] name = "streams" path = "examples/streams.rs" [[example]] name = "tone" path = "examples/tone.rs" [dependencies.anyhow] version = "1" [dependencies.bitflags] version = "2" [dependencies.libc] version = "0.2" [dependencies.nix] version = "0.30.1" features = [ "signal", "fs", ] [dependencies.once_cell] version = "1.0" [dependencies.pw_sys] version = "0.9" package = "pipewire-sys" [dependencies.spa] version = "0.9" package = "libspa" [dependencies.spa_sys] version = "0.9" package = "libspa-sys" [dependencies.thiserror] version = "2.0.12" [dev-dependencies.clap] version = "4.3.2" features = ["derive"] [dev-dependencies.once_cell] version = "1.5" pipewire-0.9.2/Cargo.toml.orig000064400000000000000000000023671046102023000143570ustar 00000000000000[package] name = "pipewire" version.workspace = true authors.workspace = true rust-version.workspace = true edition.workspace = true categories.workspace = true description = "Rust bindings for PipeWire" repository.workspace = true license.workspace = true readme = "README.md" homepage.workspace = true documentation = "https://pipewire.pages.freedesktop.org/pipewire-rs/pipewire/" keywords = ["pipewire", "multimedia", "audio", "video"] [dependencies] pw_sys = { package = "pipewire-sys", version = "0.9", path = "../pipewire-sys" } spa_sys = { package = "libspa-sys", version = "0.9", path = "../libspa-sys" } spa = { package = "libspa", version = "0.9", path = "../libspa" } anyhow = "1" thiserror = "2.0.12" libc = "0.2" nix = { version = "0.30.1", 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.9.2/LICENSE000064400000000000000000000021101046102023000124570ustar 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.9.2/README.md000064400000000000000000000006701046102023000127420ustar 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.9.2/examples/audio-capture.rs000064400000000000000000000150171046102023000164120ustar 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::MainLoopRc::new(None)?; let context = pw::context::ContextRc::new(&mainloop, None)?; let core = context.connect_rc(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::StreamBox::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.9.2/examples/create-delete-remote-objects.rs000064400000000000000000000072411046102023000212730ustar 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::MainLoopRc::new(None).expect("Failed to create Pipewire Mainloop"); let context = pw::context::ContextRc::new(&mainloop, None).expect("Failed to create Pipewire Context"); let core = context .connect_rc(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::MainLoopRc, core: &pw::core::CoreRc) { 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.9.2/examples/pw-mon.rs000064400000000000000000000165351046102023000150730ustar 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_default(); v.push(listener); } fn add_proxy_listener(&mut self, proxy_id: u32, listener: ProxyListener) { let v = self.listeners.entry(proxy_id).or_default(); 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::MainLoopRc::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::ContextRc::new(&main_loop, None)?; let props = remote.map(|remote| { properties! { *pw::keys::REMOTE_NAME => remote } }); let core = context.connect_rc(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 = core.get_registry_rc()?; let registry_weak = registry.downgrade(); // 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.9.2/examples/roundtrip.rs000064400000000000000000000033311046102023000156720ustar 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::MainLoopRc::new(None).expect("Failed to create main loop"); let context = pw::context::ContextRc::new(&mainloop, None).expect("Failed to create context"); let core = context.connect_rc(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.9.2/examples/streams.rs000064400000000000000000000135301046102023000153240ustar 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::MainLoopRc::new(None)?; let context = pw::context::ContextRc::new(&mainloop, None)?; let core = context.connect_rc(None)?; let data = UserData { format: Default::default(), }; let stream = pw::stream::StreamBox::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.9.2/examples/tone.rs000064400000000000000000000072631046102023000146210ustar 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::MainLoopRc::new(None)?; let context = pw::context::ContextRc::new(&mainloop, None)?; let core = context.connect_rc(None)?; let data: f64 = 0.0; let stream = pw::stream::StreamBox::new( &core, "audio-src", properties! { *pw::keys::MEDIA_TYPE => "Audio", *pw::keys::MEDIA_ROLE => "Music", *pw::keys::MEDIA_CATEGORY => "Playback", *pw::keys::AUDIO_CHANNELS => "2", }, )?; 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 mut position = [0; spa::param::audio::MAX_CHANNELS]; position[0] = spa_sys::SPA_AUDIO_CHANNEL_FL; position[1] = spa_sys::SPA_AUDIO_CHANNEL_FR; audio_info.set_position(position); 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.9.2/src/buffer.rs000064400000000000000000000025071046102023000140720ustar 00000000000000use super::stream::Stream; 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 Stream, } impl Buffer<'_> { pub(crate) unsafe fn from_raw( buf: *mut pw_sys::pw_buffer, stream: &Stream, ) -> 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.9.2/src/channel.rs000064400000000000000000000151761046102023000142370ustar 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::MainLoopRc; //! //! // 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 = MainLoopRc::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, Loop}; 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_: &Loop, callback: F) -> AttachedReceiver where F: Fn(T) + 'static, { let channel = self.channel.clone(); let readfd = channel .lock() .expect("Channel mutex lock poisoned") .readfd .as_raw_fd(); // 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: OwnedFd, writefd: OwnedFd, /// Queue of any messages waiting to be received. queue: VecDeque, } /// 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 /// [`Loop`](`crate::loop_::Loop`) 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.9.2/src/client.rs000064400000000000000000000204041046102023000140730ustar 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, 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"); let message_cstr = message.as_c_str(); Client::error_cstr(self, id, res, message_cstr) } pub fn error_cstr(&self, id: u32, res: i32, message: &CStr) { 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.9.2/src/constants.rs000064400000000000000000000003131046102023000146260ustar 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.9.2/src/context/box_.rs000064400000000000000000000040441046102023000152320ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{marker::PhantomData, ops::Deref, ptr}; use crate::{loop_::Loop, properties::PropertiesBox, Error}; use super::Context; #[derive(Debug)] pub struct ContextBox<'l> { ptr: std::ptr::NonNull, loop_: PhantomData<&'l Loop>, } impl<'l> ContextBox<'l> { pub fn new( loop_: &'l Loop, properties: Option, ) -> Result, Error> { unsafe { let props = properties .map_or(ptr::null(), |props| props.into_raw()) .cast_mut(); let raw = ptr::NonNull::new(pw_sys::pw_context_new((*loop_).as_raw_ptr(), props, 0)) .ok_or(Error::CreationFailed)?; Ok(Self::from_raw(raw)) } } /// Create a `ContextBox` by taking ownership of a raw `pw_context`. /// /// # Safety /// The provided pointer must point to a valid, well aligned [`pw_context`](`pw_sys::pw_context`). /// /// The raw context must not be manually destroyed or moved, as the new [`ContextBox`] takes /// ownership of it. /// /// The lifetime of the returned box is unbounded. The caller is responsible to make sure /// that the loop used with this context outlives the context. pub unsafe fn from_raw(raw: std::ptr::NonNull) -> ContextBox<'l> { Self { ptr: raw, loop_: PhantomData, } } pub fn into_raw(self) -> std::ptr::NonNull { std::mem::ManuallyDrop::new(self).ptr } } impl<'l> std::ops::Deref for ContextBox<'l> { type Target = Context; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl<'l> AsRef for ContextBox<'l> { fn as_ref(&self) -> &Context { self.deref() } } impl<'l> std::ops::Drop for ContextBox<'l> { fn drop(&mut self) { unsafe { pw_sys::pw_context_destroy(self.as_raw_ptr()); } } } pipewire-0.9.2/src/context/mod.rs000064400000000000000000000036421046102023000150650ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ os::fd::{IntoRawFd, OwnedFd}, ptr, }; use crate::{ core::CoreBox, properties::{Properties, PropertiesBox}, Error, }; mod box_; pub use box_::*; mod rc; pub use rc::*; #[repr(transparent)] pub struct Context(pw_sys::pw_context); impl Context { 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) -> &Properties { 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()); } } pub fn connect(&self, properties: Option) -> Result, Error> { 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(CoreBox::from_raw(ptr)) } } pub fn connect_fd( &self, fd: OwnedFd, properties: Option, ) -> Result, Error> { 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(CoreBox::from_raw(ptr)) } } } pipewire-0.9.2/src/context/rc.rs000064400000000000000000000063561046102023000147170ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ fmt, ops::Deref, os::fd::{IntoRawFd, OwnedFd}, ptr, rc::{Rc, Weak}, }; use crate::{ core::CoreRc, loop_::{IsLoopRc, Loop}, properties::PropertiesBox, Error, }; use super::{Context, ContextBox}; struct ContextRcInner { context: ContextBox<'static>, // Store the loop here, so that the loop is not dropped before the context, // which may lead to undefined behaviour. Rusts drop order of struct fields // (from top to bottom) ensures that this is always destroyed _after_ the context. _loop: Box>, } impl fmt::Debug for ContextRcInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ContextRcInner") .field("context", &self.context) .finish() } } #[derive(Clone, Debug)] pub struct ContextRc { inner: Rc, } impl ContextRc { pub fn new(loop_: &T, properties: Option) -> Result { let loop_: Box> = Box::new(loop_.clone()); let props = properties .map_or(ptr::null(), |props| props.into_raw()) .cast_mut(); unsafe { let raw = ptr::NonNull::new(pw_sys::pw_context_new( (*loop_).as_ref().as_raw_ptr(), props, 0, )) .ok_or(Error::CreationFailed)?; let context: ContextBox<'static> = ContextBox::from_raw(raw); Ok(ContextRc { inner: Rc::new(ContextRcInner { context, _loop: loop_, }), }) } } // TODO: from_raw() pub fn downgrade(&self) -> ContextWeak { let weak = Rc::downgrade(&self.inner); ContextWeak { weak } } pub fn connect_rc(&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(CoreRc::from_raw(ptr, self.clone())) } } pub fn connect_fd_rc( &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(CoreRc::from_raw(ptr, self.clone())) } } } impl std::ops::Deref for ContextRc { type Target = Context; fn deref(&self) -> &Self::Target { self.inner.context.deref() } } impl std::convert::AsRef for ContextRc { fn as_ref(&self) -> &Context { self.deref() } } pub struct ContextWeak { weak: Weak, } impl ContextWeak { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| ContextRc { inner }) } } pipewire-0.9.2/src/core/box_.rs000064400000000000000000000027641046102023000145050ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{marker::PhantomData, ops::Deref}; use crate::context::Context; use super::Core; #[derive(Debug)] pub struct CoreBox<'c> { ptr: std::ptr::NonNull, context: PhantomData<&'c Context>, } impl<'c> CoreBox<'c> { /// Create a `CoreBox` by taking ownership of a raw `pw_core`. /// /// # Safety /// The provided pointer must point to a valid, well aligned [`pw_core`](`pw_sys::pw_core`). /// /// The raw core must not be manually destroyed or moved, as the new [`CoreBox`] takes /// ownership of it. /// /// The lifetime of the returned box is unbounded. The caller is responsible to make sure /// that the context used with this core outlives the core. pub unsafe fn from_raw(raw: std::ptr::NonNull) -> CoreBox<'c> { Self { ptr: raw, context: PhantomData, } } pub fn into_raw(self) -> std::ptr::NonNull { std::mem::ManuallyDrop::new(self).ptr } } impl<'c> std::ops::Deref for CoreBox<'c> { type Target = Core; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl<'c> AsRef for CoreBox<'c> { fn as_ref(&self) -> &Core { self.deref() } } impl<'c> std::ops::Drop for CoreBox<'c> { fn drop(&mut self) { unsafe { pw_sys::pw_core_disconnect(self.as_raw_ptr()); } } } pipewire-0.9.2/src/core/mod.rs000064400000000000000000000273011046102023000143270ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use bitflags::bitflags; use libc::{c_char, c_void}; use std::{ ffi::{CStr, CString}, fmt, mem, pin::Pin, ptr, }; use crate::{ proxy::{Proxy, ProxyT}, registry::RegistryBox, Error, }; use spa::{ spa_interface_call_method, utils::result::{AsyncSeq, SpaResult}, }; mod box_; pub use box_::*; mod rc; pub use rc::*; pub const PW_ID_CORE: u32 = pw_sys::PW_ID_CORE; #[repr(transparent)] pub struct Core(pw_sys::pw_core); impl Core { 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 { unsafe { let registry = 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(RegistryBox::from_raw(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 factory_name = CString::new(factory_name).expect("Null byte in factory_name parameter"); let factory_name_cstr = factory_name.as_c_str(); self.create_object_cstr(factory_name_cstr, properties) } pub fn create_object_cstr( &self, factory_name: &CStr, properties: &impl AsRef, ) -> Result { let type_ = P::type_(); 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(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 Core, 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.9.2/src/core/rc.rs000064400000000000000000000037551046102023000141630ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ ops::Deref, ptr, rc::{Rc, Weak}, }; use spa::spa_interface_call_method; use crate::{registry::RegistryRc, Error}; use super::{Core, CoreBox}; #[derive(Debug)] struct CoreRcInner { core: CoreBox<'static>, // Store the context here, so that the context is not dropped before the core, // which may lead to undefined behaviour. Rusts drop order of struct fields // (from top to bottom) ensures that this is always destroyed _after_ the core. _context: crate::context::ContextRc, } #[derive(Debug, Clone)] pub struct CoreRc { inner: Rc, } impl CoreRc { pub fn from_raw( ptr: ptr::NonNull, context: crate::context::ContextRc, ) -> Self { let core = unsafe { CoreBox::from_raw(ptr) }; Self { inner: Rc::new(CoreRcInner { core, _context: context, }), } } pub fn downgrade(&self) -> CoreWeak { let weak = Rc::downgrade(&self.inner); CoreWeak { weak } } pub fn get_registry_rc(&self) -> Result { unsafe { let registry = 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(RegistryRc::from_raw(registry, self.clone())) } } } impl Deref for CoreRc { type Target = Core; fn deref(&self) -> &Self::Target { self.inner.core.deref() } } impl AsRef for CoreRc { fn as_ref(&self) -> &Core { self.deref() } } pub struct CoreWeak { weak: Weak, } impl CoreWeak { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| CoreRc { inner }) } } pipewire-0.9.2/src/device.rs000064400000000000000000000222361046102023000140610ustar 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.9.2/src/error.rs000064400000000000000000000005511046102023000137470ustar 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.9.2/src/factory.rs000064400000000000000000000142721046102023000142720ustar 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.9.2/src/keys.rs000064400000000000000000000441441046102023000135770ustar 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.9.2/src/lib.rs000064400000000000000000000162501046102023000133670ustar 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: //! ```no_run //! use pipewire::{main_loop::MainLoopBox, context::ContextBox}; //! //! fn main() -> Result<(), Box> { //! let mainloop = MainLoopBox::new(None)?; //! let context = ContextBox::new(&mainloop.loop_(), None)?; //! let core = context.connect(None)?; //! let registry = core.get_registry()?; //! //! Ok(()) //! } //! ``` //! //! The example above uses [`std::boxed::Box`]-like smart pointers to create the needed objects. //! Those boxes use lifetimes to ensure the objects dependencies (e.g. `Core` depends on `MainLoop`) //! outlive the object itself. //! If more flexibility is needed, [`std::rc::Rc`]-like reference-counting smart pointers also exist. //! Those will automatically keep the objects dependencies alive until the object is destroyed. //! //! Both of these kinds of types will automatically dereference to non-owning references for shared //! functionality, e.g. [`&MainLoop`](`main_loop::MainLoop`) or [`&Core`](`core::Core`). //! //! The same example as above, but using `Rc` types: //! ```no_run //! use pipewire::{main_loop::MainLoopRc, context::ContextRc}; //! //! fn main() -> Result<(), Box> { //! let mainloop = MainLoopRc::new(None)?; //! let context = ContextRc::new(&mainloop, None)?; //! let core = context.connect_rc(None)?; //! let registry = core.get_registry_rc()?; //! //! Ok(()) //! } //! ``` //! //! Once the needed objects are created, you can start hooking up different kinds of callbacks to //! them to react to events, and call methods to change the state of the remote. //! ```no_run //! use pipewire::{main_loop::MainLoopBox, context::ContextBox}; //! //! fn main() -> Result<(), Box> { //! let mainloop = MainLoopBox::new(None)?; //! let context = ContextBox::new(&mainloop.loop_(), None)?; //! 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::MainLoopBox; //! use std::time::Duration; //! //! fn main() -> Result<(), Box> { //! let mainloop = MainLoopBox::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; 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.9.2/src/link.rs000064400000000000000000000174021046102023000135560ustar 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.9.2/src/loop_/box_.rs000064400000000000000000000035041046102023000146560ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ops::Deref, ptr}; use super::Loop; use crate::Error; #[derive(Debug)] pub struct LoopBox { ptr: std::ptr::NonNull, } impl LoopBox { /// Create a new [`LoopBox`]. 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 raw = pw_sys::pw_loop_new(props); let ptr = ptr::NonNull::new(raw).ok_or(Error::CreationFailed)?; Ok(Self::from_raw(ptr)) } } /// Create a new [`LoopBox`] 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 [`LoopBox`] takes ownership of it. pub unsafe fn from_raw(ptr: ptr::NonNull) -> Self { Self { ptr } } pub fn into_raw(self) -> std::ptr::NonNull { std::mem::ManuallyDrop::new(self).ptr } } impl std::ops::Deref for LoopBox { type Target = Loop; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for LoopBox { fn as_ref(&self) -> &Loop { self.deref() } } // The owning type implements the Drop trait to clean up the raw type automatically. impl std::ops::Drop for LoopBox { fn drop(&mut self) { unsafe { pw_sys::pw_loop_destroy(self.as_raw_ptr()); } } } pipewire-0.9.2/src/loop_/mod.rs000064400000000000000000000436421046102023000145150ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{convert::TryInto, os::unix::prelude::*, ptr, 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; mod box_; pub use box_::*; mod rc; pub use rc::*; /// A transparent wrapper around a raw [`pw_loop`](`pw_sys::pw_loop`). /// It is usually only seen in a reference (`&Loop`), and does not own the `pw_loop`. /// /// Owned versions, [`LoopRc`] for shared ownership and [`LoopBox`] for unique ownership, are available, /// which lets you create and own a [`pw_loop`](`pw_sys::pw_loop`). /// /// Other objects, such as [`MainLoop`](`crate::main_loop::MainLoop`), can also contain loops. #[repr(transparent)] pub struct Loop(pw_sys::pw_loop); impl Loop { 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() ) } } 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`](`Loop::add_io`) on a loop, registering a callback to it. pub struct IoSource<'l, I> where I: AsRawFd, { ptr: ptr::NonNull, loop_: &'l Loop, // 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`](`Loop::add_idle`) on a loop, registering a callback to it. pub struct IdleSource<'l> { ptr: ptr::NonNull, loop_: &'l Loop, // 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`](`Loop::add_signal_local`) on a loop, registering a callback to it. pub struct SignalSource<'l> { ptr: ptr::NonNull, loop_: &'l Loop, // 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`](`Loop::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 Loop, // 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`](`Loop::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 Loop, // 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"), // `Into` is only implemented on some platforms for these types, // so use a fallible conversion. // As there are a limited amount of nanoseconds in a second, this shouldn't fail #[allow(clippy::unnecessary_fallible_conversions)] tv_nsec: duration .subsec_nanos() .try_into() .expect("Nanoseconds should fit into timespec"), } } 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.9.2/src/loop_/rc.rs000064400000000000000000000042231046102023000143320ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ ops::Deref, ptr, rc::{Rc, Weak}, }; use super::{Loop, LoopBox}; use crate::Error; /// Trait implemented by objects that implement a `pw_loop` and are reference counted in some way. /// /// # Safety /// /// The [`Loop`] 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(Debug)] struct LoopRcInner { loop_: LoopBox, } #[derive(Clone, Debug)] pub struct LoopRc { inner: Rc, } impl LoopRc { /// Create a new [`LoopRc`]. pub fn new(properties: Option<&spa::utils::dict::DictRef>) -> Result { let loop_ = LoopBox::new(properties)?; Ok(Self { inner: Rc::new(LoopRcInner { loop_ }), }) } /// Create a new [`LoopRc`] 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 [`LoopRc`] takes ownership of it. pub unsafe fn from_raw(ptr: ptr::NonNull) -> Self { let loop_ = LoopBox::from_raw(ptr); Self { inner: Rc::new(LoopRcInner { loop_ }), } } pub fn downgrade(&self) -> LoopWeak { let weak = Rc::downgrade(&self.inner); LoopWeak { weak } } } // Safety: The inner pw_loop is guaranteed to remain valid while any clone of the `LoopRc` is held, // because we use an internal Rc to keep it alive. unsafe impl IsLoopRc for LoopRc {} impl Deref for LoopRc { type Target = Loop; fn deref(&self) -> &Self::Target { self.inner.loop_.deref() } } impl std::convert::AsRef for LoopRc { fn as_ref(&self) -> &Loop { self.deref() } } pub struct LoopWeak { weak: Weak, } impl LoopWeak { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| LoopRc { inner }) } } pipewire-0.9.2/src/main_loop/box_.rs000064400000000000000000000035421046102023000155250ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ops::Deref, ptr}; use crate::Error; use super::MainLoop; #[derive(Debug)] pub struct MainLoopBox { ptr: std::ptr::NonNull, } impl MainLoopBox { /// Initialize Pipewire and create a new [`MainLoopBox`] pub fn new(properties: Option<&spa::utils::dict::DictRef>) -> Result { crate::init(); unsafe { let props = properties .map_or(ptr::null(), |props| props.as_raw()) .cast_mut(); let raw = pw_sys::pw_main_loop_new(props); let ptr = ptr::NonNull::new(raw).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 [`MainLoopBox`] takes ownership of it. pub unsafe fn from_raw(ptr: ptr::NonNull) -> Self { Self { ptr } } pub fn into_raw(self) -> std::ptr::NonNull { // Use ManuallyDrop to give up ownership of the managed struct and not run the destructor. std::mem::ManuallyDrop::new(self).ptr } } impl std::ops::Deref for MainLoopBox { type Target = MainLoop; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for MainLoopBox { fn as_ref(&self) -> &MainLoop { self.deref() } } impl std::ops::Drop for MainLoopBox { fn drop(&mut self) { unsafe { pw_sys::pw_main_loop_destroy(self.as_raw_ptr()); } } } pipewire-0.9.2/src/main_loop/mod.rs000064400000000000000000000017461046102023000153610ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use crate::loop_::Loop; mod box_; pub use box_::*; mod rc; pub use rc::*; #[repr(transparent)] pub struct MainLoop(pw_sys::pw_main_loop); impl MainLoop { pub fn as_raw(&self) -> &pw_sys::pw_main_loop { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_main_loop { std::ptr::addr_of!(self.0).cast_mut() } pub fn loop_(&self) -> &Loop { 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()); } } } impl std::convert::AsRef for MainLoop { fn as_ref(&self) -> &Loop { self.loop_() } } pipewire-0.9.2/src/main_loop/rc.rs000064400000000000000000000042701046102023000152010ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ ops::Deref, ptr, rc::{Rc, Weak}, }; use crate::{ loop_::{IsLoopRc, Loop}, Error, }; use super::{MainLoop, MainLoopBox}; #[derive(Debug)] struct MainLoopRcInner { main_loop: MainLoopBox, } #[derive(Debug, Clone)] pub struct MainLoopRc { inner: Rc, } impl MainLoopRc { /// Initialize Pipewire and create a new [`MainLoopRc`] pub fn new(properties: Option<&spa::utils::dict::DictRef>) -> Result { let main_loop = MainLoopBox::new(properties)?; Ok(Self { inner: Rc::new(MainLoopRcInner { main_loop }), }) } /// 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 [`MainLoopRc`] takes ownership of it. pub unsafe fn from_raw(ptr: ptr::NonNull) -> Self { let main_loop = MainLoopBox::from_raw(ptr); Self { inner: Rc::new(MainLoopRcInner { main_loop }), } } pub fn downgrade(&self) -> MainLoopWeak { let weak = Rc::downgrade(&self.inner); MainLoopWeak { weak } } } // 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 MainLoopRc {} impl std::ops::Deref for MainLoopRc { type Target = MainLoop; fn deref(&self) -> &Self::Target { self.inner.main_loop.deref() } } impl std::convert::AsRef for MainLoopRc { fn as_ref(&self) -> &MainLoop { self.deref() } } impl std::convert::AsRef for MainLoopRc { fn as_ref(&self) -> &Loop { self.loop_() } } pub struct MainLoopWeak { weak: Weak, } impl MainLoopWeak { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| MainLoopRc { inner }) } } pipewire-0.9.2/src/metadata.rs000064400000000000000000000131131046102023000143740ustar 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")); let key_cstr = key.as_c_str(); Metadata::set_property_cstr(self, subject, key_cstr, type_.as_deref(), value.as_deref()) } pub fn set_property_cstr( &self, subject: u32, key: &CStr, type_: Option<&CStr>, value: Option<&CStr>, ) { unsafe { spa::spa_interface_call_method!( self.proxy.as_ptr(), pw_sys::pw_metadata_methods, set_property, subject, key.as_ptr() as *const _, type_.map_or_else(ptr::null, CStr::as_ptr) as *const _, value.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.9.2/src/module.rs000064400000000000000000000145551046102023000141140ustar 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.9.2/src/node.rs000064400000000000000000000263261046102023000135530ustar 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.9.2/src/permissions.rs000064400000000000000000000025161046102023000151740ustar 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; } } #[derive(Clone, Copy)] #[repr(transparent)] pub struct Permission(pw_sys::pw_permission); impl Permission { pub fn new(id: u32, flags: PermissionFlags) -> Self { Self(pw_sys::pw_permission { id, permissions: flags.bits(), }) } pub fn id(&self) -> u32 { self.0.id } pub fn set_id(&mut self, id: u32) { self.0.id = id; } pub fn permission_flags(&self) -> PermissionFlags { PermissionFlags::from_bits_retain(self.0.permissions) } pub fn set_permission_flags(&mut self, flags: PermissionFlags) { self.0.permissions = flags.bits(); } } 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.9.2/src/port.rs000064400000000000000000000215521046102023000136060ustar 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.9.2/src/properties/box_.rs000064400000000000000000000114171046102023000157440ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{fmt, mem::ManuallyDrop, ops::Deref, ptr}; use super::Properties; /// Smart pointer to hold an owned [`Properties`] struct. /// /// # Examples /// Create a `PropertiesBox` struct and access the stored values by key: /// ```rust /// use pipewire::{properties::{properties, PropertiesBox}}; /// /// let props = properties!{ /// "Key" => "Value", /// "OtherKey" => "OtherValue" /// }; /// /// assert_eq!(Some("Value"), props.get("Key")); /// assert_eq!(Some("OtherValue"), props.get("OtherKey")); /// ``` pub struct PropertiesBox { ptr: ptr::NonNull, } impl PropertiesBox { /// 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_raw(raw) } } /// Take ownership of an existing raw `pw_properties` pointer. /// /// # Safety /// - The provided pointer must point to a valid, well-aligned `pw_properties` struct. /// - After this call, the returned `PropertiesBox` struct will assume ownership of the data pointed to, /// so that data must not be freed elsewhere. pub unsafe fn from_raw(ptr: ptr::NonNull) -> Self { Self { ptr } } /// Give up ownership of the contained properties , 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_raw(ptr::NonNull::new(copy).expect("pw_properties_new_dict() returned NULL")) } } } impl AsRef for PropertiesBox { fn as_ref(&self) -> &Properties { self.deref() } } impl AsRef for PropertiesBox { fn as_ref(&self) -> &spa::utils::dict::DictRef { self.deref().as_ref() } } impl std::ops::Deref for PropertiesBox { type Target = Properties; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast().as_ref() } } } impl std::ops::DerefMut for PropertiesBox { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { self.ptr.cast().as_mut() } } } impl Default for PropertiesBox { fn default() -> Self { Self::new() } } impl Clone for PropertiesBox { 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 PropertiesBox { fn drop(&mut self) { unsafe { pw_sys::pw_properties_free(self.ptr.as_ptr()) } } } impl fmt::Debug for PropertiesBox { 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("PropertiesBox").field(dict).finish() } } /// 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::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::PropertiesBox::new(); $( properties.insert($k, $v); )* properties }}; } pub use __properties__ as properties; pipewire-0.9.2/src/properties/mod.rs000064400000000000000000000113621046102023000155730ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ffi::CString, fmt, ptr}; mod box_; pub use box_::*; /// A collection of key/value pairs. #[repr(transparent)] pub struct Properties(pw_sys::pw_properties); impl Properties { 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 [`Properties`] 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) -> PropertiesBox { unsafe { let ptr = pw_sys::pw_properties_copy(self.as_raw_ptr()); PropertiesBox::from_raw(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 Properties { fn as_ref(&self) -> &spa::utils::dict::DictRef { self.dict() } } impl fmt::Debug for Properties { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // FIXME: Debug-print dict key and values directly f.debug_tuple("Properties").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" }; PropertiesBox::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() { use std::ops::Deref; let props = properties! { "K0" => "V0" }; println!("{:?}", &props); let props_ref: &Properties = 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.9.2/src/proxy.rs000064400000000000000000000172251046102023000140050ustar 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.9.2/src/registry/box_.rs000064400000000000000000000026451046102023000154230ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{marker::PhantomData, ops::Deref, ptr}; use crate::core::Core; use super::Registry; #[derive(Debug)] pub struct RegistryBox<'c> { ptr: ptr::NonNull, core: PhantomData<&'c Core>, } impl<'c> RegistryBox<'c> { /// Create a `RegistryBox` by taking ownership of a raw `pw_registry`. /// /// # Safety /// The provided pointer must point to a valid, well aligned [`pw_registry`](`pw_sys::pw_registry`). /// /// The raw registry must not be manually destroyed or moved, as the new [`RegistryBox`] takes /// ownership of it. /// /// The lifetime of the returned box is unbounded. The caller is responsible to make sure /// that the core used with this registry outlives the registry. pub unsafe fn from_raw(ptr: ptr::NonNull) -> Self { Self { ptr, core: PhantomData, } } } impl<'c> std::ops::Deref for RegistryBox<'c> { type Target = Registry; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl<'c> AsRef for RegistryBox<'c> { fn as_ref(&self) -> &Registry { self.deref() } } impl<'c> Drop for RegistryBox<'c> { fn drop(&mut self) { unsafe { pw_sys::pw_proxy_destroy(self.as_raw_ptr().cast()); } } } pipewire-0.9.2/src/registry/mod.rs000064400000000000000000000164101046102023000152460ustar 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::PropertiesBox, proxy::{Proxy, ProxyT}, types::ObjectType, Error, }; mod box_; pub use box_::*; mod rc; pub use rc::*; #[repr(transparent)] pub struct Registry(pw_sys::pw_registry); impl Registry { pub fn as_raw(&self) -> &pw_sys::pw_registry { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_registry { 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 { 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_raw_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_raw_ptr(), pw_sys::pw_registry_methods, destroy, global_id ) }; spa::utils::result::SpaResult::from_c(result) } } 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_raw_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| PropertiesBox::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.9.2/src/registry/rc.rs000064400000000000000000000035401046102023000150730ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ ops::Deref, ptr, rc::{Rc, Weak}, }; use super::{Registry, RegistryBox}; #[derive(Debug)] struct RegistryRcInner { registry: RegistryBox<'static>, // Store the core here, so that the registry is not dropped before the core, // which may lead to undefined behaviour. Rusts drop order of struct fields // (from top to bottom) ensures that this is always destroyed _after_ the registry. _core: crate::core::CoreRc, } #[derive(Debug, Clone)] pub struct RegistryRc { inner: Rc, } impl RegistryRc { /// Create a `RegistryRc` by taking ownership of a raw `pw_registry`. /// /// # Safety /// The provided pointer must point to a valid, well aligned [`pw_registry`](`pw_sys::pw_registry`). /// /// The raw registry must not be manually destroyed or moved, as the new [`RegistryRc`] takes /// ownership of it. pub unsafe fn from_raw( ptr: ptr::NonNull, core: crate::core::CoreRc, ) -> Self { let registry = unsafe { RegistryBox::from_raw(ptr) }; Self { inner: Rc::new(RegistryRcInner { registry, _core: core, }), } } pub fn downgrade(&self) -> RegistryWeak { let weak = Rc::downgrade(&self.inner); RegistryWeak { weak } } } impl Deref for RegistryRc { type Target = Registry; fn deref(&self) -> &Self::Target { self.inner.registry.deref() } } impl AsRef for RegistryRc { fn as_ref(&self) -> &Registry { self.deref() } } pub struct RegistryWeak { weak: Weak, } impl RegistryWeak { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| RegistryRc { inner }) } } pipewire-0.9.2/src/stream/box_.rs000064400000000000000000000043151046102023000150420ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use crate::{core::Core, error::Error, properties::PropertiesBox}; use std::{ ffi::{CStr, CString}, marker::PhantomData, ptr, }; use super::Stream; pub struct StreamBox<'c> { ptr: ptr::NonNull, core: PhantomData<&'c Core>, } impl<'c> StreamBox<'c> { /// Create a [`StreamBox`] /// /// Initialises a new stream with the given `name` and `properties`. pub fn new( core: &'c Core, name: &str, properties: PropertiesBox, ) -> Result, Error> { let name = CString::new(name).expect("Invalid byte in stream name"); let c_str = name.as_c_str(); StreamBox::new_cstr(core, c_str, properties) } /// Initialises a new stream with the given `name` as C String and `properties`. pub fn new_cstr( core: &'c Core, name: &CStr, properties: PropertiesBox, ) -> Result, Error> { unsafe { let stream = 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(Self::from_raw(stream)) } } pub unsafe fn from_raw(raw: ptr::NonNull) -> StreamBox<'c> { Self { ptr: raw, core: PhantomData, } } pub fn into_raw(self) -> ptr::NonNull { std::mem::ManuallyDrop::new(self).ptr } } impl<'c> std::ops::Deref for StreamBox<'c> { type Target = Stream; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast().as_ref() } } } impl<'c> std::fmt::Debug for StreamBox<'c> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("StreamBox") .field("name", &self.name()) .field("state", &self.state()) .field("node-id", &self.node_id()) .field("properties", &self.properties()) .finish() } } impl<'c> std::ops::Drop for StreamBox<'c> { fn drop(&mut self) { unsafe { pw_sys::pw_stream_destroy(self.as_raw_ptr()) } } } pipewire-0.9.2/src/stream/mod.rs000064400000000000000000000541401046102023000146730ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT //! Pipewire Stream use crate::buffer::Buffer; use crate::{error::Error, properties::Properties}; use bitflags::bitflags; use spa::utils::result::SpaResult; use std::{ ffi::{self, CStr, CString}, fmt::Debug, mem, os, pin::Pin, ptr, }; mod box_; pub use box_::*; mod rc; pub use rc::*; #[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. #[repr(transparent)] pub struct Stream(pw_sys::pw_stream); impl Stream { 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"); let error_cstr = error.as_c_str(); Stream::set_error_cstr(self, res, error_cstr) } /// Set the stream in error state with CStr /// /// # Panics /// Will panic if `error` contains a 0 byte. /// pub fn set_error_cstr(&mut self, res: i32, error: &CStr) { unsafe { pw_sys::pw_stream_set_error(self.as_raw_ptr(), res, error.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) -> &Properties { 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(&Stream, &mut D, u32, Option<&spa::pod::Pod>); type ProcessCB = dyn FnMut(&Stream, &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 Stream { 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 Stream, 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(&Stream, &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(&Stream, &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(&Stream, &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(&Stream, &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(&Stream, &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(&Stream, &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(&Stream, &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(&Stream, &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.9.2/src/stream/rc.rs000064400000000000000000000043141046102023000145160ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ ffi::{CStr, CString}, ops::Deref, ptr, rc::{Rc, Weak}, }; use crate::{ core::CoreRc, properties::PropertiesBox, stream::{Stream, StreamBox}, Error, }; #[derive(Debug)] struct StreamRcInner { stream: StreamBox<'static>, // Store the core here, so that the core is not dropped before the stream, // which may lead to undefined behaviour. Rusts drop order of struct fields // (from top to bottom) ensures that this is always destroyed _after_ the context. _core: CoreRc, } #[derive(Clone, Debug)] pub struct StreamRc { inner: Rc, } impl StreamRc { pub fn new(core: CoreRc, name: &str, properties: PropertiesBox) -> Result { let name = CString::new(name).expect("Invalid byte in stream name"); let c_str = name.as_c_str(); StreamRc::new_cstr(core, c_str, properties) } /// Initialises a new stream with the given `name` as C String and `properties`. pub fn new_cstr( core: CoreRc, name: &CStr, properties: PropertiesBox, ) -> Result { unsafe { let stream = 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)?; let stream: StreamBox<'static> = StreamBox::from_raw(stream); Ok(Self { inner: Rc::new(StreamRcInner { stream, _core: core, }), }) } } pub fn downgrade(&self) -> StreamWeak { let weak = Rc::downgrade(&self.inner); StreamWeak { weak } } } impl std::ops::Deref for StreamRc { type Target = Stream; fn deref(&self) -> &Self::Target { self.inner.stream.deref() } } impl std::convert::AsRef for StreamRc { fn as_ref(&self) -> &Stream { self.deref() } } pub struct StreamWeak { weak: Weak, } impl StreamWeak { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| StreamRc { inner }) } } pipewire-0.9.2/src/thread_loop/box_.rs000064400000000000000000000050401046102023000160430ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ ffi::{CStr, CString}, ops::Deref, ptr, }; use crate::Error; use super::ThreadLoop; #[derive(Debug)] pub struct ThreadLoopBox { ptr: ptr::NonNull, } impl ThreadLoopBox { /// Initialize Pipewire and create a new [`ThreadLoopBox`] 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()); ThreadLoopBox::new_cstr(name.as_deref(), properties) } /// Initialize Pipewire and create a new [`ThreadLoopBox`] with the given `name` as a C string, /// and optional properties. /// /// # Safety /// TODO pub unsafe fn new_cstr( name: Option<&CStr>, properties: Option<&spa::utils::dict::DictRef>, ) -> Result { crate::init(); unsafe { let props = properties.map_or(ptr::null(), |props| props.as_raw_ptr()); let name = name.map_or(ptr::null(), |p| p.as_ptr() as *const _); let raw = pw_sys::pw_thread_loop_new(name, props); let ptr = ptr::NonNull::new(raw).ok_or(Error::CreationFailed)?; Ok(Self::from_raw(ptr)) } } /// Create a new thread loop from a raw [`pw_thread_loop`](`pw_sys::pw_thread_loop`), taking ownership of it. /// /// # Safety /// The provided pointer must point to a valid, well aligned [`pw_thread_loop`](`pw_sys::pw_thread_loop`). /// /// The raw loop should not be manually destroyed or moved, as the new [`ThreadLoopBox`] takes ownership of it. pub unsafe fn from_raw(ptr: ptr::NonNull) -> Self { Self { ptr } } pub fn into_raw(self) -> std::ptr::NonNull { // Use ManuallyDrop to give up ownership of the managed struct and not run the destructor. std::mem::ManuallyDrop::new(self).ptr } } impl std::ops::Deref for ThreadLoopBox { type Target = ThreadLoop; fn deref(&self) -> &Self::Target { unsafe { self.ptr.cast::().as_ref() } } } impl AsRef for ThreadLoopBox { fn as_ref(&self) -> &ThreadLoop { self.deref() } } impl std::ops::Drop for ThreadLoopBox { fn drop(&mut self) { unsafe { pw_sys::pw_thread_loop_destroy(self.as_raw_ptr()); } } } pipewire-0.9.2/src/thread_loop/mod.rs000064400000000000000000000114531046102023000157000ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::mem::MaybeUninit; use crate::loop_::Loop; mod box_; pub use box_::*; mod rc; pub use rc::*; /// 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. #[repr(transparent)] pub struct ThreadLoop(pw_sys::pw_thread_loop); impl ThreadLoop { pub fn as_raw(&self) -> &pw_sys::pw_thread_loop { &self.0 } pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_thread_loop { std::ptr::addr_of!(self.0).cast_mut() } pub fn loop_(&self) -> &Loop { unsafe { let pw_loop = pw_sys::pw_thread_loop_get_loop(self.as_raw_ptr()); // FIXME: Make sure pw_loop is not null &*(pw_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()); } } } 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 /// /// Equivalent to dropping the lock guard. 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()); } } } pipewire-0.9.2/src/thread_loop/rc.rs000064400000000000000000000060641046102023000155270ustar 00000000000000// Copyright The pipewire-rs Contributors. // SPDX-License-Identifier: MIT use std::{ ffi::CStr, ops::Deref, ptr, rc::{Rc, Weak}, }; use crate::{ error::Error, loop_::{IsLoopRc, Loop}, }; use super::{ThreadLoop, ThreadLoopBox}; #[derive(Debug)] struct ThreadLoopRcInner { thread_loop: ThreadLoopBox, } #[derive(Debug, Clone)] pub struct ThreadLoopRc { inner: Rc, } impl ThreadLoopRc { /// Initialize Pipewire and create a new [`ThreadLoopRc`] with the given `name` and optional properties. /// /// # Safety /// TODO pub unsafe fn new( name: Option<&str>, properties: Option<&spa::utils::dict::DictRef>, ) -> Result { let thread_loop = ThreadLoopBox::new(name, properties)?; Ok(Self::from_box(thread_loop)) } /// Initialize Pipewire and create a new [`ThreadLoopRc`] with the given `name` as C string, /// and optional properties. /// /// # Safety /// TODO pub unsafe fn new_cstr( name: Option<&CStr>, properties: Option<&spa::utils::dict::DictRef>, ) -> Result { let thread_loop = ThreadLoopBox::new_cstr(name, properties)?; Ok(Self::from_box(thread_loop)) } /// Create a [`ThreadLoopRc`] using an existing [`ThreadLoopBox`]. /// /// # Safety /// TODO pub unsafe fn from_box(thread_loop: ThreadLoopBox) -> Self { Self { inner: Rc::new(ThreadLoopRcInner { thread_loop }), } } /// Create a new main loop from a raw [`pw_thread_loop`](`pw_sys::pw_thread_loop`), taking ownership of it. /// /// # Safety /// The provided pointer must point to a valid, well aligned [`pw_thread_loop`](`pw_sys::pw_thread_loop`). /// /// The raw loop should not be manually destroyed or moved, as the new [`ThreadLoopRc`] takes ownership of it. pub unsafe fn from_raw(ptr: ptr::NonNull) -> Self { let thread_loop = ThreadLoopBox::from_raw(ptr); Self { inner: Rc::new(ThreadLoopRcInner { thread_loop }), } } pub fn downgrade(&self) -> ThreadLoopWeak { let weak = Rc::downgrade(&self.inner); ThreadLoopWeak { weak } } } // Safety: The pw_loop is guaranteed to remain valid while any clone of the `ThreadLoopRc` is held, // because we use an internal Rc to keep the pw_thread_loop containing the pw_loop alive. unsafe impl IsLoopRc for ThreadLoopRc {} impl std::ops::Deref for ThreadLoopRc { type Target = ThreadLoop; fn deref(&self) -> &Self::Target { self.inner.thread_loop.deref() } } impl std::convert::AsRef for ThreadLoopRc { fn as_ref(&self) -> &ThreadLoop { self.deref() } } impl std::convert::AsRef for ThreadLoopRc { fn as_ref(&self) -> &Loop { self.loop_() } } pub struct ThreadLoopWeak { weak: Weak, } impl ThreadLoopWeak { pub fn upgrade(&self) -> Option { self.weak.upgrade().map(|inner| ThreadLoopRc { inner }) } } pipewire-0.9.2/src/types.rs000064400000000000000000000040631046102023000137640ustar 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.9.2/src/utils.rs000064400000000000000000000002661046102023000137610ustar 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")); }