├── .github └── workflows │ └── rust.yml ├── .gitignore ├── LICENSE ├── README.md ├── Tuxphones.service ├── bd ├── package-lock.json ├── package.json ├── release │ └── Tuxphones.plugin.js └── src │ └── Tuxphones │ ├── config.json │ └── index.jsx ├── daemon ├── Cargo.lock ├── Cargo.toml └── src │ ├── gstreamer.rs │ ├── lib.rs │ ├── main.rs │ ├── pulse.rs │ ├── socket.rs │ └── x.rs ├── install.sh └── updateDaemon.sh /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | defaults: 17 | run: 18 | working-directory: daemon 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Dependencies 23 | run: sudo apt-get update && sudo apt-get install -y libunwind-dev && sudo apt-get install -y libpulse-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio 24 | - name: Build 25 | run: cargo build --verbose 26 | - name: Run tests 27 | run: cargo test --verbose 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | daemon/target/ 2 | bd/node_modules/ 3 | .idea 4 | .vscode 5 | bundled.js 6 | *.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jack Hogan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tuxphones 2 | 3 | Discord screensharing audio for Linux. 4 | 5 | Development is ongoing and therefore some features do not work fully or are broken. If you find a bug, open an issue! 6 | 7 | ## State of the Project 8 | ### Project Activity 9 | We are very busy, so progress will be slow for the forseeable future. 10 | 11 | ### General Support 12 | We are currently working on supporting PulseAudio and X11. Once these become stable, we will start working on PipeWire and Wayland. 13 | 14 | ### BetterDiscord Plugin 15 | After the big Discord update in September 2022, the functionality of the plugin was completely broken. While we have tried to fix it as much as possible, it is still slightly inconsistent in reporting and integration so you may have to refresh Discord (Ctrl/Cmd+R) a few times to get it to load properly. 16 | 17 | ### Daemon 18 | While most of the daemon works properly, we can only transmit video to Discord at the moment (through WebRTC). We are undergoing a transition from WebRTC to UDP transmission, so nothing transmits right now. To get a better look at the actual transmission code, look at our sister project [here](https://github.com/ImTheSquid/gst-discordsender). 19 | 20 | ### Contributions 21 | We are open to contributions, however the project is still not stable so things will break. We plan to make an API for extending to PipeWire and Wayland in the future once we have a MVP. 22 | 23 | ## Installation 24 | ### Prerequisites 25 | - BetterDiscord 26 | - Rust 27 | - Cargo 28 | - Systemd 29 | - PulseAudio Dev Libraries 30 | - All GStreamer Dev Libraries 31 | 32 | ### Tuxphones is still in-development. Follow these temporary instructions to try it: 33 | Clone the repo, then copy the plugin file from `bd/release` to your BD plugins folder. Then run `cargo run` from the `daemon` directory. Finally, enable the BD plugin. 34 | 35 | ### The below instructions do not work yet! The Crates package is very outdated and will not be updated until the project works. 36 | ### Manual 37 | Run: 38 | ``` 39 | ./install.sh 40 | ``` 41 | This will install the daemon then copy the BetterDiscord plugin to your plugins folder. 42 | 43 | ### Updating 44 | The client-side plugin updates through Discord. 45 | 46 | To update the daemon, run: 47 | ``` 48 | ./updateDaemon.sh 49 | ``` 50 | -------------------------------------------------------------------------------- /Tuxphones.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Tuxphones Daemon 3 | [Service] 4 | Type=simple 5 | StandardOutput=journal 6 | ExecStart=%h/.cargo/bin/tuxphones 7 | [Install] 8 | WantedBy=default.target -------------------------------------------------------------------------------- /bd/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tuxphones", 3 | "version": "0.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "tuxphones", 9 | "version": "0.1.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "esbuild": "^0.15.16", 13 | "zerespluginlibrary": "^2.0.6" 14 | } 15 | }, 16 | "node_modules/@esbuild/android-arm": { 17 | "version": "0.15.16", 18 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.16.tgz", 19 | "integrity": "sha512-nyB6CH++2mSgx3GbnrJsZSxzne5K0HMyNIWafDHqYy7IwxFc4fd/CgHVZXr8Eh+Q3KbIAcAe3vGyqIPhGblvMQ==", 20 | "cpu": [ 21 | "arm" 22 | ], 23 | "optional": true, 24 | "os": [ 25 | "android" 26 | ], 27 | "engines": { 28 | "node": ">=12" 29 | } 30 | }, 31 | "node_modules/@esbuild/linux-loong64": { 32 | "version": "0.15.16", 33 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.16.tgz", 34 | "integrity": "sha512-SDLfP1uoB0HZ14CdVYgagllgrG7Mdxhkt4jDJOKl/MldKrkQ6vDJMZKl2+5XsEY/Lzz37fjgLQoJBGuAw/x8kQ==", 35 | "cpu": [ 36 | "loong64" 37 | ], 38 | "optional": true, 39 | "os": [ 40 | "linux" 41 | ], 42 | "engines": { 43 | "node": ">=12" 44 | } 45 | }, 46 | "node_modules/esbuild": { 47 | "version": "0.15.16", 48 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.16.tgz", 49 | "integrity": "sha512-o6iS9zxdHrrojjlj6pNGC2NAg86ECZqIETswTM5KmJitq+R1YmahhWtMumeQp9lHqJaROGnsBi2RLawGnfo5ZQ==", 50 | "hasInstallScript": true, 51 | "bin": { 52 | "esbuild": "bin/esbuild" 53 | }, 54 | "engines": { 55 | "node": ">=12" 56 | }, 57 | "optionalDependencies": { 58 | "@esbuild/android-arm": "0.15.16", 59 | "@esbuild/linux-loong64": "0.15.16", 60 | "esbuild-android-64": "0.15.16", 61 | "esbuild-android-arm64": "0.15.16", 62 | "esbuild-darwin-64": "0.15.16", 63 | "esbuild-darwin-arm64": "0.15.16", 64 | "esbuild-freebsd-64": "0.15.16", 65 | "esbuild-freebsd-arm64": "0.15.16", 66 | "esbuild-linux-32": "0.15.16", 67 | "esbuild-linux-64": "0.15.16", 68 | "esbuild-linux-arm": "0.15.16", 69 | "esbuild-linux-arm64": "0.15.16", 70 | "esbuild-linux-mips64le": "0.15.16", 71 | "esbuild-linux-ppc64le": "0.15.16", 72 | "esbuild-linux-riscv64": "0.15.16", 73 | "esbuild-linux-s390x": "0.15.16", 74 | "esbuild-netbsd-64": "0.15.16", 75 | "esbuild-openbsd-64": "0.15.16", 76 | "esbuild-sunos-64": "0.15.16", 77 | "esbuild-windows-32": "0.15.16", 78 | "esbuild-windows-64": "0.15.16", 79 | "esbuild-windows-arm64": "0.15.16" 80 | } 81 | }, 82 | "node_modules/esbuild-android-64": { 83 | "version": "0.15.16", 84 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.16.tgz", 85 | "integrity": "sha512-Vwkv/sT0zMSgPSVO3Jlt1pUbnZuOgtOQJkJkyyJFAlLe7BiT8e9ESzo0zQSx4c3wW4T6kGChmKDPMbWTgtliQA==", 86 | "cpu": [ 87 | "x64" 88 | ], 89 | "optional": true, 90 | "os": [ 91 | "android" 92 | ], 93 | "engines": { 94 | "node": ">=12" 95 | } 96 | }, 97 | "node_modules/esbuild-android-arm64": { 98 | "version": "0.15.16", 99 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.16.tgz", 100 | "integrity": "sha512-lqfKuofMExL5niNV3gnhMUYacSXfsvzTa/58sDlBET/hCOG99Zmeh+lz6kvdgvGOsImeo6J9SW21rFCogNPLxg==", 101 | "cpu": [ 102 | "arm64" 103 | ], 104 | "optional": true, 105 | "os": [ 106 | "android" 107 | ], 108 | "engines": { 109 | "node": ">=12" 110 | } 111 | }, 112 | "node_modules/esbuild-darwin-64": { 113 | "version": "0.15.16", 114 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.16.tgz", 115 | "integrity": "sha512-wo2VWk/n/9V2TmqUZ/KpzRjCEcr00n7yahEdmtzlrfQ3lfMCf3Wa+0sqHAbjk3C6CKkR3WKK/whkMq5Gj4Da9g==", 116 | "cpu": [ 117 | "x64" 118 | ], 119 | "optional": true, 120 | "os": [ 121 | "darwin" 122 | ], 123 | "engines": { 124 | "node": ">=12" 125 | } 126 | }, 127 | "node_modules/esbuild-darwin-arm64": { 128 | "version": "0.15.16", 129 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.16.tgz", 130 | "integrity": "sha512-fMXaUr5ou0M4WnewBKsspMtX++C1yIa3nJ5R2LSbLCfJT3uFdcRoU/NZjoM4kOMKyOD9Sa/2vlgN8G07K3SJnw==", 131 | "cpu": [ 132 | "arm64" 133 | ], 134 | "optional": true, 135 | "os": [ 136 | "darwin" 137 | ], 138 | "engines": { 139 | "node": ">=12" 140 | } 141 | }, 142 | "node_modules/esbuild-freebsd-64": { 143 | "version": "0.15.16", 144 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.16.tgz", 145 | "integrity": "sha512-UzIc0xlRx5x9kRuMr+E3+hlSOxa/aRqfuMfiYBXu2jJ8Mzej4lGL7+o6F5hzhLqWfWm1GWHNakIdlqg1ayaTNQ==", 146 | "cpu": [ 147 | "x64" 148 | ], 149 | "optional": true, 150 | "os": [ 151 | "freebsd" 152 | ], 153 | "engines": { 154 | "node": ">=12" 155 | } 156 | }, 157 | "node_modules/esbuild-freebsd-arm64": { 158 | "version": "0.15.16", 159 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.16.tgz", 160 | "integrity": "sha512-8xyiYuGc0DLZphFQIiYaLHlfoP+hAN9RHbE+Ibh8EUcDNHAqbQgUrQg7pE7Bo00rXmQ5Ap6KFgcR0b4ALZls1g==", 161 | "cpu": [ 162 | "arm64" 163 | ], 164 | "optional": true, 165 | "os": [ 166 | "freebsd" 167 | ], 168 | "engines": { 169 | "node": ">=12" 170 | } 171 | }, 172 | "node_modules/esbuild-linux-32": { 173 | "version": "0.15.16", 174 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.16.tgz", 175 | "integrity": "sha512-iGijUTV+0kIMyUVoynK0v+32Oi8yyp0xwMzX69GX+5+AniNy/C/AL1MjFTsozRp/3xQPl7jVux/PLe2ds10/2w==", 176 | "cpu": [ 177 | "ia32" 178 | ], 179 | "optional": true, 180 | "os": [ 181 | "linux" 182 | ], 183 | "engines": { 184 | "node": ">=12" 185 | } 186 | }, 187 | "node_modules/esbuild-linux-64": { 188 | "version": "0.15.16", 189 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.16.tgz", 190 | "integrity": "sha512-tuSOjXdLw7VzaUj89fIdAaQT7zFGbKBcz4YxbWrOiXkwscYgE7HtTxUavreBbnRkGxKwr9iT/gmeJWNm4djy/g==", 191 | "cpu": [ 192 | "x64" 193 | ], 194 | "optional": true, 195 | "os": [ 196 | "linux" 197 | ], 198 | "engines": { 199 | "node": ">=12" 200 | } 201 | }, 202 | "node_modules/esbuild-linux-arm": { 203 | "version": "0.15.16", 204 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.16.tgz", 205 | "integrity": "sha512-XKcrxCEXDTOuoRj5l12tJnkvuxXBMKwEC5j0JISw3ziLf0j4zIwXbKbTmUrKFWbo6ZgvNpa7Y5dnbsjVvH39bQ==", 206 | "cpu": [ 207 | "arm" 208 | ], 209 | "optional": true, 210 | "os": [ 211 | "linux" 212 | ], 213 | "engines": { 214 | "node": ">=12" 215 | } 216 | }, 217 | "node_modules/esbuild-linux-arm64": { 218 | "version": "0.15.16", 219 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.16.tgz", 220 | "integrity": "sha512-mPYksnfHnemNrvjrDhZyixL/AfbJN0Xn9S34ZOHYdh6/jJcNd8iTsv3JwJoEvTJqjMggjMhGUPJAdjnFBHoH8A==", 221 | "cpu": [ 222 | "arm64" 223 | ], 224 | "optional": true, 225 | "os": [ 226 | "linux" 227 | ], 228 | "engines": { 229 | "node": ">=12" 230 | } 231 | }, 232 | "node_modules/esbuild-linux-mips64le": { 233 | "version": "0.15.16", 234 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.16.tgz", 235 | "integrity": "sha512-kSJO2PXaxfm0pWY39+YX+QtpFqyyrcp0ZeI8QPTrcFVQoWEPiPVtOfTZeS3ZKedfH+Ga38c4DSzmKMQJocQv6A==", 236 | "cpu": [ 237 | "mips64el" 238 | ], 239 | "optional": true, 240 | "os": [ 241 | "linux" 242 | ], 243 | "engines": { 244 | "node": ">=12" 245 | } 246 | }, 247 | "node_modules/esbuild-linux-ppc64le": { 248 | "version": "0.15.16", 249 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.16.tgz", 250 | "integrity": "sha512-NimPikwkBY0yGABw6SlhKrtT35sU4O23xkhlrTT/O6lSxv3Pm5iSc6OYaqVAHWkLdVf31bF4UDVFO+D990WpAA==", 251 | "cpu": [ 252 | "ppc64" 253 | ], 254 | "optional": true, 255 | "os": [ 256 | "linux" 257 | ], 258 | "engines": { 259 | "node": ">=12" 260 | } 261 | }, 262 | "node_modules/esbuild-linux-riscv64": { 263 | "version": "0.15.16", 264 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.16.tgz", 265 | "integrity": "sha512-ty2YUHZlwFOwp7pR+J87M4CVrXJIf5ZZtU/umpxgVJBXvWjhziSLEQxvl30SYfUPq0nzeWKBGw5i/DieiHeKfw==", 266 | "cpu": [ 267 | "riscv64" 268 | ], 269 | "optional": true, 270 | "os": [ 271 | "linux" 272 | ], 273 | "engines": { 274 | "node": ">=12" 275 | } 276 | }, 277 | "node_modules/esbuild-linux-s390x": { 278 | "version": "0.15.16", 279 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.16.tgz", 280 | "integrity": "sha512-VkZaGssvPDQtx4fvVdZ9czezmyWyzpQhEbSNsHZZN0BHvxRLOYAQ7sjay8nMQwYswP6O2KlZluRMNPYefFRs+w==", 281 | "cpu": [ 282 | "s390x" 283 | ], 284 | "optional": true, 285 | "os": [ 286 | "linux" 287 | ], 288 | "engines": { 289 | "node": ">=12" 290 | } 291 | }, 292 | "node_modules/esbuild-netbsd-64": { 293 | "version": "0.15.16", 294 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.16.tgz", 295 | "integrity": "sha512-ElQ9rhdY51et6MJTWrCPbqOd/YuPowD7Cxx3ee8wlmXQQVW7UvQI6nSprJ9uVFQISqSF5e5EWpwWqXZsECLvXg==", 296 | "cpu": [ 297 | "x64" 298 | ], 299 | "optional": true, 300 | "os": [ 301 | "netbsd" 302 | ], 303 | "engines": { 304 | "node": ">=12" 305 | } 306 | }, 307 | "node_modules/esbuild-openbsd-64": { 308 | "version": "0.15.16", 309 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.16.tgz", 310 | "integrity": "sha512-KgxMHyxMCT+NdLQE1zVJEsLSt2QQBAvJfmUGDmgEq8Fvjrf6vSKB00dVHUEDKcJwMID6CdgCpvYNt999tIYhqA==", 311 | "cpu": [ 312 | "x64" 313 | ], 314 | "optional": true, 315 | "os": [ 316 | "openbsd" 317 | ], 318 | "engines": { 319 | "node": ">=12" 320 | } 321 | }, 322 | "node_modules/esbuild-sunos-64": { 323 | "version": "0.15.16", 324 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.16.tgz", 325 | "integrity": "sha512-exSAx8Phj7QylXHlMfIyEfNrmqnLxFqLxdQF6MBHPdHAjT7fsKaX6XIJn+aQEFiOcE4X8e7VvdMCJ+WDZxjSRQ==", 326 | "cpu": [ 327 | "x64" 328 | ], 329 | "optional": true, 330 | "os": [ 331 | "sunos" 332 | ], 333 | "engines": { 334 | "node": ">=12" 335 | } 336 | }, 337 | "node_modules/esbuild-windows-32": { 338 | "version": "0.15.16", 339 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.16.tgz", 340 | "integrity": "sha512-zQgWpY5pUCSTOwqKQ6/vOCJfRssTvxFuEkpB4f2VUGPBpdddZfdj8hbZuFRdZRPIVHvN7juGcpgCA/XCF37mAQ==", 341 | "cpu": [ 342 | "ia32" 343 | ], 344 | "optional": true, 345 | "os": [ 346 | "win32" 347 | ], 348 | "engines": { 349 | "node": ">=12" 350 | } 351 | }, 352 | "node_modules/esbuild-windows-64": { 353 | "version": "0.15.16", 354 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.16.tgz", 355 | "integrity": "sha512-HjW1hHRLSncnM3MBCP7iquatHVJq9l0S2xxsHHj4yzf4nm9TU4Z7k4NkeMlD/dHQ4jPlQQhwcMvwbJiOefSuZw==", 356 | "cpu": [ 357 | "x64" 358 | ], 359 | "optional": true, 360 | "os": [ 361 | "win32" 362 | ], 363 | "engines": { 364 | "node": ">=12" 365 | } 366 | }, 367 | "node_modules/esbuild-windows-arm64": { 368 | "version": "0.15.16", 369 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.16.tgz", 370 | "integrity": "sha512-oCcUKrJaMn04Vxy9Ekd8x23O8LoU01+4NOkQ2iBToKgnGj5eo1vU9i27NQZ9qC8NFZgnQQZg5oZWAejmbsppNA==", 371 | "cpu": [ 372 | "arm64" 373 | ], 374 | "optional": true, 375 | "os": [ 376 | "win32" 377 | ], 378 | "engines": { 379 | "node": ">=12" 380 | } 381 | }, 382 | "node_modules/zerespluginlibrary": { 383 | "version": "2.0.6", 384 | "resolved": "https://registry.npmjs.org/zerespluginlibrary/-/zerespluginlibrary-2.0.6.tgz", 385 | "integrity": "sha512-xFJGAKWWHVhCPCX6Qq1tsVJzOzTHRF7xnz2bu+3DKGaXsNCP7hPxy0LJ3mXtHLeSlH3v9ARZ0kzDs4cj5bCVnw==", 386 | "bin": { 387 | "zpl": "bin/zpl.js" 388 | } 389 | } 390 | }, 391 | "dependencies": { 392 | "@esbuild/android-arm": { 393 | "version": "0.15.16", 394 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.16.tgz", 395 | "integrity": "sha512-nyB6CH++2mSgx3GbnrJsZSxzne5K0HMyNIWafDHqYy7IwxFc4fd/CgHVZXr8Eh+Q3KbIAcAe3vGyqIPhGblvMQ==", 396 | "optional": true 397 | }, 398 | "@esbuild/linux-loong64": { 399 | "version": "0.15.16", 400 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.16.tgz", 401 | "integrity": "sha512-SDLfP1uoB0HZ14CdVYgagllgrG7Mdxhkt4jDJOKl/MldKrkQ6vDJMZKl2+5XsEY/Lzz37fjgLQoJBGuAw/x8kQ==", 402 | "optional": true 403 | }, 404 | "esbuild": { 405 | "version": "0.15.16", 406 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.16.tgz", 407 | "integrity": "sha512-o6iS9zxdHrrojjlj6pNGC2NAg86ECZqIETswTM5KmJitq+R1YmahhWtMumeQp9lHqJaROGnsBi2RLawGnfo5ZQ==", 408 | "requires": { 409 | "@esbuild/android-arm": "0.15.16", 410 | "@esbuild/linux-loong64": "0.15.16", 411 | "esbuild-android-64": "0.15.16", 412 | "esbuild-android-arm64": "0.15.16", 413 | "esbuild-darwin-64": "0.15.16", 414 | "esbuild-darwin-arm64": "0.15.16", 415 | "esbuild-freebsd-64": "0.15.16", 416 | "esbuild-freebsd-arm64": "0.15.16", 417 | "esbuild-linux-32": "0.15.16", 418 | "esbuild-linux-64": "0.15.16", 419 | "esbuild-linux-arm": "0.15.16", 420 | "esbuild-linux-arm64": "0.15.16", 421 | "esbuild-linux-mips64le": "0.15.16", 422 | "esbuild-linux-ppc64le": "0.15.16", 423 | "esbuild-linux-riscv64": "0.15.16", 424 | "esbuild-linux-s390x": "0.15.16", 425 | "esbuild-netbsd-64": "0.15.16", 426 | "esbuild-openbsd-64": "0.15.16", 427 | "esbuild-sunos-64": "0.15.16", 428 | "esbuild-windows-32": "0.15.16", 429 | "esbuild-windows-64": "0.15.16", 430 | "esbuild-windows-arm64": "0.15.16" 431 | } 432 | }, 433 | "esbuild-android-64": { 434 | "version": "0.15.16", 435 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.16.tgz", 436 | "integrity": "sha512-Vwkv/sT0zMSgPSVO3Jlt1pUbnZuOgtOQJkJkyyJFAlLe7BiT8e9ESzo0zQSx4c3wW4T6kGChmKDPMbWTgtliQA==", 437 | "optional": true 438 | }, 439 | "esbuild-android-arm64": { 440 | "version": "0.15.16", 441 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.16.tgz", 442 | "integrity": "sha512-lqfKuofMExL5niNV3gnhMUYacSXfsvzTa/58sDlBET/hCOG99Zmeh+lz6kvdgvGOsImeo6J9SW21rFCogNPLxg==", 443 | "optional": true 444 | }, 445 | "esbuild-darwin-64": { 446 | "version": "0.15.16", 447 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.16.tgz", 448 | "integrity": "sha512-wo2VWk/n/9V2TmqUZ/KpzRjCEcr00n7yahEdmtzlrfQ3lfMCf3Wa+0sqHAbjk3C6CKkR3WKK/whkMq5Gj4Da9g==", 449 | "optional": true 450 | }, 451 | "esbuild-darwin-arm64": { 452 | "version": "0.15.16", 453 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.16.tgz", 454 | "integrity": "sha512-fMXaUr5ou0M4WnewBKsspMtX++C1yIa3nJ5R2LSbLCfJT3uFdcRoU/NZjoM4kOMKyOD9Sa/2vlgN8G07K3SJnw==", 455 | "optional": true 456 | }, 457 | "esbuild-freebsd-64": { 458 | "version": "0.15.16", 459 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.16.tgz", 460 | "integrity": "sha512-UzIc0xlRx5x9kRuMr+E3+hlSOxa/aRqfuMfiYBXu2jJ8Mzej4lGL7+o6F5hzhLqWfWm1GWHNakIdlqg1ayaTNQ==", 461 | "optional": true 462 | }, 463 | "esbuild-freebsd-arm64": { 464 | "version": "0.15.16", 465 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.16.tgz", 466 | "integrity": "sha512-8xyiYuGc0DLZphFQIiYaLHlfoP+hAN9RHbE+Ibh8EUcDNHAqbQgUrQg7pE7Bo00rXmQ5Ap6KFgcR0b4ALZls1g==", 467 | "optional": true 468 | }, 469 | "esbuild-linux-32": { 470 | "version": "0.15.16", 471 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.16.tgz", 472 | "integrity": "sha512-iGijUTV+0kIMyUVoynK0v+32Oi8yyp0xwMzX69GX+5+AniNy/C/AL1MjFTsozRp/3xQPl7jVux/PLe2ds10/2w==", 473 | "optional": true 474 | }, 475 | "esbuild-linux-64": { 476 | "version": "0.15.16", 477 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.16.tgz", 478 | "integrity": "sha512-tuSOjXdLw7VzaUj89fIdAaQT7zFGbKBcz4YxbWrOiXkwscYgE7HtTxUavreBbnRkGxKwr9iT/gmeJWNm4djy/g==", 479 | "optional": true 480 | }, 481 | "esbuild-linux-arm": { 482 | "version": "0.15.16", 483 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.16.tgz", 484 | "integrity": "sha512-XKcrxCEXDTOuoRj5l12tJnkvuxXBMKwEC5j0JISw3ziLf0j4zIwXbKbTmUrKFWbo6ZgvNpa7Y5dnbsjVvH39bQ==", 485 | "optional": true 486 | }, 487 | "esbuild-linux-arm64": { 488 | "version": "0.15.16", 489 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.16.tgz", 490 | "integrity": "sha512-mPYksnfHnemNrvjrDhZyixL/AfbJN0Xn9S34ZOHYdh6/jJcNd8iTsv3JwJoEvTJqjMggjMhGUPJAdjnFBHoH8A==", 491 | "optional": true 492 | }, 493 | "esbuild-linux-mips64le": { 494 | "version": "0.15.16", 495 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.16.tgz", 496 | "integrity": "sha512-kSJO2PXaxfm0pWY39+YX+QtpFqyyrcp0ZeI8QPTrcFVQoWEPiPVtOfTZeS3ZKedfH+Ga38c4DSzmKMQJocQv6A==", 497 | "optional": true 498 | }, 499 | "esbuild-linux-ppc64le": { 500 | "version": "0.15.16", 501 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.16.tgz", 502 | "integrity": "sha512-NimPikwkBY0yGABw6SlhKrtT35sU4O23xkhlrTT/O6lSxv3Pm5iSc6OYaqVAHWkLdVf31bF4UDVFO+D990WpAA==", 503 | "optional": true 504 | }, 505 | "esbuild-linux-riscv64": { 506 | "version": "0.15.16", 507 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.16.tgz", 508 | "integrity": "sha512-ty2YUHZlwFOwp7pR+J87M4CVrXJIf5ZZtU/umpxgVJBXvWjhziSLEQxvl30SYfUPq0nzeWKBGw5i/DieiHeKfw==", 509 | "optional": true 510 | }, 511 | "esbuild-linux-s390x": { 512 | "version": "0.15.16", 513 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.16.tgz", 514 | "integrity": "sha512-VkZaGssvPDQtx4fvVdZ9czezmyWyzpQhEbSNsHZZN0BHvxRLOYAQ7sjay8nMQwYswP6O2KlZluRMNPYefFRs+w==", 515 | "optional": true 516 | }, 517 | "esbuild-netbsd-64": { 518 | "version": "0.15.16", 519 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.16.tgz", 520 | "integrity": "sha512-ElQ9rhdY51et6MJTWrCPbqOd/YuPowD7Cxx3ee8wlmXQQVW7UvQI6nSprJ9uVFQISqSF5e5EWpwWqXZsECLvXg==", 521 | "optional": true 522 | }, 523 | "esbuild-openbsd-64": { 524 | "version": "0.15.16", 525 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.16.tgz", 526 | "integrity": "sha512-KgxMHyxMCT+NdLQE1zVJEsLSt2QQBAvJfmUGDmgEq8Fvjrf6vSKB00dVHUEDKcJwMID6CdgCpvYNt999tIYhqA==", 527 | "optional": true 528 | }, 529 | "esbuild-sunos-64": { 530 | "version": "0.15.16", 531 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.16.tgz", 532 | "integrity": "sha512-exSAx8Phj7QylXHlMfIyEfNrmqnLxFqLxdQF6MBHPdHAjT7fsKaX6XIJn+aQEFiOcE4X8e7VvdMCJ+WDZxjSRQ==", 533 | "optional": true 534 | }, 535 | "esbuild-windows-32": { 536 | "version": "0.15.16", 537 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.16.tgz", 538 | "integrity": "sha512-zQgWpY5pUCSTOwqKQ6/vOCJfRssTvxFuEkpB4f2VUGPBpdddZfdj8hbZuFRdZRPIVHvN7juGcpgCA/XCF37mAQ==", 539 | "optional": true 540 | }, 541 | "esbuild-windows-64": { 542 | "version": "0.15.16", 543 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.16.tgz", 544 | "integrity": "sha512-HjW1hHRLSncnM3MBCP7iquatHVJq9l0S2xxsHHj4yzf4nm9TU4Z7k4NkeMlD/dHQ4jPlQQhwcMvwbJiOefSuZw==", 545 | "optional": true 546 | }, 547 | "esbuild-windows-arm64": { 548 | "version": "0.15.16", 549 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.16.tgz", 550 | "integrity": "sha512-oCcUKrJaMn04Vxy9Ekd8x23O8LoU01+4NOkQ2iBToKgnGj5eo1vU9i27NQZ9qC8NFZgnQQZg5oZWAejmbsppNA==", 551 | "optional": true 552 | }, 553 | "zerespluginlibrary": { 554 | "version": "2.0.6", 555 | "resolved": "https://registry.npmjs.org/zerespluginlibrary/-/zerespluginlibrary-2.0.6.tgz", 556 | "integrity": "sha512-xFJGAKWWHVhCPCX6Qq1tsVJzOzTHRF7xnz2bu+3DKGaXsNCP7hPxy0LJ3mXtHLeSlH3v9ARZ0kzDs4cj5bCVnw==" 557 | } 558 | } 559 | } 560 | -------------------------------------------------------------------------------- /bd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tuxphones", 3 | "version": "0.1.0", 4 | "description": "Discord soundshare for Linux", 5 | "main": "index.jsx", 6 | "scripts": { 7 | "build": "esbuild src/Tuxphones/index.jsx --bundle --outfile=src/Tuxphones/bundled.js --platform=node && zpl build Tuxphones", 8 | "init": "zpl init" 9 | }, 10 | "author": "Jack Hogan", 11 | "license": "MIT", 12 | "dependencies": { 13 | "esbuild": "^0.15.16", 14 | "zerespluginlibrary": "^2.0.6" 15 | }, 16 | "zplConfig": { 17 | "base": "./src", 18 | "out": "./release", 19 | "copyToBD": true, 20 | "addInstallScript": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bd/release/Tuxphones.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name Tuxphones 3 | * @description Tuxphones 4 | * @version 0.1.0 5 | * @author ImTheSquid 6 | * @authorId 262055523896131584 7 | * @website https://github.com/ImTheSquid/Tuxphones 8 | * @source https://raw.githubusercontent.com/ImTheSquid/Tuxphones/main/plugin/Tuxphones.plugin.js 9 | */ 10 | /*@cc_on 11 | @if (@_jscript) 12 | 13 | // Offer to self-install for clueless users that try to run this directly. 14 | var shell = WScript.CreateObject("WScript.Shell"); 15 | var fs = new ActiveXObject("Scripting.FileSystemObject"); 16 | var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins"); 17 | var pathSelf = WScript.ScriptFullName; 18 | // Put the user at ease by addressing them in the first person 19 | shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); 20 | if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { 21 | shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); 22 | } else if (!fs.FolderExists(pathPlugins)) { 23 | shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); 24 | } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { 25 | fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); 26 | // Show the user where to put plugins in the future 27 | shell.Exec("explorer " + pathPlugins); 28 | shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); 29 | } 30 | WScript.Quit(); 31 | 32 | @else@*/ 33 | const config = { 34 | info: { 35 | name: "Tuxphones", 36 | authors: [ 37 | { 38 | name: "ImTheSquid", 39 | discord_id: "262055523896131584", 40 | github_username: "ImTheSquid", 41 | twitter_username: "ImTheSquid11" 42 | } 43 | ], 44 | version: "0.1.0", 45 | description: "Tuxphones", 46 | github: "https://github.com/ImTheSquid/Tuxphones", 47 | github_raw: "https://raw.githubusercontent.com/ImTheSquid/Tuxphones/main/plugin/Tuxphones.plugin.js" 48 | }, 49 | main: "bundled.js" 50 | }; 51 | class Dummy { 52 | constructor() {this._config = config;} 53 | start() {} 54 | stop() {} 55 | } 56 | 57 | if (!global.ZeresPluginLibrary) { 58 | BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.name ?? config.info.name} is missing. Please click Download Now to install it.`, { 59 | confirmText: "Download Now", 60 | cancelText: "Cancel", 61 | onConfirm: () => { 62 | require("request").get("https://betterdiscord.app/gh-redirect?id=9", async (err, resp, body) => { 63 | if (err) return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9"); 64 | if (resp.statusCode === 302) { 65 | require("request").get(resp.headers.location, async (error, response, content) => { 66 | if (error) return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9"); 67 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), content, r)); 68 | }); 69 | } 70 | else { 71 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r)); 72 | } 73 | }); 74 | } 75 | }); 76 | } 77 | 78 | module.exports = !global.ZeresPluginLibrary ? Dummy : (([Plugin, Api]) => { 79 | const plugin = (Plugin, Library) => { 80 | const { Logger, Patcher, WebpackModules, DiscordModules, ContextMenu } = Library; 81 | const { Dispatcher, SelectedChannelStore, ButtonData, UserStore } = DiscordModules; 82 | const React = BdApi.React; 83 | const AuthenticationStore = Object.values(ZLibrary.WebpackModules.getAllModules()).find((m) => m.exports?.default?.getToken).exports.default; 84 | const RTCConnectionStore = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byProps("getRTCConnectionId", "getWasEverRtcConnected")); 85 | const ChunkedRequests = BdApi.findModuleByProps("makeChunkedRequest"); 86 | const WebSocketControl = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byProps("lastTimeConnectedChanged")).getSocket(); 87 | const GoLiveModal = BdApi.Webpack.getModule((m) => m.default?.toString().includes("GO_LIVE_MODAL")); 88 | const GetDesktopSources = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byStrings("Can't get desktop sources outside of native app"), { defaultExport: false }); 89 | function getFunctionNameFromString(obj, search) { 90 | for (const [k, v] of Object.entries(obj)) { 91 | if (search.every((str) => v?.toString().match(str))) { 92 | return k; 93 | } 94 | } 95 | return null; 96 | } 97 | return class extends Plugin { 98 | onStart() { 99 | this.webSocket = new WebSocket("ws://127.0.0.1:9000"); 100 | this.webSocket.onmessage = this.parseData; 101 | this.webSocket.onerror = (_) => { 102 | BdApi.showConfirmationModal("Tuxphones Daemon Error", [ 103 | "The Tuxphones daemon was not detected.\n", 104 | "If you don't know what this means or installed just the plugin and not the daemon, get help installing the daemon by going to the GitHub page:", 105 | /* @__PURE__ */ React.createElement("a", { 106 | href: "https://github.com/ImTheSquid/Tuxphones", 107 | target: "_blank" 108 | }, "Tuxphones Github"), 109 | " \n", 110 | `If you're sure you already installed the daemon, make sure it's running then click "Reload Discord".` 111 | ], { 112 | danger: true, 113 | confirmText: "Reload Discord", 114 | cancelText: "Stop Tuxphones", 115 | onConfirm: () => { 116 | location.reload(); 117 | } 118 | }); 119 | }; 120 | this.webSocket.onopen = (_) => this.onOpen(); 121 | this.interceptNextStreamServerUpdate = false; 122 | this.currentSoundProfile = null; 123 | this.selectedFPS = null; 124 | this.selectedResolution = null; 125 | this.serverId = null; 126 | this.wsOnMessage = this.wsOnMessage.bind(this); 127 | this._onmessage = null; 128 | this._ws = null; 129 | this.voice_ssrc = null; 130 | } 131 | onOpen() { 132 | Patcher.before(WebSocket.prototype, "send", (that, args) => { 133 | const arg = args[0]; 134 | if (typeof arg !== "string" || !that.url.includes("discord") || this._ws && this._ws !== that) 135 | return; 136 | const json = JSON.parse(arg); 137 | console.log("%cWS SEND FRAME ================================", "color: green; font-size: large; margin-top: 20px;"); 138 | if (json.op === 0 && json.d.streams.length > 0 && json.d.streams[0].type === "screen" && json.d.user_id === UserStore.getCurrentUser().id) { 139 | console.log("%cHOOKING SOCKET", "color: blue; font-size: xx-large;"); 140 | if (this._ws) { 141 | this.resetVars(); 142 | } 143 | this._ws = that; 144 | this._onmessage = that.onmessage; 145 | that.onmessage = this.wsOnMessage; 146 | } else if (json.op == 1 && this._ws === that) { 147 | json.d.data.mode = "xsalsa20_poly1305_lite"; 148 | json.d.mode = "xsalsa20_poly1305_lite"; 149 | args[0] = JSON.stringify(json); 150 | } else if (json.op == 5) { 151 | if (this.voice_ssrc) { 152 | this.audio_ssrc = json.d.ssrc; 153 | json.d.speaking = 1; 154 | args[0] = JSON.stringify(json); 155 | } else { 156 | this.voice_ssrc = json.d.ssrc; 157 | } 158 | } 159 | Logger.log(json); 160 | console.log("%cWS END SEND FRAME ============================", "color: green; font-size: large; margin-bottom: 20px;"); 161 | }); 162 | Patcher.before(WebSocket.prototype, "close", (that, [arg]) => { 163 | Logger.log("TUXPHONES CLOSE!"); 164 | Logger.log(that); 165 | Logger.log(arg); 166 | if (this._ws === that) { 167 | console.log("%cSCREENSHARE CLOSED! Unlocking log...", "color: red; font-size: x-large;"); 168 | if (this._ws) { 169 | this.resetVars(); 170 | } 171 | } 172 | }); 173 | Patcher.instead(Dispatcher, "dispatch", (_, [arg], original) => { 174 | if (this.interceptNextStreamServerUpdate && arg.type === "STREAM_SERVER_UPDATE") { 175 | Logger.log("STREAM SERVER UPDATE INTERCEPTED"); 176 | Logger.log(arg); 177 | if (arg.streamKey) { 178 | this.streamKey = arg.streamKey; 179 | } 180 | WebSocketControl.streamSetPaused(this.streamKey, false); 181 | Logger.log(this.streamKey); 182 | } 183 | return original(arg); 184 | }); 185 | this.showTuxOk = false; 186 | if (GoLiveModal) 187 | this.patchGoLive(GoLiveModal); 188 | else { 189 | new Promise((resolve) => { 190 | const cancel = WebpackModules.addListener((module2) => { 191 | if (!module2.default?.toString().includes("GO_LIVE_MODAL")) 192 | return; 193 | resolve(module2); 194 | cancel(); 195 | }); 196 | }).then((m) => { 197 | this.patchGoLive(m); 198 | }); 199 | } 200 | this.observer = new MutationObserver((mutations) => { 201 | if (mutations.filter((mut) => mut.addedNodes.length === 0 && mut.target.hasChildNodes()).length == 0) 202 | return; 203 | const res = mutations.flatMap((mut) => Array.from(mut.target.childNodes.values())).filter((node) => node.childNodes.length === 1).flatMap((node) => Array.from(node.childNodes.values())).filter((node) => node.nodeName === "DIV" && Array.from(node.childNodes.values()).some((node2) => node2.matches && node2.matches("[class*=flex]")))[0]; 204 | if (res) { 205 | res.querySelector("[class*=flex]").innerText = this.showTuxOk ? "Tuxphones sound enabled!" : "Tuxphones not available."; 206 | } 207 | }); 208 | this.observer.observe(document.querySelector("div > [class^=layerContainer]"), { childList: true, subtree: true }); 209 | Patcher.after(GetDesktopSources, getFunctionNameFromString(GetDesktopSources, [/getDesktopCaptureSources/]), (_, __, ret) => { 210 | return ret.then((vals) => new Promise((res) => { 211 | const f = function dispatch(e) { 212 | Dispatcher.unsubscribe("TUX_APPS", dispatch); 213 | Logger.log("Found Sources:"); 214 | Logger.log(vals); 215 | Logger.log("Found Sound Apps:"); 216 | Logger.log(e.apps); 217 | res(vals.map((v) => { 218 | let found = e.apps.find((el) => el.xid === parseInt(v.id.split(":")[1])); 219 | if (v.id.startsWith("window") && found) { 220 | Logger.log(`Associating ${v.id} with sound profile for ${found.name}`); 221 | v.sound = found; 222 | } else { 223 | v.sound = null; 224 | } 225 | return v; 226 | })); 227 | }; 228 | Dispatcher.subscribe("TUX_APPS", f); 229 | this.getInfo(vals.filter((v) => v.id.startsWith("window")).map((v) => parseInt(v.id.split(":")[1]))); 230 | })); 231 | }); 232 | } 233 | wsOnMessage(m) { 234 | const json = JSON.parse(m.data); 235 | console.log("%cWS RECV FRAME ================================", "color: orange; font-size: large; margin-top: 20px;"); 236 | if (json.op === 4) { 237 | console.log("%cRECEIVED CODEC AND ENCRYPTION INFORMATION", "color: aqua; font-size: xx-large;"); 238 | Logger.log("Audio Codec:"); 239 | Logger.log(json.d.audio_codec); 240 | Logger.log("Encryption Mode:"); 241 | Logger.log(json.d.mode); 242 | Logger.log("Secret key:"); 243 | Logger.log(json.d.secret_key); 244 | this.secret_key = json.d.secret_key; 245 | const op12 = { 246 | "op": 12, 247 | "d": { 248 | "audio_ssrc": this.audio_ssrc, 249 | "video_ssrc": this.video_ssrc, 250 | "rtx_ssrc": this.video_ssrc + 1, 251 | "streams": [ 252 | { 253 | "type": "video", 254 | "rid": "100", 255 | "ssrc": this.video_ssrc, 256 | "active": true, 257 | "quality": 100, 258 | "rtx_ssrc": this.video_ssrc + 1, 259 | "max_bitrate": 8e6, 260 | "max_framerate": this.selectedFPS, 261 | "max_resolution": { 262 | "type": "fixed", 263 | "width": this.selectedResolution.width, 264 | "height": this.selectedResolution.height 265 | } 266 | } 267 | ] 268 | } 269 | }; 270 | this._ws.send(JSON.stringify(op12)); 271 | this.startStream(this.currentSoundProfile.pid, this.currentSoundProfile.xid, this.selectedResolution, this.selectedFPS, this.ip, this.port, this.secret_key, this.voice_ssrc, this.base_ssrc, this.audio_ssrc); 272 | return; 273 | } else if (json.op == 2) { 274 | this.base_ssrc = json.d.ssrc; 275 | this.ip = json.d.ip; 276 | this.port = json.d.port; 277 | } 278 | Logger.log(json); 279 | console.log("%cWS END RECV FRAME ============================", "color: orange; font-size: large; margin-bottom: 20px;"); 280 | this._onmessage(m); 281 | } 282 | resetVars() { 283 | this.endStream(); 284 | this._ws.onmessage = this._onmessage; 285 | this._ws = null; 286 | this._onmessage = null; 287 | this.currentSoundProfile = null; 288 | this.interceptNextStreamServerUpdate = false; 289 | this.base_ssrc = null; 290 | this.voice_ssrc = null; 291 | this.audio_ssrc = null; 292 | } 293 | patchGoLive(m) { 294 | Patcher.after(m, "default", (_, __, ret) => { 295 | Logger.log(ret); 296 | if (ret.props.children.props.children[2].props.children[1].props.activeSlide == 2) { 297 | if (ret.props.children.props.children[2].props.children[1].props.children[2].props.children.props.children.props.selectedSource.sound) { 298 | this.showTuxOk = true; 299 | ret.props.children.props.children[2].props.children[2].props.children[0] = /* @__PURE__ */ React.createElement("div", { 300 | style: { "margin-right": "8px" } 301 | }, React.createElement(ButtonData, { 302 | onClick: () => { 303 | const streamInfo = ret.props.children.props.children[2].props.children[1].props.children[2].props.children.props.children.props; 304 | this.currentSoundProfile = streamInfo.selectedSource.sound; 305 | this.selectedFPS = streamInfo.selectedFPS; 306 | this.selectedResolution = streamInfo.selectedResolution; 307 | Logger.log("Creating Sound Stream"); 308 | this.createStream(streamInfo.guildId, SelectedChannelStore.getVoiceChannelId()); 309 | }, 310 | size: ButtonData.Sizes.SMALL 311 | }, "Go Live with Sound")); 312 | } else { 313 | this.showTuxOk = false; 314 | } 315 | } 316 | }); 317 | } 318 | createStream(guild_id, channel_id) { 319 | this.interceptNextStreamServerUpdate = true; 320 | WebSocketControl.streamCreate(guild_id === null ? "call" : "guild", guild_id, channel_id, null); 321 | } 322 | parseData(msg) { 323 | let obj = JSON.parse(msg.data); 324 | Logger.log(obj); 325 | switch (obj.type) { 326 | case "ApplicationList": 327 | Dispatcher.dispatch({ 328 | type: "TUX_APPS", 329 | apps: obj.apps 330 | }); 331 | break; 332 | case "StreamPreview": 333 | Logger.log(this.streamKey); 334 | ChunkedRequests.makeChunkedRequest(`/streams/${this.streamKey}/preview`, { 335 | thumbnail: `data:image/jpeg;base64,${obj.jpg}` 336 | }, { 337 | method: "POST", 338 | token: AuthenticationStore.getToken() 339 | }); 340 | break; 341 | default: 342 | Logger.err(`Received unknown command type: ${obj.type}`); 343 | } 344 | } 345 | startStream(pid, xid, selectedResolution, framerate, ip, port, secret_key, voice_ssrc, base_ssrc, audio_ssrc) { 346 | let resolution = null; 347 | switch (selectedResolution) { 348 | case 720: 349 | resolution = { 350 | width: 1280, 351 | height: 720, 352 | is_fixed: true 353 | }; 354 | break; 355 | case 1080: 356 | resolution = { 357 | width: 1920, 358 | height: 1080, 359 | is_fixed: true 360 | }; 361 | break; 362 | default: 363 | resolution = { 364 | width: 0, 365 | height: 0, 366 | is_fixed: false 367 | }; 368 | break; 369 | } 370 | this.webSocket.send(JSON.stringify({ 371 | type: "StartStream", 372 | pid, 373 | xid, 374 | resolution, 375 | framerate, 376 | rtc_connection_id: RTCConnectionStore.getRTCConnectionId(), 377 | secret_key, 378 | voice_ssrc, 379 | base_ssrc, 380 | ip, 381 | port, 382 | audio_ssrc 383 | })); 384 | } 385 | endStream() { 386 | this.webSocket.send(JSON.stringify({ 387 | type: "StopStream" 388 | })); 389 | } 390 | getInfo(xids) { 391 | this.webSocket.send(JSON.stringify({ 392 | type: "GetInfo", 393 | xids 394 | })); 395 | } 396 | onStop() { 397 | this.webSocket.close(); 398 | if (this._ws) { 399 | this.resetVars(); 400 | } 401 | Patcher.unpatchAll(); 402 | if (this.observer) 403 | this.observer.disconnect(); 404 | } 405 | }; 406 | }; 407 | return plugin(Plugin, Api); 408 | })(global.ZeresPluginLibrary.buildPlugin(config)); 409 | /*@end@*/ -------------------------------------------------------------------------------- /bd/src/Tuxphones/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "Tuxphones", 4 | "authors": [{ 5 | "name": "ImTheSquid", 6 | "discord_id": "262055523896131584", 7 | "github_username": "ImTheSquid", 8 | "twitter_username": "ImTheSquid11" 9 | }], 10 | "version": "0.1.0", 11 | "description": "Tuxphones", 12 | "github": "https://github.com/ImTheSquid/Tuxphones", 13 | "github_raw": "https://raw.githubusercontent.com/ImTheSquid/Tuxphones/main/plugin/Tuxphones.plugin.js" 14 | }, 15 | "main": "bundled.js" 16 | } -------------------------------------------------------------------------------- /bd/src/Tuxphones/index.jsx: -------------------------------------------------------------------------------- 1 | module.exports = (Plugin, Library) => { 2 | const {Logger, Patcher, WebpackModules, DiscordModules, ContextMenu} = Library; 3 | const { Dispatcher, SelectedChannelStore, ButtonData, UserStore } = DiscordModules; 4 | const React = BdApi.React; 5 | 6 | // Useful modules maybe: ApplicationStreamingSettingsStore, ApplicationStreamingStore 7 | const AuthenticationStore = Object.values(ZLibrary.WebpackModules.getAllModules()).find(m => m.exports?.default?.getToken).exports.default; // Works (should be replaced with custom solution eventually) 8 | const RTCConnectionStore = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byProps("getRTCConnectionId", "getWasEverRtcConnected")); 9 | //const WebRequests = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byProps("getXHR")); 10 | const ChunkedRequests = BdApi.findModuleByProps("makeChunkedRequest"); 11 | //const RTCControlSocket = BdApi.Webpack.getModule(m => m.Z?.prototype?.connect); 12 | const WebSocketControl = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byProps("lastTimeConnectedChanged")).getSocket(); 13 | const GoLiveModal = BdApi.Webpack.getModule(m => m.default?.toString().includes("GO_LIVE_MODAL")); 14 | // const DesktopSourcesChecker = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byProps("installedLogHooks")).prototype; 15 | const GetDesktopSources = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byStrings("Can't get desktop sources outside of native app"), {defaultExport: false}); 16 | 17 | function getFunctionNameFromString(obj, search) { 18 | for (const [k, v] of Object.entries(obj)) { 19 | if (search.every(str => v?.toString().match(str))) { 20 | return k; 21 | } 22 | } 23 | return null; 24 | } 25 | 26 | return class extends Plugin { 27 | onStart() { 28 | this.webSocket = new WebSocket("ws://127.0.0.1:9000"); 29 | this.webSocket.onmessage = this.parseData; 30 | this.webSocket.onerror = _ => { 31 | BdApi.showConfirmationModal('Tuxphones Daemon Error', [ 32 | 'The Tuxphones daemon was not detected.\n', 33 | 'If you don\'t know what this means or installed just the plugin and not the daemon, get help installing the daemon by going to the GitHub page:', 34 | Tuxphones Github, 35 | ' \n', 36 | 'If you\'re sure you already installed the daemon, make sure it\'s running then click "Reload Discord".' 37 | ], { 38 | danger: true, 39 | confirmText: 'Reload Discord', 40 | cancelText: 'Stop Tuxphones', 41 | onConfirm: () => { 42 | location.reload(); 43 | } 44 | }) 45 | } 46 | 47 | this.webSocket.onopen = _ => this.onOpen(); 48 | 49 | // Hook Dispatcher for when to intercept 50 | this.interceptNextStreamServerUpdate = false; 51 | this.currentSoundProfile = null; 52 | this.selectedFPS = null; 53 | this.selectedResolution = null; 54 | this.serverId = null; 55 | 56 | this.wsOnMessage = this.wsOnMessage.bind(this); 57 | this._onmessage = null; 58 | this._ws = null; 59 | 60 | this.voice_ssrc = null; 61 | } 62 | 63 | onOpen() { 64 | Patcher.before(WebSocket.prototype, 'send', (that, args) => { 65 | const arg = args[0]; 66 | if (typeof(arg) !== 'string' || !that.url.includes('discord') || (this._ws && this._ws !== that)) return; 67 | 68 | const json = JSON.parse(arg); 69 | 70 | console.log('%cWS SEND FRAME ================================', 'color: green; font-size: large; margin-top: 20px;'); 71 | 72 | // Condition for voice stream: json.op === 0 && json.d.streams.length > 0 && json.d.streams[0].type === 'video' && json.d.user_id === UserStore.getCurrentUser().id 73 | 74 | // Check if stream has started, if so then hook onmessage 75 | if (json.op === 0 && json.d.streams.length > 0 && json.d.streams[0].type === 'screen' && json.d.user_id === UserStore.getCurrentUser().id) { 76 | console.log('%cHOOKING SOCKET', 'color: blue; font-size: xx-large;'); 77 | if (this._ws) { 78 | this.resetVars(); 79 | } 80 | this._ws = that; 81 | this._onmessage = that.onmessage; 82 | that.onmessage = this.wsOnMessage; 83 | // this.token = json.d.token; 84 | } else if (json.op == 1 && this._ws === that) { 85 | json.d.data.mode = 'xsalsa20_poly1305_lite'; 86 | json.d.mode = 'xsalsa20_poly1305_lite'; 87 | args[0] = JSON.stringify(json); 88 | } else if (json.op == 5) { 89 | // WARNING WARNING WARNING ========================================================================== 90 | // This is a hack, it may not always work! 91 | // Still need to test this in multi-person VC 92 | if (this.voice_ssrc) { 93 | this.audio_ssrc = json.d.ssrc; 94 | json.d.speaking = 1; 95 | args[0] = JSON.stringify(json); 96 | } else { 97 | this.voice_ssrc = json.d.ssrc; 98 | } 99 | } 100 | // else if (json.op === 12 && json.d.video_ssrc !== 0 && json.d.rtx_ssrc !== 0) { 101 | // console.log('%cRECEIVED SSRC INFORMATION', 'color: aqua; font-size: xx-large;'); 102 | // Logger.log('Video SSRC:'); 103 | // Logger.log(json.d.video_ssrc); 104 | // Logger.log('RTX SSRC:'); 105 | // Logger.log(json.d.rtx_ssrc); 106 | 107 | // this.ssrc = json.d.video_ssrc; 108 | // const res = json.d.streams[0].max_resolution; 109 | // this.resolution = { 110 | // width: res.width, 111 | // height: res.height, 112 | // is_fixed: res.type === 'fixed' 113 | // }; 114 | // } 115 | 116 | Logger.log(json); 117 | console.log('%cWS END SEND FRAME ============================', 'color: green; font-size: large; margin-bottom: 20px;'); 118 | }); 119 | 120 | Patcher.before(WebSocket.prototype, 'close', (that, [arg]) => { 121 | Logger.log('TUXPHONES CLOSE!'); 122 | Logger.log(that); 123 | Logger.log(arg); 124 | if (this._ws === that) { 125 | console.log('%cSCREENSHARE CLOSED! Unlocking log...', 'color: red; font-size: x-large;'); 126 | if (this._ws) { 127 | this.resetVars(); 128 | } 129 | } 130 | }); 131 | 132 | Patcher.instead(Dispatcher, 'dispatch', (_, [arg], original) => { 133 | if (this.interceptNextStreamServerUpdate && arg.type === 'STREAM_SERVER_UPDATE') { 134 | Logger.log("STREAM SERVER UPDATE INTERCEPTED"); 135 | Logger.log(arg) 136 | // let res = null; 137 | // switch (this.selectedResolution) { 138 | // case 720: res = { 139 | // width: 1280, 140 | // height: 720, 141 | // is_fixed: true 142 | // }; 143 | // break; 144 | // case 1080: res = { 145 | // width: 1920, 146 | // height: 1080, 147 | // is_fixed: true 148 | // }; 149 | // break; 150 | // default: res = { 151 | // width: 0, 152 | // height: 0, 153 | // is_fixed: false 154 | // }; 155 | // break; 156 | // } 157 | 158 | if (arg.streamKey) { 159 | this.streamKey = arg.streamKey; 160 | } 161 | WebSocketControl.streamSetPaused(this.streamKey, false); 162 | Logger.log(this.streamKey) 163 | // this.startStream(this.currentSoundProfile.pid, this.currentSoundProfile.xid, this.selectedResolution, this.selectedFPS, this.ip, this.port, this.secret_key, this.voice_ssrc, this.base_ssrc); 164 | 165 | // this.startStream(this.currentSoundProfile.pid, this.currentSoundProfile.xid, res, this.selectedFPS, this.serverId, arg.token, arg.endpoint); 166 | // return new Promise(res => res()); 167 | } 168 | // } else if (this.currentSoundProfile) { 169 | // // Hide the stream's existence from Discord until ready to test Tuxphones/Discord interaction 170 | // switch (arg.type) { 171 | // case 'STREAM_CREATE': 172 | // Logger.log("SOUND SC PROFILE"); 173 | // Logger.log(arg); 174 | // this.serverId = arg.rtcServerId; 175 | // break; 176 | // // return new Promise(res => res()); 177 | // case 'STREAM_UPDATE': 178 | // Logger.log("SOUND SU PROFILE"); 179 | // Logger.log(arg); 180 | // // this.streamKey = arg.streamKey; 181 | // break; 182 | // // return new Promise(res => res()); 183 | // case 'VOICE_STATE_UPDATES': 184 | // Logger.log("SOUND VSU PROFILE"); 185 | // Logger.log(arg); 186 | // arg.voiceStates[0].selfStream = false; 187 | // break; 188 | // } 189 | // } else if (arg.type.match(/(STREAM.*_UPDATE|STREAM_CREATE)/)) { 190 | // Logger.log("STREAM CREATE OR UPDATE"); 191 | // Logger.log(arg); 192 | // }else { 193 | // // Logger.log(arg) 194 | // } 195 | return original(arg); 196 | }); 197 | 198 | this.showTuxOk = false; 199 | 200 | if (GoLiveModal) this.patchGoLive(GoLiveModal) 201 | else { 202 | new Promise(resolve => { 203 | const cancel = WebpackModules.addListener(module => { 204 | if (!module.default?.toString().includes("GO_LIVE_MODAL")) return; 205 | resolve(module); 206 | cancel(); 207 | }); 208 | }).then(m => { 209 | this.patchGoLive(m); 210 | }); 211 | } 212 | 213 | this.observer = new MutationObserver(mutations => { 214 | if (mutations.filter(mut => mut.addedNodes.length === 0 && mut.target.hasChildNodes()).length == 0) return; 215 | 216 | const res = mutations 217 | .flatMap(mut => Array.from(mut.target.childNodes.values())) 218 | .filter(node => node.childNodes.length === 1) 219 | .flatMap(node => Array.from(node.childNodes.values())) 220 | .filter(node => node.nodeName === "DIV" && Array.from(node.childNodes.values()) 221 | .some(node => node.matches && node.matches("[class*=flex]")))[0]; 222 | 223 | if (res) { 224 | res.querySelector("[class*=flex]").innerText = this.showTuxOk ? "Tuxphones sound enabled!" : "Tuxphones not available."; 225 | } 226 | }); 227 | 228 | this.observer.observe(document.querySelector("div > [class^=layerContainer]"), {childList: true, subtree: true}); 229 | 230 | // Add extra info to desktop sources list 231 | Patcher.after(GetDesktopSources, getFunctionNameFromString(GetDesktopSources, [/getDesktopCaptureSources/]), (_, __, ret) => { 232 | return ret.then(vals => new Promise(res => { 233 | const f = function dispatch(e) { 234 | Dispatcher.unsubscribe('TUX_APPS', dispatch); 235 | 236 | // Check against window IDs to see if comaptible with sound 237 | Logger.log("Found Sources:") 238 | Logger.log(vals); 239 | Logger.log("Found Sound Apps:") 240 | Logger.log(e.apps); 241 | res(vals.map(v => { 242 | let found = e.apps.find(el => el.xid === parseInt(v.id.split(':')[1])); 243 | if (v.id.startsWith('window') && found) { 244 | Logger.log(`Associating ${v.id} with sound profile for ${found.name}`) 245 | v.sound = found; 246 | } else { 247 | v.sound = null; 248 | } 249 | return v; 250 | })); 251 | } 252 | 253 | Dispatcher.subscribe('TUX_APPS', f); 254 | this.getInfo(vals.filter(v => v.id.startsWith('window')).map(v => parseInt(v.id.split(':')[1]))); 255 | })); 256 | }); 257 | 258 | // Patch stream to get IP address 259 | // Patcher.after(RTCControlSocket.Z.prototype, '_handleReady', (that, _, __) => { 260 | // Logger.log("handling ready") 261 | // that._connection.on("connected", (___, info) => { 262 | // Logger.log(info) 263 | // this.ip = info.address; 264 | // }); 265 | // }); 266 | } 267 | 268 | wsOnMessage(m) { 269 | const json = JSON.parse(m.data); 270 | 271 | console.log('%cWS RECV FRAME ================================', 'color: orange; font-size: large; margin-top: 20px;'); 272 | 273 | if (json.op === 4) { 274 | console.log('%cRECEIVED CODEC AND ENCRYPTION INFORMATION', 'color: aqua; font-size: xx-large;'); 275 | Logger.log('Audio Codec:'); 276 | Logger.log(json.d.audio_codec); 277 | Logger.log('Encryption Mode:'); 278 | Logger.log(json.d.mode); 279 | Logger.log('Secret key:'); 280 | Logger.log(json.d.secret_key); 281 | this.secret_key = json.d.secret_key; 282 | 283 | // Send video stream op 284 | const op12 = { 285 | "op":12, 286 | "d":{ 287 | "audio_ssrc":this.audio_ssrc, 288 | "video_ssrc":this.video_ssrc, 289 | "rtx_ssrc":this.video_ssrc + 1, 290 | "streams":[ 291 | { 292 | "type":"video", 293 | "rid":"100", 294 | "ssrc":this.video_ssrc, 295 | "active":true, 296 | "quality":100, 297 | "rtx_ssrc":this.video_ssrc + 1, 298 | "max_bitrate":8000000, 299 | "max_framerate": this.selectedFPS, 300 | "max_resolution":{ 301 | "type":"fixed", 302 | "width": this.selectedResolution.width, 303 | "height": this.selectedResolution.height, 304 | } 305 | } 306 | ] 307 | } 308 | } 309 | this._ws.send(JSON.stringify(op12)); 310 | 311 | this.startStream(this.currentSoundProfile.pid, this.currentSoundProfile.xid, this.selectedResolution, this.selectedFPS, this.ip, this.port, this.secret_key, this.voice_ssrc, this.base_ssrc, this.audio_ssrc); 312 | return; // Disallow encryption information, stopping the stream from being created 313 | } else if (json.op == 2) { 314 | this.base_ssrc = json.d.ssrc; 315 | this.ip = json.d.ip; 316 | this.port = json.d.port; 317 | } 318 | 319 | Logger.log(json); 320 | 321 | console.log('%cWS END RECV FRAME ============================', 'color: orange; font-size: large; margin-bottom: 20px;'); 322 | 323 | this._onmessage(m); 324 | } 325 | 326 | resetVars() { 327 | this.endStream(); 328 | this._ws.onmessage = this._onmessage; 329 | this._ws = null; 330 | this._onmessage = null; 331 | this.currentSoundProfile = null; 332 | this.interceptNextStreamServerUpdate = false; 333 | this.base_ssrc = null; 334 | this.voice_ssrc = null; 335 | this.audio_ssrc = null; 336 | } 337 | 338 | patchGoLive(m) { 339 | Patcher.after(m, 'default', (_, __, ret) => { 340 | Logger.log(ret) 341 | 342 | if (ret.props.children.props.children[2].props.children[1].props.activeSlide == 2) { 343 | if (ret.props.children.props.children[2].props.children[1].props.children[2].props.children.props.children.props.selectedSource.sound) { 344 | this.showTuxOk = true; 345 | ret.props.children.props.children[2].props.children[2].props.children[0] =
346 | {React.createElement(ButtonData, { 347 | onClick: () => { 348 | const streamInfo = ret.props.children.props.children[2].props.children[1].props.children[2].props.children.props.children.props; 349 | this.currentSoundProfile = streamInfo.selectedSource.sound; 350 | this.selectedFPS = streamInfo.selectedFPS; 351 | this.selectedResolution = streamInfo.selectedResolution; 352 | Logger.log("Creating Sound Stream"); 353 | this.createStream(streamInfo.guildId, SelectedChannelStore.getVoiceChannelId()); 354 | }, 355 | size: ButtonData.Sizes.SMALL 356 | }, "Go Live with Sound")} 357 |
358 | } else { 359 | this.showTuxOk = false; 360 | } 361 | } 362 | }); 363 | } 364 | 365 | createStream(guild_id, channel_id) { 366 | this.interceptNextStreamServerUpdate = true; 367 | WebSocketControl.streamCreate( 368 | guild_id === null ? 'call' : 'guild', // type 369 | guild_id, // guild_id 370 | channel_id, // channel or DM id 371 | null, // preferred_region 372 | ); 373 | } 374 | 375 | parseData(msg) { 376 | let obj = JSON.parse(msg.data); 377 | Logger.log(obj) 378 | switch (obj.type) { 379 | case 'ApplicationList': 380 | Dispatcher.dispatch({ 381 | type: 'TUX_APPS', 382 | apps: obj.apps 383 | }); 384 | break; 385 | case 'StreamPreview': 386 | // Alternatively, DiscordNative.http.makeChunkedRequest 387 | Logger.log(this.streamKey) 388 | ChunkedRequests.makeChunkedRequest(`/streams/${this.streamKey}/preview`, { 389 | thumbnail: `data:image/jpeg;base64,${obj.jpg}` // May have to include charset? 390 | }, { 391 | method: 'POST', 392 | token: AuthenticationStore.getToken() 393 | }); 394 | break; 395 | default: 396 | Logger.err(`Received unknown command type: ${obj.type}`); 397 | } 398 | } 399 | 400 | // server_id PRIORITY: RTC Server ID -> Guild ID -> Channel ID 401 | // Guild ID will always exist, so get RTC Server ID 402 | startStream(pid, xid, selectedResolution, framerate, ip, port, secret_key, voice_ssrc, base_ssrc, audio_ssrc) { 403 | let resolution = null; 404 | switch (selectedResolution) { 405 | case 720: resolution = { 406 | width: 1280, 407 | height: 720, 408 | is_fixed: true 409 | }; 410 | break; 411 | case 1080: resolution = { 412 | width: 1920, 413 | height: 1080, 414 | is_fixed: true 415 | }; 416 | break; 417 | default: resolution = { 418 | width: 0, 419 | height: 0, 420 | is_fixed: false 421 | }; 422 | break; 423 | } 424 | 425 | this.webSocket.send(JSON.stringify({ 426 | type: 'StartStream', 427 | pid: pid, 428 | xid: xid, 429 | resolution: resolution, 430 | framerate: framerate, 431 | // server_id: server_id, 432 | // user_id: AuthenticationStore.getId(), 433 | // token: token, 434 | // session_id: AuthenticationStore.getSessionId(), // getSessionId [no], getMediaSessionId [no], getRemoteSessionId [no], getActiveMediaSessionId [no] 435 | rtc_connection_id: RTCConnectionStore.getRTCConnectionId(), 436 | secret_key: secret_key, 437 | voice_ssrc: voice_ssrc, 438 | base_ssrc: base_ssrc, 439 | ip: ip, 440 | port: port, 441 | audio_ssrc: audio_ssrc, 442 | })); 443 | } 444 | 445 | endStream() { 446 | this.webSocket.send(JSON.stringify({ 447 | type: 'StopStream' 448 | })); 449 | } 450 | 451 | getInfo(xids) { 452 | this.webSocket.send(JSON.stringify({ 453 | type: 'GetInfo', 454 | xids: xids 455 | })); 456 | } 457 | 458 | onStop() { 459 | this.webSocket.close(); 460 | if (this._ws) { 461 | this.resetVars(); 462 | } 463 | Patcher.unpatchAll(); 464 | if (this.observer) 465 | this.observer.disconnect(); 466 | } 467 | } 468 | } -------------------------------------------------------------------------------- /daemon/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aead" 13 | version = "0.5.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 16 | dependencies = [ 17 | "crypto-common", 18 | "generic-array", 19 | ] 20 | 21 | [[package]] 22 | name = "aho-corasick" 23 | version = "1.0.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" 26 | dependencies = [ 27 | "memchr", 28 | ] 29 | 30 | [[package]] 31 | name = "android_system_properties" 32 | version = "0.1.5" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 35 | dependencies = [ 36 | "libc", 37 | ] 38 | 39 | [[package]] 40 | name = "anyhow" 41 | version = "1.0.71" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" 44 | 45 | [[package]] 46 | name = "async-tungstenite" 47 | version = "0.22.2" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58" 50 | dependencies = [ 51 | "futures-io", 52 | "futures-util", 53 | "log", 54 | "pin-project-lite", 55 | "tokio", 56 | "tungstenite", 57 | ] 58 | 59 | [[package]] 60 | name = "atomic_refcell" 61 | version = "0.1.10" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" 64 | 65 | [[package]] 66 | name = "autocfg" 67 | version = "1.1.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 70 | 71 | [[package]] 72 | name = "base64" 73 | version = "0.13.1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 76 | 77 | [[package]] 78 | name = "bit_field" 79 | version = "0.10.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 82 | 83 | [[package]] 84 | name = "bitflags" 85 | version = "1.3.2" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 88 | 89 | [[package]] 90 | name = "block-buffer" 91 | version = "0.10.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 94 | dependencies = [ 95 | "generic-array", 96 | ] 97 | 98 | [[package]] 99 | name = "bumpalo" 100 | version = "3.13.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 103 | 104 | [[package]] 105 | name = "bytemuck" 106 | version = "1.13.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" 109 | 110 | [[package]] 111 | name = "byteorder" 112 | version = "1.4.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 115 | 116 | [[package]] 117 | name = "bytes" 118 | version = "1.4.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 121 | 122 | [[package]] 123 | name = "cc" 124 | version = "1.0.79" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 127 | 128 | [[package]] 129 | name = "cfg-expr" 130 | version = "0.15.1" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "c8790cf1286da485c72cf5fc7aeba308438800036ec67d89425924c4807268c9" 133 | dependencies = [ 134 | "smallvec", 135 | "target-lexicon", 136 | ] 137 | 138 | [[package]] 139 | name = "cfg-if" 140 | version = "1.0.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 143 | 144 | [[package]] 145 | name = "chrono" 146 | version = "0.4.24" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" 149 | dependencies = [ 150 | "iana-time-zone", 151 | "js-sys", 152 | "num-integer", 153 | "num-traits", 154 | "time 0.1.45", 155 | "wasm-bindgen", 156 | "winapi", 157 | ] 158 | 159 | [[package]] 160 | name = "cipher" 161 | version = "0.4.4" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 164 | dependencies = [ 165 | "crypto-common", 166 | "inout", 167 | "zeroize", 168 | ] 169 | 170 | [[package]] 171 | name = "color_quant" 172 | version = "1.1.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 175 | 176 | [[package]] 177 | name = "core-foundation" 178 | version = "0.9.3" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 181 | dependencies = [ 182 | "core-foundation-sys", 183 | "libc", 184 | ] 185 | 186 | [[package]] 187 | name = "core-foundation-sys" 188 | version = "0.8.4" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 191 | 192 | [[package]] 193 | name = "cpufeatures" 194 | version = "0.2.7" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" 197 | dependencies = [ 198 | "libc", 199 | ] 200 | 201 | [[package]] 202 | name = "crc32fast" 203 | version = "1.3.2" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 206 | dependencies = [ 207 | "cfg-if", 208 | ] 209 | 210 | [[package]] 211 | name = "crossbeam-channel" 212 | version = "0.5.8" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 215 | dependencies = [ 216 | "cfg-if", 217 | "crossbeam-utils", 218 | ] 219 | 220 | [[package]] 221 | name = "crossbeam-deque" 222 | version = "0.8.3" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 225 | dependencies = [ 226 | "cfg-if", 227 | "crossbeam-epoch", 228 | "crossbeam-utils", 229 | ] 230 | 231 | [[package]] 232 | name = "crossbeam-epoch" 233 | version = "0.9.14" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" 236 | dependencies = [ 237 | "autocfg", 238 | "cfg-if", 239 | "crossbeam-utils", 240 | "memoffset", 241 | "scopeguard", 242 | ] 243 | 244 | [[package]] 245 | name = "crossbeam-utils" 246 | version = "0.8.15" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" 249 | dependencies = [ 250 | "cfg-if", 251 | ] 252 | 253 | [[package]] 254 | name = "crunchy" 255 | version = "0.2.2" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 258 | 259 | [[package]] 260 | name = "crypto-common" 261 | version = "0.1.6" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 264 | dependencies = [ 265 | "generic-array", 266 | "rand_core", 267 | "typenum", 268 | ] 269 | 270 | [[package]] 271 | name = "ctrlc" 272 | version = "3.3.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "04d778600249295e82b6ab12e291ed9029407efee0cfb7baf67157edc65964df" 275 | dependencies = [ 276 | "nix", 277 | "windows-sys 0.48.0", 278 | ] 279 | 280 | [[package]] 281 | name = "data-encoding" 282 | version = "2.4.0" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" 285 | 286 | [[package]] 287 | name = "derivative" 288 | version = "2.2.0" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 291 | dependencies = [ 292 | "proc-macro2", 293 | "quote", 294 | "syn 1.0.109", 295 | ] 296 | 297 | [[package]] 298 | name = "digest" 299 | version = "0.10.7" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 302 | dependencies = [ 303 | "block-buffer", 304 | "crypto-common", 305 | ] 306 | 307 | [[package]] 308 | name = "discortp" 309 | version = "0.5.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "524b9439c09174aede2c88d58cfc6b83575b06569d1af4d07562f76595b2896b" 312 | dependencies = [ 313 | "pnet_macros", 314 | "pnet_macros_support", 315 | ] 316 | 317 | [[package]] 318 | name = "either" 319 | version = "1.8.1" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 322 | 323 | [[package]] 324 | name = "errno" 325 | version = "0.3.1" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 328 | dependencies = [ 329 | "errno-dragonfly", 330 | "libc", 331 | "windows-sys 0.48.0", 332 | ] 333 | 334 | [[package]] 335 | name = "errno-dragonfly" 336 | version = "0.1.2" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 339 | dependencies = [ 340 | "cc", 341 | "libc", 342 | ] 343 | 344 | [[package]] 345 | name = "exr" 346 | version = "1.6.3" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4" 349 | dependencies = [ 350 | "bit_field", 351 | "flume", 352 | "half", 353 | "lebe", 354 | "miniz_oxide 0.6.2", 355 | "rayon-core", 356 | "smallvec", 357 | "zune-inflate", 358 | ] 359 | 360 | [[package]] 361 | name = "fastrand" 362 | version = "1.9.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 365 | dependencies = [ 366 | "instant", 367 | ] 368 | 369 | [[package]] 370 | name = "fdeflate" 371 | version = "0.3.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" 374 | dependencies = [ 375 | "simd-adler32", 376 | ] 377 | 378 | [[package]] 379 | name = "flate2" 380 | version = "1.0.26" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" 383 | dependencies = [ 384 | "crc32fast", 385 | "miniz_oxide 0.7.1", 386 | ] 387 | 388 | [[package]] 389 | name = "flume" 390 | version = "0.10.14" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" 393 | dependencies = [ 394 | "futures-core", 395 | "futures-sink", 396 | "nanorand", 397 | "pin-project", 398 | "spin", 399 | ] 400 | 401 | [[package]] 402 | name = "fnv" 403 | version = "1.0.7" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 406 | 407 | [[package]] 408 | name = "foreign-types" 409 | version = "0.3.2" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 412 | dependencies = [ 413 | "foreign-types-shared", 414 | ] 415 | 416 | [[package]] 417 | name = "foreign-types-shared" 418 | version = "0.1.1" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 421 | 422 | [[package]] 423 | name = "form_urlencoded" 424 | version = "1.1.0" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 427 | dependencies = [ 428 | "percent-encoding", 429 | ] 430 | 431 | [[package]] 432 | name = "futures-channel" 433 | version = "0.3.28" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 436 | dependencies = [ 437 | "futures-core", 438 | ] 439 | 440 | [[package]] 441 | name = "futures-core" 442 | version = "0.3.28" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 445 | 446 | [[package]] 447 | name = "futures-executor" 448 | version = "0.3.28" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 451 | dependencies = [ 452 | "futures-core", 453 | "futures-task", 454 | "futures-util", 455 | ] 456 | 457 | [[package]] 458 | name = "futures-io" 459 | version = "0.3.28" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 462 | 463 | [[package]] 464 | name = "futures-macro" 465 | version = "0.3.28" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 468 | dependencies = [ 469 | "proc-macro2", 470 | "quote", 471 | "syn 2.0.16", 472 | ] 473 | 474 | [[package]] 475 | name = "futures-sink" 476 | version = "0.3.28" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 479 | 480 | [[package]] 481 | name = "futures-task" 482 | version = "0.3.28" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 485 | 486 | [[package]] 487 | name = "futures-util" 488 | version = "0.3.28" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 491 | dependencies = [ 492 | "futures-core", 493 | "futures-macro", 494 | "futures-sink", 495 | "futures-task", 496 | "pin-project-lite", 497 | "pin-utils", 498 | "slab", 499 | ] 500 | 501 | [[package]] 502 | name = "generic-array" 503 | version = "0.14.7" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 506 | dependencies = [ 507 | "typenum", 508 | "version_check", 509 | ] 510 | 511 | [[package]] 512 | name = "getrandom" 513 | version = "0.2.9" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" 516 | dependencies = [ 517 | "cfg-if", 518 | "js-sys", 519 | "libc", 520 | "wasi 0.11.0+wasi-snapshot-preview1", 521 | "wasm-bindgen", 522 | ] 523 | 524 | [[package]] 525 | name = "gif" 526 | version = "0.12.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" 529 | dependencies = [ 530 | "color_quant", 531 | "weezl", 532 | ] 533 | 534 | [[package]] 535 | name = "gio-sys" 536 | version = "0.17.4" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "6b1d43b0d7968b48455244ecafe41192871257f5740aa6b095eb19db78e362a5" 539 | dependencies = [ 540 | "glib-sys", 541 | "gobject-sys", 542 | "libc", 543 | "system-deps", 544 | "winapi", 545 | ] 546 | 547 | [[package]] 548 | name = "glib" 549 | version = "0.17.9" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "a7f1de7cbde31ea4f0a919453a2dcece5d54d5b70e08f8ad254dc4840f5f09b6" 552 | dependencies = [ 553 | "bitflags", 554 | "futures-channel", 555 | "futures-core", 556 | "futures-executor", 557 | "futures-task", 558 | "futures-util", 559 | "gio-sys", 560 | "glib-macros", 561 | "glib-sys", 562 | "gobject-sys", 563 | "libc", 564 | "memchr", 565 | "once_cell", 566 | "smallvec", 567 | "thiserror", 568 | ] 569 | 570 | [[package]] 571 | name = "glib-macros" 572 | version = "0.17.9" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "0a7206c5c03851ef126ea1444990e81fdd6765fb799d5bc694e4897ca01bb97f" 575 | dependencies = [ 576 | "anyhow", 577 | "heck", 578 | "proc-macro-crate", 579 | "proc-macro-error", 580 | "proc-macro2", 581 | "quote", 582 | "syn 1.0.109", 583 | ] 584 | 585 | [[package]] 586 | name = "glib-sys" 587 | version = "0.17.4" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "49f00ad0a1bf548e61adfff15d83430941d9e1bb620e334f779edd1c745680a5" 590 | dependencies = [ 591 | "libc", 592 | "system-deps", 593 | ] 594 | 595 | [[package]] 596 | name = "gobject-sys" 597 | version = "0.17.4" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "15e75b0000a64632b2d8ca3cf856af9308e3a970844f6e9659bd197f026793d0" 600 | dependencies = [ 601 | "glib-sys", 602 | "libc", 603 | "system-deps", 604 | ] 605 | 606 | [[package]] 607 | name = "gst-plugin-discordstreamer" 608 | version = "0.0.1" 609 | source = "git+https://github.com/ImTheSquid/gst-discordsender#abf8429b9867bfa8397949b9c373fd16f0f4740b" 610 | dependencies = [ 611 | "byteorder", 612 | "discortp", 613 | "gst-plugin-version-helper", 614 | "gstreamer", 615 | "gstreamer-app", 616 | "gstreamer-audio", 617 | "gstreamer-base", 618 | "gstreamer-video", 619 | "once_cell", 620 | "parking_lot", 621 | "rand", 622 | "serde", 623 | "serde_plain", 624 | "xsalsa20poly1305", 625 | ] 626 | 627 | [[package]] 628 | name = "gst-plugin-version-helper" 629 | version = "0.7.5" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "87921209945e5dc809848a100115fad65bd127671896f0206f45e272080cc4c9" 632 | dependencies = [ 633 | "chrono", 634 | ] 635 | 636 | [[package]] 637 | name = "gst-plugin-ximageredux" 638 | version = "0.1.7" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "facf6e022c58db3d1bf79d8e7edb9a81805f68f5975e15aeeec4fa120153e95e" 641 | dependencies = [ 642 | "anyhow", 643 | "derivative", 644 | "gst-plugin-version-helper", 645 | "gstreamer", 646 | "gstreamer-app", 647 | "gstreamer-base", 648 | "gstreamer-video", 649 | "once_cell", 650 | "xcb", 651 | ] 652 | 653 | [[package]] 654 | name = "gstreamer" 655 | version = "0.20.5" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "4530401c89be6dc10d77ae1587b811cf455c97dce7abf594cb9164527c7da7fc" 658 | dependencies = [ 659 | "bitflags", 660 | "cfg-if", 661 | "futures-channel", 662 | "futures-core", 663 | "futures-util", 664 | "glib", 665 | "gstreamer-sys", 666 | "libc", 667 | "muldiv", 668 | "num-integer", 669 | "num-rational", 670 | "once_cell", 671 | "option-operations", 672 | "paste", 673 | "pretty-hex", 674 | "smallvec", 675 | "thiserror", 676 | ] 677 | 678 | [[package]] 679 | name = "gstreamer-app" 680 | version = "0.20.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "aa1550d18fe8d97900148cc97d63a3212c3d53169c8469b9bf617de8953c05a8" 683 | dependencies = [ 684 | "bitflags", 685 | "futures-core", 686 | "futures-sink", 687 | "glib", 688 | "gstreamer", 689 | "gstreamer-app-sys", 690 | "gstreamer-base", 691 | "libc", 692 | "once_cell", 693 | ] 694 | 695 | [[package]] 696 | name = "gstreamer-app-sys" 697 | version = "0.20.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "d7cb5375aa8c23012ec5fadde1a37b972e87680776772669d2628772867056e2" 700 | dependencies = [ 701 | "glib-sys", 702 | "gstreamer-base-sys", 703 | "gstreamer-sys", 704 | "libc", 705 | "system-deps", 706 | ] 707 | 708 | [[package]] 709 | name = "gstreamer-audio" 710 | version = "0.20.4" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "06b5a8658e575f6469053026ac663a348d5a562c9fce20ab2ca0c349e05d079e" 713 | dependencies = [ 714 | "bitflags", 715 | "cfg-if", 716 | "glib", 717 | "gstreamer", 718 | "gstreamer-audio-sys", 719 | "gstreamer-base", 720 | "libc", 721 | "once_cell", 722 | ] 723 | 724 | [[package]] 725 | name = "gstreamer-audio-sys" 726 | version = "0.20.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "9d4001b779e4707b32acd6ec0960e327b926369c1a34f7c41d477ac42b2670e8" 729 | dependencies = [ 730 | "glib-sys", 731 | "gobject-sys", 732 | "gstreamer-base-sys", 733 | "gstreamer-sys", 734 | "libc", 735 | "system-deps", 736 | ] 737 | 738 | [[package]] 739 | name = "gstreamer-base" 740 | version = "0.20.5" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "0b8ff5dfbf7bcaf1466a385b836bad0d8da25759f121458727fdda1f771c69b3" 743 | dependencies = [ 744 | "atomic_refcell", 745 | "bitflags", 746 | "cfg-if", 747 | "glib", 748 | "gstreamer", 749 | "gstreamer-base-sys", 750 | "libc", 751 | ] 752 | 753 | [[package]] 754 | name = "gstreamer-base-sys" 755 | version = "0.20.0" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "26114ed96f6668380f5a1554128159e98e06c3a7a8460f216d7cd6dce28f928c" 758 | dependencies = [ 759 | "glib-sys", 760 | "gobject-sys", 761 | "gstreamer-sys", 762 | "libc", 763 | "system-deps", 764 | ] 765 | 766 | [[package]] 767 | name = "gstreamer-sys" 768 | version = "0.20.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "e56fe047adef7d47dbafa8bc1340fddb53c325e16574763063702fc94b5786d2" 771 | dependencies = [ 772 | "glib-sys", 773 | "gobject-sys", 774 | "libc", 775 | "system-deps", 776 | ] 777 | 778 | [[package]] 779 | name = "gstreamer-video" 780 | version = "0.20.4" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "dce97769effde2d779dc4f7037b37106457b74e53f2a711bddc90b30ffeb7e06" 783 | dependencies = [ 784 | "bitflags", 785 | "cfg-if", 786 | "futures-channel", 787 | "glib", 788 | "gstreamer", 789 | "gstreamer-base", 790 | "gstreamer-video-sys", 791 | "libc", 792 | "once_cell", 793 | ] 794 | 795 | [[package]] 796 | name = "gstreamer-video-sys" 797 | version = "0.20.0" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "66ddb6112d438aac0004d2db6053a572f92b1c5e0e9d6ff6c71d9245f7f73e46" 800 | dependencies = [ 801 | "glib-sys", 802 | "gobject-sys", 803 | "gstreamer-base-sys", 804 | "gstreamer-sys", 805 | "libc", 806 | "system-deps", 807 | ] 808 | 809 | [[package]] 810 | name = "half" 811 | version = "2.2.1" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" 814 | dependencies = [ 815 | "crunchy", 816 | ] 817 | 818 | [[package]] 819 | name = "hashbrown" 820 | version = "0.12.3" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 823 | 824 | [[package]] 825 | name = "heck" 826 | version = "0.4.1" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 829 | 830 | [[package]] 831 | name = "hermit-abi" 832 | version = "0.2.6" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 835 | dependencies = [ 836 | "libc", 837 | ] 838 | 839 | [[package]] 840 | name = "hermit-abi" 841 | version = "0.3.1" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 844 | 845 | [[package]] 846 | name = "http" 847 | version = "0.2.9" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 850 | dependencies = [ 851 | "bytes", 852 | "fnv", 853 | "itoa", 854 | ] 855 | 856 | [[package]] 857 | name = "httparse" 858 | version = "1.8.0" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 861 | 862 | [[package]] 863 | name = "iana-time-zone" 864 | version = "0.1.56" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" 867 | dependencies = [ 868 | "android_system_properties", 869 | "core-foundation-sys", 870 | "iana-time-zone-haiku", 871 | "js-sys", 872 | "wasm-bindgen", 873 | "windows", 874 | ] 875 | 876 | [[package]] 877 | name = "iana-time-zone-haiku" 878 | version = "0.1.2" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 881 | dependencies = [ 882 | "cc", 883 | ] 884 | 885 | [[package]] 886 | name = "idna" 887 | version = "0.3.0" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 890 | dependencies = [ 891 | "unicode-bidi", 892 | "unicode-normalization", 893 | ] 894 | 895 | [[package]] 896 | name = "image" 897 | version = "0.24.6" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" 900 | dependencies = [ 901 | "bytemuck", 902 | "byteorder", 903 | "color_quant", 904 | "exr", 905 | "gif", 906 | "jpeg-decoder", 907 | "num-rational", 908 | "num-traits", 909 | "png", 910 | "qoi", 911 | "tiff", 912 | ] 913 | 914 | [[package]] 915 | name = "indexmap" 916 | version = "1.9.3" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 919 | dependencies = [ 920 | "autocfg", 921 | "hashbrown", 922 | ] 923 | 924 | [[package]] 925 | name = "inout" 926 | version = "0.1.3" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 929 | dependencies = [ 930 | "generic-array", 931 | ] 932 | 933 | [[package]] 934 | name = "instant" 935 | version = "0.1.12" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 938 | dependencies = [ 939 | "cfg-if", 940 | ] 941 | 942 | [[package]] 943 | name = "io-lifetimes" 944 | version = "1.0.10" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" 947 | dependencies = [ 948 | "hermit-abi 0.3.1", 949 | "libc", 950 | "windows-sys 0.48.0", 951 | ] 952 | 953 | [[package]] 954 | name = "itoa" 955 | version = "1.0.6" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 958 | 959 | [[package]] 960 | name = "jpeg-decoder" 961 | version = "0.3.0" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" 964 | dependencies = [ 965 | "rayon", 966 | ] 967 | 968 | [[package]] 969 | name = "js-sys" 970 | version = "0.3.63" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" 973 | dependencies = [ 974 | "wasm-bindgen", 975 | ] 976 | 977 | [[package]] 978 | name = "lazy_static" 979 | version = "1.4.0" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 982 | 983 | [[package]] 984 | name = "lebe" 985 | version = "0.5.2" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" 988 | 989 | [[package]] 990 | name = "libc" 991 | version = "0.2.144" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" 994 | 995 | [[package]] 996 | name = "libpulse-binding" 997 | version = "2.27.1" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "1745b20bfc194ac12ef828f144f0ec2d4a7fe993281fa3567a0bd4969aee6890" 1000 | dependencies = [ 1001 | "bitflags", 1002 | "libc", 1003 | "libpulse-sys", 1004 | "num-derive", 1005 | "num-traits", 1006 | "winapi", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "libpulse-sys" 1011 | version = "1.20.1" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "2191e6880818d1df4cf72eac8e91dce7a5a52ba0da4b2a5cdafabc22b937eadb" 1014 | dependencies = [ 1015 | "libc", 1016 | "num-derive", 1017 | "num-traits", 1018 | "pkg-config", 1019 | "winapi", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "linux-raw-sys" 1024 | version = "0.3.8" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 1027 | 1028 | [[package]] 1029 | name = "lock_api" 1030 | version = "0.4.9" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 1033 | dependencies = [ 1034 | "autocfg", 1035 | "scopeguard", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "log" 1040 | version = "0.4.17" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 1043 | dependencies = [ 1044 | "cfg-if", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "memchr" 1049 | version = "2.5.0" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 1052 | 1053 | [[package]] 1054 | name = "memoffset" 1055 | version = "0.8.0" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" 1058 | dependencies = [ 1059 | "autocfg", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "miniz_oxide" 1064 | version = "0.6.2" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" 1067 | dependencies = [ 1068 | "adler", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "miniz_oxide" 1073 | version = "0.7.1" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 1076 | dependencies = [ 1077 | "adler", 1078 | "simd-adler32", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "mio" 1083 | version = "0.8.6" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" 1086 | dependencies = [ 1087 | "libc", 1088 | "log", 1089 | "wasi 0.11.0+wasi-snapshot-preview1", 1090 | "windows-sys 0.45.0", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "muldiv" 1095 | version = "1.0.1" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" 1098 | 1099 | [[package]] 1100 | name = "nanorand" 1101 | version = "0.7.0" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 1104 | dependencies = [ 1105 | "getrandom", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "native-tls" 1110 | version = "0.2.11" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 1113 | dependencies = [ 1114 | "lazy_static", 1115 | "libc", 1116 | "log", 1117 | "openssl", 1118 | "openssl-probe", 1119 | "openssl-sys", 1120 | "schannel", 1121 | "security-framework", 1122 | "security-framework-sys", 1123 | "tempfile", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "nix" 1128 | version = "0.26.2" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" 1131 | dependencies = [ 1132 | "bitflags", 1133 | "cfg-if", 1134 | "libc", 1135 | "static_assertions", 1136 | ] 1137 | 1138 | [[package]] 1139 | name = "no-std-net" 1140 | version = "0.6.0" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" 1143 | 1144 | [[package]] 1145 | name = "ntapi" 1146 | version = "0.4.1" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" 1149 | dependencies = [ 1150 | "winapi", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "nu-ansi-term" 1155 | version = "0.46.0" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1158 | dependencies = [ 1159 | "overload", 1160 | "winapi", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "num-derive" 1165 | version = "0.3.3" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 1168 | dependencies = [ 1169 | "proc-macro2", 1170 | "quote", 1171 | "syn 1.0.109", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "num-integer" 1176 | version = "0.1.45" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 1179 | dependencies = [ 1180 | "autocfg", 1181 | "num-traits", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "num-rational" 1186 | version = "0.4.1" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" 1189 | dependencies = [ 1190 | "autocfg", 1191 | "num-integer", 1192 | "num-traits", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "num-traits" 1197 | version = "0.2.15" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 1200 | dependencies = [ 1201 | "autocfg", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "num_cpus" 1206 | version = "1.15.0" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 1209 | dependencies = [ 1210 | "hermit-abi 0.2.6", 1211 | "libc", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "once_cell" 1216 | version = "1.17.1" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 1219 | 1220 | [[package]] 1221 | name = "opaque-debug" 1222 | version = "0.3.0" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 1225 | 1226 | [[package]] 1227 | name = "openssl" 1228 | version = "0.10.52" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" 1231 | dependencies = [ 1232 | "bitflags", 1233 | "cfg-if", 1234 | "foreign-types", 1235 | "libc", 1236 | "once_cell", 1237 | "openssl-macros", 1238 | "openssl-sys", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "openssl-macros" 1243 | version = "0.1.1" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1246 | dependencies = [ 1247 | "proc-macro2", 1248 | "quote", 1249 | "syn 2.0.16", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "openssl-probe" 1254 | version = "0.1.5" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1257 | 1258 | [[package]] 1259 | name = "openssl-sys" 1260 | version = "0.9.87" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" 1263 | dependencies = [ 1264 | "cc", 1265 | "libc", 1266 | "pkg-config", 1267 | "vcpkg", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "option-operations" 1272 | version = "0.5.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" 1275 | dependencies = [ 1276 | "paste", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "overload" 1281 | version = "0.1.1" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1284 | 1285 | [[package]] 1286 | name = "parking_lot" 1287 | version = "0.12.1" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1290 | dependencies = [ 1291 | "lock_api", 1292 | "parking_lot_core", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "parking_lot_core" 1297 | version = "0.9.7" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 1300 | dependencies = [ 1301 | "cfg-if", 1302 | "libc", 1303 | "redox_syscall 0.2.16", 1304 | "smallvec", 1305 | "windows-sys 0.45.0", 1306 | ] 1307 | 1308 | [[package]] 1309 | name = "paste" 1310 | version = "1.0.12" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" 1313 | 1314 | [[package]] 1315 | name = "percent-encoding" 1316 | version = "2.2.0" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 1319 | 1320 | [[package]] 1321 | name = "pin-project" 1322 | version = "1.1.0" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" 1325 | dependencies = [ 1326 | "pin-project-internal", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "pin-project-internal" 1331 | version = "1.1.0" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" 1334 | dependencies = [ 1335 | "proc-macro2", 1336 | "quote", 1337 | "syn 2.0.16", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "pin-project-lite" 1342 | version = "0.2.9" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 1345 | 1346 | [[package]] 1347 | name = "pin-utils" 1348 | version = "0.1.0" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1351 | 1352 | [[package]] 1353 | name = "pkg-config" 1354 | version = "0.3.27" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 1357 | 1358 | [[package]] 1359 | name = "pnet_base" 1360 | version = "0.31.0" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "f9d3a993d49e5fd5d4d854d6999d4addca1f72d86c65adf224a36757161c02b6" 1363 | dependencies = [ 1364 | "no-std-net", 1365 | ] 1366 | 1367 | [[package]] 1368 | name = "pnet_macros" 1369 | version = "0.31.0" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "48dd52a5211fac27e7acb14cfc9f30ae16ae0e956b7b779c8214c74559cef4c3" 1372 | dependencies = [ 1373 | "proc-macro2", 1374 | "quote", 1375 | "regex", 1376 | "syn 1.0.109", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "pnet_macros_support" 1381 | version = "0.31.0" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "89de095dc7739349559913aed1ef6a11e73ceade4897dadc77c5e09de6740750" 1384 | dependencies = [ 1385 | "pnet_base", 1386 | ] 1387 | 1388 | [[package]] 1389 | name = "png" 1390 | version = "0.17.8" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" 1393 | dependencies = [ 1394 | "bitflags", 1395 | "crc32fast", 1396 | "fdeflate", 1397 | "flate2", 1398 | "miniz_oxide 0.7.1", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "poly1305" 1403 | version = "0.8.0" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" 1406 | dependencies = [ 1407 | "cpufeatures", 1408 | "opaque-debug", 1409 | "universal-hash", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "ppv-lite86" 1414 | version = "0.2.17" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1417 | 1418 | [[package]] 1419 | name = "pretty-hex" 1420 | version = "0.3.0" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" 1423 | 1424 | [[package]] 1425 | name = "proc-macro-crate" 1426 | version = "1.3.1" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 1429 | dependencies = [ 1430 | "once_cell", 1431 | "toml_edit", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "proc-macro-error" 1436 | version = "1.0.4" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1439 | dependencies = [ 1440 | "proc-macro-error-attr", 1441 | "proc-macro2", 1442 | "quote", 1443 | "syn 1.0.109", 1444 | "version_check", 1445 | ] 1446 | 1447 | [[package]] 1448 | name = "proc-macro-error-attr" 1449 | version = "1.0.4" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1452 | dependencies = [ 1453 | "proc-macro2", 1454 | "quote", 1455 | "version_check", 1456 | ] 1457 | 1458 | [[package]] 1459 | name = "proc-macro2" 1460 | version = "1.0.58" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" 1463 | dependencies = [ 1464 | "unicode-ident", 1465 | ] 1466 | 1467 | [[package]] 1468 | name = "qoi" 1469 | version = "0.4.1" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" 1472 | dependencies = [ 1473 | "bytemuck", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "quick-xml" 1478 | version = "0.28.2" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" 1481 | dependencies = [ 1482 | "memchr", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "quote" 1487 | version = "1.0.27" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" 1490 | dependencies = [ 1491 | "proc-macro2", 1492 | ] 1493 | 1494 | [[package]] 1495 | name = "rand" 1496 | version = "0.8.5" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1499 | dependencies = [ 1500 | "libc", 1501 | "rand_chacha", 1502 | "rand_core", 1503 | ] 1504 | 1505 | [[package]] 1506 | name = "rand_chacha" 1507 | version = "0.3.1" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1510 | dependencies = [ 1511 | "ppv-lite86", 1512 | "rand_core", 1513 | ] 1514 | 1515 | [[package]] 1516 | name = "rand_core" 1517 | version = "0.6.4" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1520 | dependencies = [ 1521 | "getrandom", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "rayon" 1526 | version = "1.7.0" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" 1529 | dependencies = [ 1530 | "either", 1531 | "rayon-core", 1532 | ] 1533 | 1534 | [[package]] 1535 | name = "rayon-core" 1536 | version = "1.11.0" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" 1539 | dependencies = [ 1540 | "crossbeam-channel", 1541 | "crossbeam-deque", 1542 | "crossbeam-utils", 1543 | "num_cpus", 1544 | ] 1545 | 1546 | [[package]] 1547 | name = "redox_syscall" 1548 | version = "0.2.16" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1551 | dependencies = [ 1552 | "bitflags", 1553 | ] 1554 | 1555 | [[package]] 1556 | name = "redox_syscall" 1557 | version = "0.3.5" 1558 | source = "registry+https://github.com/rust-lang/crates.io-index" 1559 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 1560 | dependencies = [ 1561 | "bitflags", 1562 | ] 1563 | 1564 | [[package]] 1565 | name = "regex" 1566 | version = "1.8.2" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974" 1569 | dependencies = [ 1570 | "aho-corasick", 1571 | "memchr", 1572 | "regex-syntax", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "regex-syntax" 1577 | version = "0.7.2" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 1580 | 1581 | [[package]] 1582 | name = "rustix" 1583 | version = "0.37.19" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" 1586 | dependencies = [ 1587 | "bitflags", 1588 | "errno", 1589 | "io-lifetimes", 1590 | "libc", 1591 | "linux-raw-sys", 1592 | "windows-sys 0.48.0", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "ryu" 1597 | version = "1.0.13" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 1600 | 1601 | [[package]] 1602 | name = "salsa20" 1603 | version = "0.10.2" 1604 | source = "registry+https://github.com/rust-lang/crates.io-index" 1605 | checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" 1606 | dependencies = [ 1607 | "cipher", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "schannel" 1612 | version = "0.1.21" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" 1615 | dependencies = [ 1616 | "windows-sys 0.42.0", 1617 | ] 1618 | 1619 | [[package]] 1620 | name = "scopeguard" 1621 | version = "1.1.0" 1622 | source = "registry+https://github.com/rust-lang/crates.io-index" 1623 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1624 | 1625 | [[package]] 1626 | name = "security-framework" 1627 | version = "2.9.1" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" 1630 | dependencies = [ 1631 | "bitflags", 1632 | "core-foundation", 1633 | "core-foundation-sys", 1634 | "libc", 1635 | "security-framework-sys", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "security-framework-sys" 1640 | version = "2.9.0" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" 1643 | dependencies = [ 1644 | "core-foundation-sys", 1645 | "libc", 1646 | ] 1647 | 1648 | [[package]] 1649 | name = "serde" 1650 | version = "1.0.163" 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" 1652 | checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" 1653 | dependencies = [ 1654 | "serde_derive", 1655 | ] 1656 | 1657 | [[package]] 1658 | name = "serde_derive" 1659 | version = "1.0.163" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" 1662 | dependencies = [ 1663 | "proc-macro2", 1664 | "quote", 1665 | "syn 2.0.16", 1666 | ] 1667 | 1668 | [[package]] 1669 | name = "serde_json" 1670 | version = "1.0.96" 1671 | source = "registry+https://github.com/rust-lang/crates.io-index" 1672 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 1673 | dependencies = [ 1674 | "itoa", 1675 | "ryu", 1676 | "serde", 1677 | ] 1678 | 1679 | [[package]] 1680 | name = "serde_plain" 1681 | version = "1.0.1" 1682 | source = "registry+https://github.com/rust-lang/crates.io-index" 1683 | checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" 1684 | dependencies = [ 1685 | "serde", 1686 | ] 1687 | 1688 | [[package]] 1689 | name = "serde_spanned" 1690 | version = "0.6.2" 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" 1692 | checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" 1693 | dependencies = [ 1694 | "serde", 1695 | ] 1696 | 1697 | [[package]] 1698 | name = "sha1" 1699 | version = "0.10.5" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 1702 | dependencies = [ 1703 | "cfg-if", 1704 | "cpufeatures", 1705 | "digest", 1706 | ] 1707 | 1708 | [[package]] 1709 | name = "sharded-slab" 1710 | version = "0.1.4" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1713 | dependencies = [ 1714 | "lazy_static", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "signal-hook-registry" 1719 | version = "1.4.1" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1722 | dependencies = [ 1723 | "libc", 1724 | ] 1725 | 1726 | [[package]] 1727 | name = "simd-adler32" 1728 | version = "0.3.5" 1729 | source = "registry+https://github.com/rust-lang/crates.io-index" 1730 | checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" 1731 | 1732 | [[package]] 1733 | name = "slab" 1734 | version = "0.4.8" 1735 | source = "registry+https://github.com/rust-lang/crates.io-index" 1736 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 1737 | dependencies = [ 1738 | "autocfg", 1739 | ] 1740 | 1741 | [[package]] 1742 | name = "smallvec" 1743 | version = "1.10.0" 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" 1745 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1746 | 1747 | [[package]] 1748 | name = "socket2" 1749 | version = "0.4.9" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 1752 | dependencies = [ 1753 | "libc", 1754 | "winapi", 1755 | ] 1756 | 1757 | [[package]] 1758 | name = "spin" 1759 | version = "0.9.8" 1760 | source = "registry+https://github.com/rust-lang/crates.io-index" 1761 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1762 | dependencies = [ 1763 | "lock_api", 1764 | ] 1765 | 1766 | [[package]] 1767 | name = "static_assertions" 1768 | version = "1.1.0" 1769 | source = "registry+https://github.com/rust-lang/crates.io-index" 1770 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1771 | 1772 | [[package]] 1773 | name = "subtle" 1774 | version = "2.5.0" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 1777 | 1778 | [[package]] 1779 | name = "syn" 1780 | version = "1.0.109" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1783 | dependencies = [ 1784 | "proc-macro2", 1785 | "quote", 1786 | "unicode-ident", 1787 | ] 1788 | 1789 | [[package]] 1790 | name = "syn" 1791 | version = "2.0.16" 1792 | source = "registry+https://github.com/rust-lang/crates.io-index" 1793 | checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" 1794 | dependencies = [ 1795 | "proc-macro2", 1796 | "quote", 1797 | "unicode-ident", 1798 | ] 1799 | 1800 | [[package]] 1801 | name = "sysinfo" 1802 | version = "0.26.9" 1803 | source = "registry+https://github.com/rust-lang/crates.io-index" 1804 | checksum = "5c18a6156d1f27a9592ee18c1a846ca8dd5c258b7179fc193ae87c74ebb666f5" 1805 | dependencies = [ 1806 | "cfg-if", 1807 | "core-foundation-sys", 1808 | "libc", 1809 | "ntapi", 1810 | "once_cell", 1811 | "rayon", 1812 | "winapi", 1813 | ] 1814 | 1815 | [[package]] 1816 | name = "system-deps" 1817 | version = "6.1.0" 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" 1819 | checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2" 1820 | dependencies = [ 1821 | "cfg-expr", 1822 | "heck", 1823 | "pkg-config", 1824 | "toml", 1825 | "version-compare", 1826 | ] 1827 | 1828 | [[package]] 1829 | name = "target-lexicon" 1830 | version = "0.12.7" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" 1833 | 1834 | [[package]] 1835 | name = "tempfile" 1836 | version = "3.5.0" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" 1839 | dependencies = [ 1840 | "cfg-if", 1841 | "fastrand", 1842 | "redox_syscall 0.3.5", 1843 | "rustix", 1844 | "windows-sys 0.45.0", 1845 | ] 1846 | 1847 | [[package]] 1848 | name = "thiserror" 1849 | version = "1.0.40" 1850 | source = "registry+https://github.com/rust-lang/crates.io-index" 1851 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 1852 | dependencies = [ 1853 | "thiserror-impl", 1854 | ] 1855 | 1856 | [[package]] 1857 | name = "thiserror-impl" 1858 | version = "1.0.40" 1859 | source = "registry+https://github.com/rust-lang/crates.io-index" 1860 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 1861 | dependencies = [ 1862 | "proc-macro2", 1863 | "quote", 1864 | "syn 2.0.16", 1865 | ] 1866 | 1867 | [[package]] 1868 | name = "thread_local" 1869 | version = "1.1.7" 1870 | source = "registry+https://github.com/rust-lang/crates.io-index" 1871 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 1872 | dependencies = [ 1873 | "cfg-if", 1874 | "once_cell", 1875 | ] 1876 | 1877 | [[package]] 1878 | name = "tiff" 1879 | version = "0.8.1" 1880 | source = "registry+https://github.com/rust-lang/crates.io-index" 1881 | checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" 1882 | dependencies = [ 1883 | "flate2", 1884 | "jpeg-decoder", 1885 | "weezl", 1886 | ] 1887 | 1888 | [[package]] 1889 | name = "time" 1890 | version = "0.1.45" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 1893 | dependencies = [ 1894 | "libc", 1895 | "wasi 0.10.0+wasi-snapshot-preview1", 1896 | "winapi", 1897 | ] 1898 | 1899 | [[package]] 1900 | name = "time" 1901 | version = "0.3.21" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" 1904 | dependencies = [ 1905 | "itoa", 1906 | "serde", 1907 | "time-core", 1908 | "time-macros", 1909 | ] 1910 | 1911 | [[package]] 1912 | name = "time-core" 1913 | version = "0.1.1" 1914 | source = "registry+https://github.com/rust-lang/crates.io-index" 1915 | checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" 1916 | 1917 | [[package]] 1918 | name = "time-macros" 1919 | version = "0.2.9" 1920 | source = "registry+https://github.com/rust-lang/crates.io-index" 1921 | checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" 1922 | dependencies = [ 1923 | "time-core", 1924 | ] 1925 | 1926 | [[package]] 1927 | name = "tinyvec" 1928 | version = "1.6.0" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1931 | dependencies = [ 1932 | "tinyvec_macros", 1933 | ] 1934 | 1935 | [[package]] 1936 | name = "tinyvec_macros" 1937 | version = "0.1.1" 1938 | source = "registry+https://github.com/rust-lang/crates.io-index" 1939 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1940 | 1941 | [[package]] 1942 | name = "tokio" 1943 | version = "1.28.1" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" 1946 | dependencies = [ 1947 | "autocfg", 1948 | "bytes", 1949 | "libc", 1950 | "mio", 1951 | "num_cpus", 1952 | "parking_lot", 1953 | "pin-project-lite", 1954 | "signal-hook-registry", 1955 | "socket2", 1956 | "tokio-macros", 1957 | "windows-sys 0.48.0", 1958 | ] 1959 | 1960 | [[package]] 1961 | name = "tokio-macros" 1962 | version = "2.1.0" 1963 | source = "registry+https://github.com/rust-lang/crates.io-index" 1964 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 1965 | dependencies = [ 1966 | "proc-macro2", 1967 | "quote", 1968 | "syn 2.0.16", 1969 | ] 1970 | 1971 | [[package]] 1972 | name = "tokio-native-tls" 1973 | version = "0.3.1" 1974 | source = "registry+https://github.com/rust-lang/crates.io-index" 1975 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1976 | dependencies = [ 1977 | "native-tls", 1978 | "tokio", 1979 | ] 1980 | 1981 | [[package]] 1982 | name = "toml" 1983 | version = "0.7.4" 1984 | source = "registry+https://github.com/rust-lang/crates.io-index" 1985 | checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" 1986 | dependencies = [ 1987 | "serde", 1988 | "serde_spanned", 1989 | "toml_datetime", 1990 | "toml_edit", 1991 | ] 1992 | 1993 | [[package]] 1994 | name = "toml_datetime" 1995 | version = "0.6.2" 1996 | source = "registry+https://github.com/rust-lang/crates.io-index" 1997 | checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" 1998 | dependencies = [ 1999 | "serde", 2000 | ] 2001 | 2002 | [[package]] 2003 | name = "toml_edit" 2004 | version = "0.19.9" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f" 2007 | dependencies = [ 2008 | "indexmap", 2009 | "serde", 2010 | "serde_spanned", 2011 | "toml_datetime", 2012 | "winnow", 2013 | ] 2014 | 2015 | [[package]] 2016 | name = "tracing" 2017 | version = "0.1.37" 2018 | source = "registry+https://github.com/rust-lang/crates.io-index" 2019 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 2020 | dependencies = [ 2021 | "cfg-if", 2022 | "pin-project-lite", 2023 | "tracing-attributes", 2024 | "tracing-core", 2025 | ] 2026 | 2027 | [[package]] 2028 | name = "tracing-appender" 2029 | version = "0.2.2" 2030 | source = "registry+https://github.com/rust-lang/crates.io-index" 2031 | checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" 2032 | dependencies = [ 2033 | "crossbeam-channel", 2034 | "time 0.3.21", 2035 | "tracing-subscriber", 2036 | ] 2037 | 2038 | [[package]] 2039 | name = "tracing-attributes" 2040 | version = "0.1.24" 2041 | source = "registry+https://github.com/rust-lang/crates.io-index" 2042 | checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" 2043 | dependencies = [ 2044 | "proc-macro2", 2045 | "quote", 2046 | "syn 2.0.16", 2047 | ] 2048 | 2049 | [[package]] 2050 | name = "tracing-core" 2051 | version = "0.1.31" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 2054 | dependencies = [ 2055 | "once_cell", 2056 | "valuable", 2057 | ] 2058 | 2059 | [[package]] 2060 | name = "tracing-log" 2061 | version = "0.1.3" 2062 | source = "registry+https://github.com/rust-lang/crates.io-index" 2063 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 2064 | dependencies = [ 2065 | "lazy_static", 2066 | "log", 2067 | "tracing-core", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "tracing-subscriber" 2072 | version = "0.3.17" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" 2075 | dependencies = [ 2076 | "nu-ansi-term", 2077 | "sharded-slab", 2078 | "smallvec", 2079 | "thread_local", 2080 | "tracing-core", 2081 | "tracing-log", 2082 | ] 2083 | 2084 | [[package]] 2085 | name = "tungstenite" 2086 | version = "0.19.0" 2087 | source = "registry+https://github.com/rust-lang/crates.io-index" 2088 | checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" 2089 | dependencies = [ 2090 | "byteorder", 2091 | "bytes", 2092 | "data-encoding", 2093 | "http", 2094 | "httparse", 2095 | "log", 2096 | "rand", 2097 | "sha1", 2098 | "thiserror", 2099 | "url", 2100 | "utf-8", 2101 | ] 2102 | 2103 | [[package]] 2104 | name = "tuxphones" 2105 | version = "0.1.0" 2106 | dependencies = [ 2107 | "async-tungstenite", 2108 | "base64", 2109 | "chrono", 2110 | "ctrlc", 2111 | "futures-util", 2112 | "gst-plugin-discordstreamer", 2113 | "gst-plugin-ximageredux", 2114 | "gstreamer", 2115 | "image", 2116 | "lazy_static", 2117 | "libpulse-binding", 2118 | "once_cell", 2119 | "rand", 2120 | "serde", 2121 | "serde_json", 2122 | "sysinfo", 2123 | "tokio", 2124 | "tokio-native-tls", 2125 | "tracing", 2126 | "tracing-appender", 2127 | "tracing-log", 2128 | "tracing-subscriber", 2129 | "xcb", 2130 | ] 2131 | 2132 | [[package]] 2133 | name = "typenum" 2134 | version = "1.16.0" 2135 | source = "registry+https://github.com/rust-lang/crates.io-index" 2136 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 2137 | 2138 | [[package]] 2139 | name = "unicode-bidi" 2140 | version = "0.3.13" 2141 | source = "registry+https://github.com/rust-lang/crates.io-index" 2142 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 2143 | 2144 | [[package]] 2145 | name = "unicode-ident" 2146 | version = "1.0.8" 2147 | source = "registry+https://github.com/rust-lang/crates.io-index" 2148 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 2149 | 2150 | [[package]] 2151 | name = "unicode-normalization" 2152 | version = "0.1.22" 2153 | source = "registry+https://github.com/rust-lang/crates.io-index" 2154 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 2155 | dependencies = [ 2156 | "tinyvec", 2157 | ] 2158 | 2159 | [[package]] 2160 | name = "universal-hash" 2161 | version = "0.5.1" 2162 | source = "registry+https://github.com/rust-lang/crates.io-index" 2163 | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 2164 | dependencies = [ 2165 | "crypto-common", 2166 | "subtle", 2167 | ] 2168 | 2169 | [[package]] 2170 | name = "url" 2171 | version = "2.3.1" 2172 | source = "registry+https://github.com/rust-lang/crates.io-index" 2173 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 2174 | dependencies = [ 2175 | "form_urlencoded", 2176 | "idna", 2177 | "percent-encoding", 2178 | ] 2179 | 2180 | [[package]] 2181 | name = "utf-8" 2182 | version = "0.7.6" 2183 | source = "registry+https://github.com/rust-lang/crates.io-index" 2184 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2185 | 2186 | [[package]] 2187 | name = "valuable" 2188 | version = "0.1.0" 2189 | source = "registry+https://github.com/rust-lang/crates.io-index" 2190 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 2191 | 2192 | [[package]] 2193 | name = "vcpkg" 2194 | version = "0.2.15" 2195 | source = "registry+https://github.com/rust-lang/crates.io-index" 2196 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2197 | 2198 | [[package]] 2199 | name = "version-compare" 2200 | version = "0.1.1" 2201 | source = "registry+https://github.com/rust-lang/crates.io-index" 2202 | checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" 2203 | 2204 | [[package]] 2205 | name = "version_check" 2206 | version = "0.9.4" 2207 | source = "registry+https://github.com/rust-lang/crates.io-index" 2208 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2209 | 2210 | [[package]] 2211 | name = "wasi" 2212 | version = "0.10.0+wasi-snapshot-preview1" 2213 | source = "registry+https://github.com/rust-lang/crates.io-index" 2214 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 2215 | 2216 | [[package]] 2217 | name = "wasi" 2218 | version = "0.11.0+wasi-snapshot-preview1" 2219 | source = "registry+https://github.com/rust-lang/crates.io-index" 2220 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2221 | 2222 | [[package]] 2223 | name = "wasm-bindgen" 2224 | version = "0.2.86" 2225 | source = "registry+https://github.com/rust-lang/crates.io-index" 2226 | checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" 2227 | dependencies = [ 2228 | "cfg-if", 2229 | "wasm-bindgen-macro", 2230 | ] 2231 | 2232 | [[package]] 2233 | name = "wasm-bindgen-backend" 2234 | version = "0.2.86" 2235 | source = "registry+https://github.com/rust-lang/crates.io-index" 2236 | checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" 2237 | dependencies = [ 2238 | "bumpalo", 2239 | "log", 2240 | "once_cell", 2241 | "proc-macro2", 2242 | "quote", 2243 | "syn 2.0.16", 2244 | "wasm-bindgen-shared", 2245 | ] 2246 | 2247 | [[package]] 2248 | name = "wasm-bindgen-macro" 2249 | version = "0.2.86" 2250 | source = "registry+https://github.com/rust-lang/crates.io-index" 2251 | checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" 2252 | dependencies = [ 2253 | "quote", 2254 | "wasm-bindgen-macro-support", 2255 | ] 2256 | 2257 | [[package]] 2258 | name = "wasm-bindgen-macro-support" 2259 | version = "0.2.86" 2260 | source = "registry+https://github.com/rust-lang/crates.io-index" 2261 | checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" 2262 | dependencies = [ 2263 | "proc-macro2", 2264 | "quote", 2265 | "syn 2.0.16", 2266 | "wasm-bindgen-backend", 2267 | "wasm-bindgen-shared", 2268 | ] 2269 | 2270 | [[package]] 2271 | name = "wasm-bindgen-shared" 2272 | version = "0.2.86" 2273 | source = "registry+https://github.com/rust-lang/crates.io-index" 2274 | checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" 2275 | 2276 | [[package]] 2277 | name = "weezl" 2278 | version = "0.1.7" 2279 | source = "registry+https://github.com/rust-lang/crates.io-index" 2280 | checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" 2281 | 2282 | [[package]] 2283 | name = "winapi" 2284 | version = "0.3.9" 2285 | source = "registry+https://github.com/rust-lang/crates.io-index" 2286 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2287 | dependencies = [ 2288 | "winapi-i686-pc-windows-gnu", 2289 | "winapi-x86_64-pc-windows-gnu", 2290 | ] 2291 | 2292 | [[package]] 2293 | name = "winapi-i686-pc-windows-gnu" 2294 | version = "0.4.0" 2295 | source = "registry+https://github.com/rust-lang/crates.io-index" 2296 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2297 | 2298 | [[package]] 2299 | name = "winapi-x86_64-pc-windows-gnu" 2300 | version = "0.4.0" 2301 | source = "registry+https://github.com/rust-lang/crates.io-index" 2302 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2303 | 2304 | [[package]] 2305 | name = "windows" 2306 | version = "0.48.0" 2307 | source = "registry+https://github.com/rust-lang/crates.io-index" 2308 | checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" 2309 | dependencies = [ 2310 | "windows-targets 0.48.0", 2311 | ] 2312 | 2313 | [[package]] 2314 | name = "windows-sys" 2315 | version = "0.42.0" 2316 | source = "registry+https://github.com/rust-lang/crates.io-index" 2317 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 2318 | dependencies = [ 2319 | "windows_aarch64_gnullvm 0.42.2", 2320 | "windows_aarch64_msvc 0.42.2", 2321 | "windows_i686_gnu 0.42.2", 2322 | "windows_i686_msvc 0.42.2", 2323 | "windows_x86_64_gnu 0.42.2", 2324 | "windows_x86_64_gnullvm 0.42.2", 2325 | "windows_x86_64_msvc 0.42.2", 2326 | ] 2327 | 2328 | [[package]] 2329 | name = "windows-sys" 2330 | version = "0.45.0" 2331 | source = "registry+https://github.com/rust-lang/crates.io-index" 2332 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 2333 | dependencies = [ 2334 | "windows-targets 0.42.2", 2335 | ] 2336 | 2337 | [[package]] 2338 | name = "windows-sys" 2339 | version = "0.48.0" 2340 | source = "registry+https://github.com/rust-lang/crates.io-index" 2341 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2342 | dependencies = [ 2343 | "windows-targets 0.48.0", 2344 | ] 2345 | 2346 | [[package]] 2347 | name = "windows-targets" 2348 | version = "0.42.2" 2349 | source = "registry+https://github.com/rust-lang/crates.io-index" 2350 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 2351 | dependencies = [ 2352 | "windows_aarch64_gnullvm 0.42.2", 2353 | "windows_aarch64_msvc 0.42.2", 2354 | "windows_i686_gnu 0.42.2", 2355 | "windows_i686_msvc 0.42.2", 2356 | "windows_x86_64_gnu 0.42.2", 2357 | "windows_x86_64_gnullvm 0.42.2", 2358 | "windows_x86_64_msvc 0.42.2", 2359 | ] 2360 | 2361 | [[package]] 2362 | name = "windows-targets" 2363 | version = "0.48.0" 2364 | source = "registry+https://github.com/rust-lang/crates.io-index" 2365 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 2366 | dependencies = [ 2367 | "windows_aarch64_gnullvm 0.48.0", 2368 | "windows_aarch64_msvc 0.48.0", 2369 | "windows_i686_gnu 0.48.0", 2370 | "windows_i686_msvc 0.48.0", 2371 | "windows_x86_64_gnu 0.48.0", 2372 | "windows_x86_64_gnullvm 0.48.0", 2373 | "windows_x86_64_msvc 0.48.0", 2374 | ] 2375 | 2376 | [[package]] 2377 | name = "windows_aarch64_gnullvm" 2378 | version = "0.42.2" 2379 | source = "registry+https://github.com/rust-lang/crates.io-index" 2380 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 2381 | 2382 | [[package]] 2383 | name = "windows_aarch64_gnullvm" 2384 | version = "0.48.0" 2385 | source = "registry+https://github.com/rust-lang/crates.io-index" 2386 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 2387 | 2388 | [[package]] 2389 | name = "windows_aarch64_msvc" 2390 | version = "0.42.2" 2391 | source = "registry+https://github.com/rust-lang/crates.io-index" 2392 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 2393 | 2394 | [[package]] 2395 | name = "windows_aarch64_msvc" 2396 | version = "0.48.0" 2397 | source = "registry+https://github.com/rust-lang/crates.io-index" 2398 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 2399 | 2400 | [[package]] 2401 | name = "windows_i686_gnu" 2402 | version = "0.42.2" 2403 | source = "registry+https://github.com/rust-lang/crates.io-index" 2404 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 2405 | 2406 | [[package]] 2407 | name = "windows_i686_gnu" 2408 | version = "0.48.0" 2409 | source = "registry+https://github.com/rust-lang/crates.io-index" 2410 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 2411 | 2412 | [[package]] 2413 | name = "windows_i686_msvc" 2414 | version = "0.42.2" 2415 | source = "registry+https://github.com/rust-lang/crates.io-index" 2416 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 2417 | 2418 | [[package]] 2419 | name = "windows_i686_msvc" 2420 | version = "0.48.0" 2421 | source = "registry+https://github.com/rust-lang/crates.io-index" 2422 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 2423 | 2424 | [[package]] 2425 | name = "windows_x86_64_gnu" 2426 | version = "0.42.2" 2427 | source = "registry+https://github.com/rust-lang/crates.io-index" 2428 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 2429 | 2430 | [[package]] 2431 | name = "windows_x86_64_gnu" 2432 | version = "0.48.0" 2433 | source = "registry+https://github.com/rust-lang/crates.io-index" 2434 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 2435 | 2436 | [[package]] 2437 | name = "windows_x86_64_gnullvm" 2438 | version = "0.42.2" 2439 | source = "registry+https://github.com/rust-lang/crates.io-index" 2440 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 2441 | 2442 | [[package]] 2443 | name = "windows_x86_64_gnullvm" 2444 | version = "0.48.0" 2445 | source = "registry+https://github.com/rust-lang/crates.io-index" 2446 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 2447 | 2448 | [[package]] 2449 | name = "windows_x86_64_msvc" 2450 | version = "0.42.2" 2451 | source = "registry+https://github.com/rust-lang/crates.io-index" 2452 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 2453 | 2454 | [[package]] 2455 | name = "windows_x86_64_msvc" 2456 | version = "0.48.0" 2457 | source = "registry+https://github.com/rust-lang/crates.io-index" 2458 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 2459 | 2460 | [[package]] 2461 | name = "winnow" 2462 | version = "0.4.6" 2463 | source = "registry+https://github.com/rust-lang/crates.io-index" 2464 | checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" 2465 | dependencies = [ 2466 | "memchr", 2467 | ] 2468 | 2469 | [[package]] 2470 | name = "xcb" 2471 | version = "1.2.1" 2472 | source = "registry+https://github.com/rust-lang/crates.io-index" 2473 | checksum = "4b90c622d513012e7419594a2138953603c63848cb189041e7b5dc04d3895da5" 2474 | dependencies = [ 2475 | "bitflags", 2476 | "libc", 2477 | "quick-xml", 2478 | ] 2479 | 2480 | [[package]] 2481 | name = "xsalsa20poly1305" 2482 | version = "0.9.1" 2483 | source = "registry+https://github.com/rust-lang/crates.io-index" 2484 | checksum = "02a6dad357567f81cd78ee75f7c61f1b30bb2fe4390be8fb7c69e2ac8dffb6c7" 2485 | dependencies = [ 2486 | "aead", 2487 | "poly1305", 2488 | "salsa20", 2489 | "subtle", 2490 | "zeroize", 2491 | ] 2492 | 2493 | [[package]] 2494 | name = "zeroize" 2495 | version = "1.6.0" 2496 | source = "registry+https://github.com/rust-lang/crates.io-index" 2497 | checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" 2498 | 2499 | [[package]] 2500 | name = "zune-inflate" 2501 | version = "0.2.54" 2502 | source = "registry+https://github.com/rust-lang/crates.io-index" 2503 | checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" 2504 | dependencies = [ 2505 | "simd-adler32", 2506 | ] 2507 | -------------------------------------------------------------------------------- /daemon/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tuxphones" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Daemon for Tuxphones BetterDiscord plugin" 7 | homepage = "https://github.com/ImTheSquid/Tuxphones" 8 | repository = "https://github.com/ImTheSquid/Tuxphones" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | libpulse-binding = "2.27.1" 14 | gst = { package = "gstreamer", version = "0.20.5", features = ["v1_18"] } 15 | once_cell = "1.17.1" 16 | serde = { version = "1.0", features = ["derive"] } 17 | serde_json = "1.0" 18 | ctrlc={ version = "3.2.5", features = ["termination"] } 19 | xcb = { version = "1.2.1", features = ["res"] } 20 | sysinfo = "0.26.9" 21 | tracing = "0.1.37" 22 | tracing-subscriber = "0.3.17" 23 | futures-util = "0.3.28" 24 | lazy_static = "1.4.0" 25 | rand = "0.8.5" 26 | image = "0.24.6" 27 | base64 = "0.13.1" 28 | tokio = { version = "1.28.0", features = ["full"] } 29 | tokio-native-tls = "0.3.1" 30 | tracing-log = "0.1.3" 31 | tracing-appender = "0.2.2" 32 | chrono = "0.4.24" 33 | gst-plugin-ximageredux = "0.1.7" 34 | async-tungstenite = { version = "0.22.1", features = ["tokio-runtime"] } 35 | gst-plugin-discordstreamer = {git = "https://github.com/ImTheSquid/gst-discordsender"} -------------------------------------------------------------------------------- /daemon/src/gstreamer.rs: -------------------------------------------------------------------------------- 1 | use discordstreamer::discordstreamer::DiscordStreamer; 2 | use gst::prelude::*; 3 | use gst::{ 4 | debug_bin_to_dot_data, glib, DebugGraphDetails, Element, PadLinkError, StateChangeError, 5 | StateChangeSuccess, 6 | }; 7 | use gst::subclass::prelude::ObjectSubclassIsExt; 8 | use image::EncodableLayout; 9 | use tracing::{debug, error, info, trace}; 10 | use tracing_log::log::Level; 11 | 12 | use crate::{ 13 | socket::{StreamResolutionInformation}, 14 | xid, 15 | }; 16 | 17 | #[derive(Debug)] 18 | pub enum GstInitializationError { 19 | Init(glib::Error), 20 | Element(glib::BoolError), 21 | Pad(PadLinkError), 22 | } 23 | 24 | #[derive(Debug)] 25 | pub struct StreamSSRCs { 26 | pub audio: u32, 27 | pub video: u32, 28 | pub rtx: u32, 29 | } 30 | 31 | impl std::fmt::Display for GstInitializationError { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | let str = match self { 34 | GstInitializationError::Init(e) => format!("Initialization error: {:?}", e), 35 | GstInitializationError::Element(e) => format!("Element error: {:?}", e), 36 | GstInitializationError::Pad(e) => format!("Pad error: {:?}", e), 37 | }; 38 | f.write_str(&str) 39 | } 40 | } 41 | 42 | #[derive(Clone, Copy)] 43 | pub struct H264Settings { 44 | pub nvidia_encoder: bool, 45 | } 46 | 47 | #[derive(Clone, Copy)] 48 | #[allow(dead_code)] 49 | pub enum VideoEncoderType { 50 | H264(H264Settings), 51 | VP8, 52 | VP9, 53 | } 54 | 55 | impl From for GstInitializationError { 56 | fn from(error: glib::Error) -> Self { 57 | GstInitializationError::Init(error) 58 | } 59 | } 60 | 61 | impl From for GstInitializationError { 62 | fn from(error: glib::BoolError) -> Self { 63 | GstInitializationError::Element(error) 64 | } 65 | } 66 | 67 | impl From for GstInitializationError { 68 | fn from(error: PadLinkError) -> Self { 69 | GstInitializationError::Pad(error) 70 | } 71 | } 72 | 73 | pub struct GstHandle { 74 | pipeline: gst::Pipeline, 75 | encoder: Element, 76 | encoder_type: VideoEncoderType, 77 | } 78 | 79 | //Custom drop logic to deinit gstreamer when all handles are dropped 80 | impl Drop for GstHandle { 81 | fn drop(&mut self) { 82 | info!("dropping GstHandle"); 83 | // Debug diagram 84 | let out = debug_bin_to_dot_data(&self.pipeline, DebugGraphDetails::ALL); 85 | //TODO: Move to logs folder 86 | std::fs::write("/tmp/tuxphones_gstdrop.dot", out.as_str()).unwrap(); 87 | 88 | if let Err(e) = self.pipeline.set_state(gst::State::Null) { 89 | error!("Failed to stop pipeline: {:?}", e); 90 | }; 91 | } 92 | } 93 | 94 | impl GstHandle { 95 | pub async fn new( 96 | encoder_to_use: VideoEncoderType, 97 | xid: xid, 98 | resolution: StreamResolutionInformation, 99 | fps: i32, 100 | secret_key: Vec, 101 | base_ssrc: u32, 102 | address: String, 103 | ) -> Result { 104 | info!("Creating new GstHandle"); 105 | //Create a new GStreamer pipeline 106 | let pipeline = gst::Pipeline::new(None); 107 | 108 | //--VIDEO-- 109 | 110 | //Create a new ximagesrc to get video from the X server 111 | let ximagesrc = ximageredux::XImageRedux::default(); 112 | 113 | let videoscale = gst::ElementFactory::make("videoscale").build()?; 114 | 115 | //Creating a capsfilter to set the resolution and the fps 116 | let capsfilter = gst::ElementFactory::make("capsfilter").build()?; 117 | 118 | let mut cap = gst::Caps::builder("video/x-raw") 119 | .field("frame_rate", gst::Fraction::new(fps, 1)); 120 | 121 | //If the resolution is specified, add it to the caps 122 | if resolution.is_fixed { 123 | cap = cap 124 | .field("width", resolution.width as i32) 125 | .field("height", resolution.height as i32); 126 | }; 127 | 128 | capsfilter.set_property( 129 | "caps", 130 | &cap.build(), 131 | ); 132 | 133 | // ximagesrc.set_property_from_str("show-pointer", "1"); 134 | //Set xid based on constructor parameter to get video only from the specified X window 135 | ximagesrc.set_property("xid", xid as u32); 136 | 137 | //Create a new videoconvert to allow encoding of the raw video 138 | let videoconvert = gst::ElementFactory::make("videoconvert").build()?; 139 | 140 | //Chose encoder based on constructor params 141 | let encoder = match encoder_to_use { 142 | VideoEncoderType::H264(settings) => { 143 | //Use nvidia encoder based on settings 144 | if settings.nvidia_encoder { 145 | let nvh264enc = gst::ElementFactory::make("nvh264enc").build()?; 146 | nvh264enc.set_property("gop-size", 2560i32); 147 | nvh264enc.set_property_from_str("rc-mode", "cbr-ld-hq"); 148 | nvh264enc.set_property("zerolatency", true); 149 | nvh264enc 150 | } else { 151 | let x264enc = gst::ElementFactory::make("x264enc").build()?; 152 | x264enc.set_property("threads", 12u32); 153 | x264enc.set_property_from_str("tune", "zerolatency"); 154 | x264enc.set_property_from_str("speed-preset", "ultrafast"); 155 | x264enc.set_property("key-int-max", 2560u32); 156 | x264enc.set_property("b-adapt", false); 157 | x264enc.set_property("vbv-buf-capacity", 120u32); 158 | x264enc 159 | } 160 | } 161 | VideoEncoderType::VP8 => { 162 | let vp8enc = gst::ElementFactory::make("vp8enc").build()?; 163 | vp8enc.set_property("threads", 12i32); 164 | vp8enc.set_property("cpu-used", -16i32); 165 | vp8enc.set_property_from_str("end-usage", "cbr"); 166 | vp8enc.set_property("buffer-initial-size", 100i32); 167 | vp8enc.set_property("buffer-optimal-size", 120i32); 168 | vp8enc.set_property("buffer-size", 150i32); 169 | vp8enc.set_property("max-intra-bitrate", 250i32); 170 | vp8enc.set_property_from_str("error-resilient", "default"); 171 | vp8enc.set_property("lag-in-frames", 0i32); 172 | vp8enc 173 | } 174 | VideoEncoderType::VP9 => { 175 | let vp9enc = gst::ElementFactory::make("vp9enc").build()?; 176 | vp9enc.set_property("threads", 12i32); 177 | vp9enc.set_property("cpu-used", -16i32); 178 | vp9enc.set_property_from_str("end-usage", "cbr"); 179 | vp9enc.set_property("buffer-initial-size", 100i32); 180 | vp9enc.set_property("buffer-optimal-size", 120i32); 181 | vp9enc.set_property("buffer-size", 150i32); 182 | vp9enc.set_property("max-intra-bitrate", 250i32); 183 | vp9enc.set_property_from_str("error-resilient", "default"); 184 | vp9enc.set_property("lag-in-frames", 0i32); 185 | vp9enc 186 | } 187 | }; 188 | 189 | //--AUDIO-- 190 | 191 | // Caps filter for audio from conversion to encoding 192 | let audio_capsfilter = gst::ElementFactory::make("capsfilter").build()?; 193 | 194 | let cap = gst::Caps::builder("audio/x-raw") 195 | .field("channels", 2) 196 | .field("rate", 48000); 197 | 198 | audio_capsfilter.set_property( 199 | "caps", 200 | &cap.build(), 201 | ); 202 | 203 | //Create a new pulsesrc to get audio from the PulseAudio server 204 | let pulsesrc = gst::ElementFactory::make("pulsesrc").build()?; 205 | //Set the audio device based on constructor parameter (should be the sink of the audio application) 206 | pulsesrc.set_property_from_str("device", "tuxphones.monitor"); 207 | 208 | //Create a new audioconvert to allow encoding of the raw audio 209 | let audioconvert = gst::ElementFactory::make("audioconvert").build()?; 210 | //Encoder for the raw audio to opus 211 | let opusenc = gst::ElementFactory::make("opusenc").build()?; 212 | opusenc.set_property("bitrate", 32000i32); 213 | opusenc.set_property_from_str("bitrate-type", "cbr"); 214 | opusenc.set_property("inband-fec", true); 215 | opusenc.set_property("packet-loss-percentage", 50); 216 | 217 | //DESTINATION 218 | let discord_streamer = DiscordStreamer::default(); 219 | let video_ssrc = base_ssrc; 220 | let audio_ssrc = base_ssrc + 1; 221 | discord_streamer.set_property("crypto-key", glib::Bytes::from(secret_key.as_bytes())); 222 | discord_streamer.set_property("address", address.to_value()); 223 | discord_streamer.set_property("video-ssrc", video_ssrc.to_value()); 224 | discord_streamer.set_property("audio-ssrc", audio_ssrc.to_value()); 225 | debug!("DiscordStreamer created"); 226 | trace!("DiscordStreamer address: {:?}", address); 227 | trace!("DiscordStreamer video-ssrc: {:?}", base_ssrc); 228 | trace!("DiscordStreamer audio-ssrc: {:?}", base_ssrc+1); 229 | trace!("DiscordStreamer crypto-key: {:?}", secret_key); 230 | 231 | 232 | //queues 233 | let video_encoder_queue = gst::ElementFactory::make("queue").build()?; 234 | let audio_encoder_queue = gst::ElementFactory::make("queue").build()?; 235 | let video_webrtc_queue = gst::ElementFactory::make("queue").build()?; 236 | let audio_webrtc_queue = gst::ElementFactory::make("queue").build()?; 237 | 238 | //Add elements to the pipeline 239 | pipeline.add_many(&[ 240 | ximagesrc.upcast_ref::(), 241 | &videoscale, 242 | &capsfilter, 243 | &videoconvert, 244 | &encoder, 245 | &video_encoder_queue, 246 | &video_webrtc_queue, 247 | /*&pulsesrc, 248 | &audioconvert, 249 | &audio_capsfilter, 250 | &opusenc, 251 | &audio_encoder_queue, 252 | &audio_webrtc_queue, 253 | */ 254 | discord_streamer.upcast_ref::(), 255 | ])?; 256 | 257 | //Link video elements 258 | Element::link_many(&[ 259 | ximagesrc.upcast_ref::(), 260 | &videoscale, 261 | &capsfilter, 262 | &videoconvert, 263 | &video_encoder_queue, 264 | &encoder, 265 | &video_webrtc_queue, 266 | discord_streamer.upcast_ref::(), 267 | ])?; 268 | 269 | /* //Link audio elements 270 | Element::link_many(&[ 271 | &pulsesrc, 272 | &audio_capsfilter, 273 | &audioconvert, 274 | &audio_encoder_queue, 275 | &opusenc, 276 | &audio_webrtc_queue, 277 | discord_streamer.upcast_ref::(), 278 | ])?; 279 | */ 280 | // Debug diagram 281 | let out = debug_bin_to_dot_data(&pipeline, DebugGraphDetails::ALL); 282 | //TODO: Move to logs folder 283 | std::fs::write("/tmp/tuxphones_gst.dot", out.as_str()).unwrap(); 284 | 285 | Ok(GstHandle { 286 | pipeline, 287 | encoder, 288 | encoder_type: encoder_to_use, 289 | }) 290 | } 291 | 292 | pub async fn start( 293 | &self, 294 | ) -> Result { 295 | self.pipeline.set_state(gst::State::Playing)?; 296 | 297 | Ok(StateChangeSuccess::Success) 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /daemon/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::{ 3 | atomic::{AtomicBool, Ordering}, 4 | Arc, 5 | }, 6 | time::{self, Duration}, 7 | }; 8 | 9 | use sysinfo::{Pid, PidExt, Process, ProcessExt, SystemExt}; 10 | use tracing::{error, info}; 11 | 12 | use pulse::PulseHandle; 13 | use socket::{Application, SocketListenerCommand, WebSocket}; 14 | use x::XServerHandle; 15 | // Makes sure typing is preserved 16 | use u32 as pid; 17 | use u32 as xid; 18 | 19 | use crate::gstreamer::{GstHandle, H264Settings, VideoEncoderType}; 20 | 21 | use tokio::{ 22 | sync::{ 23 | mpsc::{self, Receiver}, 24 | Mutex, 25 | }, 26 | time::sleep, 27 | }; 28 | 29 | mod gstreamer; 30 | mod pulse; 31 | pub mod socket; 32 | mod x; 33 | 34 | pub struct CommandProcessor { 35 | thread: Option>, 36 | } 37 | 38 | impl CommandProcessor { 39 | pub fn new( 40 | mut receiver: Receiver, 41 | run: Arc, 42 | sleep_time: Duration, 43 | websocket: Arc>, 44 | ) -> Self { 45 | let thread = tokio::spawn(async move { 46 | let mut pulse = match PulseHandle::new() { 47 | Ok(handle) => handle, 48 | Err(e) => { 49 | error!("Pulse error: {}", e); 50 | run.store(false, Ordering::SeqCst); 51 | return; 52 | } 53 | }; 54 | 55 | let x = match XServerHandle::new() { 56 | Ok(handle) => handle, 57 | Err(e) => { 58 | error!("X Server error: {}", e); 59 | run.store(false, Ordering::SeqCst); 60 | return; 61 | } 62 | }; 63 | 64 | let mut last_stream_preview: Option = None; 65 | let mut current_xid = None; 66 | 67 | let mut gst_is_loaded = false; 68 | 69 | let mut stream = None; 70 | 71 | loop { 72 | if !run.load(Ordering::SeqCst) { 73 | // Kill websocket if still running 74 | stream.take(); 75 | current_xid.take(); 76 | if gst_is_loaded { 77 | unsafe { 78 | gst::deinit(); 79 | } 80 | } 81 | info!("Command processor shut down"); 82 | break; 83 | } 84 | 85 | match receiver.try_recv() { 86 | Ok(cmd) => { 87 | match cmd { 88 | SocketListenerCommand::StartStream { 89 | pid, 90 | xid, 91 | resolution, 92 | framerate, 93 | rtc_connection_id, 94 | secret_key, 95 | voice_ssrc, 96 | base_ssrc, 97 | ip, 98 | port, 99 | } => { 100 | info!("[StartStream] Command received"); 101 | match pulse.setup_audio_capture(None) { 102 | Ok(_) => {} 103 | Err(e) => { 104 | error!("Failed to setup pulse capture: {}", e); 105 | continue; 106 | } 107 | } 108 | 109 | match pulse.start_capture(pid) { 110 | Ok(_) => {} 111 | Err(e) => { 112 | error!("Failed to start pulse capture: {}", e); 113 | continue; 114 | } 115 | } 116 | 117 | let _ = current_xid.insert(xid); 118 | 119 | // Quick and drity check to try to detect Nvidia drivers 120 | // TODO: Find a better way to do this 121 | //let nvidia_encoder = if let Ok(out) = Command::new("lspci").arg("-nnk").output() { 122 | // String::from_utf8_lossy(&out.stdout).contains("nvidia") 123 | //} else { false }; 124 | 125 | if !gst_is_loaded { 126 | gst_is_loaded = true; 127 | gst::init().expect("Failed to intialize gstreamer"); 128 | } 129 | 130 | let gst = GstHandle::new( 131 | VideoEncoderType::H264(H264Settings { 132 | nvidia_encoder: false, 133 | }), 134 | xid, 135 | resolution.clone(), 136 | framerate.into(), 137 | secret_key, 138 | base_ssrc, 139 | format!("{}:{}", ip, port), 140 | ) 141 | .await 142 | .expect("Failed to initialize gstreamer pipeline"); 143 | gst.start() 144 | .await 145 | .expect("Failed to start stream"); 146 | 147 | let _ = stream.insert(gst); 148 | 149 | info!("[StartStream] Command processed (stream started)"); 150 | } 151 | SocketListenerCommand::StopStream 152 | | SocketListenerCommand::StopStreamInternal => { 153 | info!("[StopStream] Command received"); 154 | 155 | // Kill gstreamer 156 | stream.take(); 157 | 158 | pulse.stop_capture(); 159 | pulse.teardown_audio_capture(); 160 | 161 | info!("[StopStream] Command processed (stream stopped)"); 162 | 163 | // If stream was stopped internally, send a notification to the client 164 | if cmd == SocketListenerCommand::StopStreamInternal { 165 | if let Err(e) = 166 | websocket.lock().await.stream_stop_internal().await 167 | { 168 | error!( 169 | "Failed to notify client of internal stream stop: {:?}", 170 | e 171 | ); 172 | } 173 | } 174 | } 175 | SocketListenerCommand::GetInfo { xids } => { 176 | info!("[GetInfo] Command received"); 177 | 178 | // Find all PIDs of given XIDs 179 | let xid_pid: Vec<(xid, pid)> = xids 180 | .into_iter() 181 | .filter_map(|xid| { 182 | if let Ok(Some(pid)) = x.pid_from_xid(xid) { 183 | return Some((xid, pid)); 184 | } 185 | 186 | None 187 | }) 188 | .collect(); 189 | 190 | // Do initial matching against returned Pulse PIDs 191 | let mut apps = pulse.get_audio_applications(); 192 | let mut found_applications = vec![]; 193 | for (xid, pid) in &xid_pid { 194 | if let Some(idx) = apps.iter().position(|app| app.pid == *pid) { 195 | let app = apps.remove(idx); 196 | found_applications.push(Application { 197 | name: app.name, 198 | pid: *pid, 199 | xid: *xid, 200 | }); 201 | } 202 | } 203 | 204 | // If there are more Pulse applications to resolve, lookup process name and try to find pair with given PID for XID 205 | // Find all processes with given name 206 | let mut system = sysinfo::System::new(); 207 | system.refresh_processes(); 208 | let processes_with_cmd: Vec<(&Pid, &Process)> = system 209 | .processes() 210 | .iter() 211 | .filter(|(_, p)| !p.cmd().is_empty()) 212 | .collect(); 213 | 214 | for app in &apps { 215 | for (proc_pid, process) in &processes_with_cmd { 216 | let cmd_strings: Vec<&str> = 217 | process.cmd()[0].split(' ').collect(); 218 | // If the command matches the Pulse application name 219 | if cmd_strings[0].ends_with(&format!("/{}", &app.name)) { 220 | // And the PID of an XID window matches the PID of the found process 221 | if let Some((xid, _)) = xid_pid 222 | .iter() 223 | .find(|(_, pid)| *pid == proc_pid.as_u32()) 224 | { 225 | // Push the application and go to the next one 226 | found_applications.push(Application { 227 | name: app.name.clone(), 228 | pid: app.pid, 229 | xid: *xid, 230 | }); 231 | break; 232 | } 233 | } 234 | } 235 | } 236 | 237 | match websocket 238 | .lock() 239 | .await 240 | .application_info(&found_applications) 241 | .await 242 | { 243 | Ok(_) => info!( 244 | "[GetInfo] Command processed (applications found: {})", 245 | found_applications.len() 246 | ), 247 | Err(e) => error!("Failed to send application data: {}", e), 248 | } 249 | } 250 | } 251 | } 252 | Err(e) => match e { 253 | mpsc::error::TryRecvError::Disconnected => { 254 | error!("Failed to watch for receiver: {}", e); 255 | run.store(false, Ordering::SeqCst); 256 | break; 257 | } 258 | mpsc::error::TryRecvError::Empty => { 259 | // Check if time to send a stream preview 260 | let send_preview = if stream.is_some() { 261 | if let Some(last) = last_stream_preview { 262 | time::Instant::now().duration_since(last) > Duration::from_secs(10 * 60) 263 | } else { 264 | true 265 | } 266 | } else { 267 | false 268 | }; 269 | 270 | if send_preview { 271 | let _ = last_stream_preview.insert(time::Instant::now()); 272 | info!("Sending stream preview"); 273 | if let Err(e) = websocket 274 | .lock() 275 | .await 276 | .stream_preview( 277 | &x.take_screenshot(current_xid.unwrap()).unwrap(), 278 | ) 279 | .await 280 | { 281 | error!("Failed to send stream preview: {}", e); 282 | } 283 | } 284 | 285 | sleep(sleep_time).await; 286 | } 287 | }, 288 | } 289 | } 290 | }); 291 | 292 | CommandProcessor { 293 | thread: Some(thread), 294 | } 295 | } 296 | 297 | /// Waits for the `CommandProcessor`'s internal thread to join. 298 | pub async fn join(&mut self) { 299 | if let Some(thread) = self.thread.take() { 300 | thread.await.expect("Unable to join thread"); 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /daemon/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, panic, process, sync::{ 2 | Arc, 3 | atomic::{AtomicBool, Ordering}, 4 | }, time::Duration}; 5 | use std::collections::HashMap; 6 | use std::str::FromStr; 7 | 8 | use tokio::{ 9 | signal::{ctrl_c, unix::SignalKind}, 10 | sync::mpsc, 11 | }; 12 | use tokio::sync::Mutex; 13 | use tracing::{error, info, Level}; 14 | use tracing_log::LogTracer; 15 | use tracing_subscriber::{filter, Layer}; 16 | use tracing_subscriber::layer::SubscriberExt; 17 | 18 | use tuxphones::{CommandProcessor, socket::WebSocket}; 19 | 20 | #[tokio::main] 21 | async fn main() { 22 | initialize_logging(); 23 | 24 | let run = Arc::new(AtomicBool::new(true)); 25 | let r = Arc::clone(&run); 26 | 27 | // Ctrl+C handling 28 | // match ctrlc::set_handler(move || { 29 | // info!("Interrupt!"); 30 | // r.store(false, Ordering::SeqCst); 31 | // }) { 32 | // Ok(_) => {}, 33 | // Err(e) => { 34 | // error!("Failed to set interrupt handler! {}", e); 35 | // process::exit(1); 36 | // } 37 | // } 38 | 39 | let (sender, receiver) = mpsc::channel(1000); 40 | 41 | let socket_watcher: Arc> = match WebSocket::new(9000, sender.clone()).await { 42 | Ok(s) => Arc::new(Mutex::new(s)), 43 | Err(_) => { 44 | error!("Error creating socket watcher!"); 45 | process::exit(2); 46 | } 47 | }; 48 | 49 | let mut command_processor = CommandProcessor::new( 50 | receiver, 51 | Arc::clone(&run), 52 | Duration::from_millis(500), 53 | socket_watcher.clone(), 54 | ); 55 | 56 | info!("Daemon started"); 57 | 58 | let mut sig = tokio::signal::unix::signal(SignalKind::terminate()).unwrap(); 59 | 60 | tokio::select! { 61 | _ = sig.recv() => {}, 62 | _ = ctrl_c() => {} 63 | } 64 | 65 | r.store(false, Ordering::SeqCst); 66 | 67 | socket_watcher.lock().await.abort().await; 68 | command_processor.join().await; 69 | } 70 | 71 | fn initialize_logging() { 72 | // "TUX_LOG=category=level,category=level..." 73 | // "TUX_FILE_LOG=category=level,category=level..." 74 | // "TUX_FILE_PATH=/path/to/folder" 75 | // TUX_FILE_PATH can include {date} and {time} which will be replaced with the current date and time and {pid} which will be replaced with the current process id 76 | //Where category is one of: 77 | // - "tuxphones" for the daemon itself 78 | // - "tuxphones::websocket" for the websocket 79 | // - "tuxphones::command" for the command processor 80 | //And level is one of: 81 | // - 0 = disabled 82 | // - 1 = "error" 83 | // - 2 = "debug" 84 | // - 3 = "info" 85 | // - 4 = "debug" 86 | // - 5 = "trace" 87 | let console_categories: HashMap = std::env::var("TUX_LOG") 88 | .unwrap_or_else(|_| "tuxphones=3".to_string()) 89 | .split(',') 90 | .map(|s| { 91 | let mut split = s.split('='); 92 | (split.next().unwrap().to_string(), split.next().unwrap().parse().unwrap()) 93 | }) 94 | .collect(); 95 | 96 | let file_categories: HashMap = std::env::var("TUX_FILE_LOG") 97 | .unwrap_or_else(|_| "tuxphones=3".to_string()) 98 | .split(',') 99 | .map(|s| { 100 | let mut split = s.split('='); 101 | (split.next().unwrap().to_string(), split.next().unwrap().parse().unwrap()) 102 | }) 103 | .collect(); 104 | 105 | let mut file_subscribers = Vec::new(); 106 | 107 | if !file_categories.is_empty() { 108 | let file_path = std::env::var("TUX_FILE_PATH").unwrap_or_else(|_| "/tmp/tuxphones-{date}-{time}-{pid}".to_string()); 109 | 110 | //replace {date}, {time}, {pid} in the file path with the current date, time, process 111 | let file_path = file_path 112 | .replace("{date}", &chrono::Local::now().format("%Y:%m:%d").to_string()) 113 | .replace("{time}", &chrono::Local::now().format("%H:%M:%S").to_string()) 114 | .replace("{pid}", &process::id().to_string()); 115 | 116 | //Create the folder file_path and if already exist add a -1, -2, -3, etc to the end of the folder name 117 | let mut file_path = std::path::PathBuf::from(file_path); 118 | let mut i = 0; 119 | while file_path.exists() { 120 | i += 1; 121 | file_path = file_path.with_file_name(format!("{}-{}", file_path.file_name().unwrap().to_str().unwrap(), i)); 122 | } 123 | 124 | match fs::create_dir_all(&file_path) { 125 | Ok(_) => { 126 | if std::env::var("TUX_OPEN_LOG_ON_START").unwrap_or_else(|_| "false".to_string()).parse::().unwrap() { 127 | match process::Command::new("xdg-open").arg(&file_path).spawn() { 128 | Ok(_) => {}, 129 | Err(e) => { 130 | eprintln!("Failed to open folder! {}", e); 131 | } 132 | } 133 | } 134 | //For each file_category create a file and a tracing_subscriber for it 135 | for (category, level) in file_categories { 136 | let file = fs::File::create(format!("{}/{}.log", file_path.to_str().unwrap(), category)).unwrap(); 137 | //TODO: Figure out why the non_blocking wrapper doesn't work 138 | //let (non_blocking, _guard) = tracing_appender::non_blocking(file); 139 | let file_log = tracing_subscriber::fmt::layer() 140 | .with_ansi(false) 141 | .with_writer(file) 142 | .with_target(false) 143 | .with_filter(filter::filter_fn(move |meta| { 144 | meta.target() == category && meta.level() <= &Level::from_str(&level.to_string()).unwrap() 145 | })) 146 | .boxed(); 147 | 148 | file_subscribers.push(file_log); 149 | } 150 | } 151 | Err(_) => { 152 | eprintln!("Failed to create folder {}, file logging disabled", file_path.to_str().unwrap()); 153 | } 154 | }; 155 | } 156 | 157 | // Stdout logging 158 | let stdout_log = tracing_subscriber::fmt::layer().pretty(); 159 | 160 | let subscriber = tracing_subscriber::registry() 161 | .with(stdout_log 162 | .with_filter(filter::filter_fn(move |meta| { 163 | console_categories.get(meta.target()).map(|level| { 164 | meta.level() <= &Level::from_str(&level.to_string()).unwrap() 165 | }).unwrap_or(false) 166 | })) 167 | ) 168 | .with(file_subscribers); 169 | error!("Logging initialized"); 170 | 171 | match tracing::subscriber::set_global_default(subscriber) { 172 | Ok(_) => {} 173 | Err(e) => { 174 | eprintln!("Failed to set global logging default subscriber: {}", e); 175 | } 176 | } 177 | 178 | LogTracer::init().unwrap(); 179 | } -------------------------------------------------------------------------------- /daemon/src/pulse.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, ops::Deref, sync::Arc}; 2 | 3 | use crate::pid; 4 | use libpulse_binding::{ 5 | callbacks::ListResult, 6 | context::{Context, FlagSet as ContextFlagSet, State}, 7 | mainloop::threaded::Mainloop, 8 | operation::Operation, 9 | }; 10 | 11 | pub struct PulseHandle { 12 | mainloop: Arc>, 13 | context: Arc>, 14 | audio_is_setup: bool, 15 | tuxphones_sink_module_index: Option, 16 | combined_sink_index: Option, 17 | combined_sink_module_index: Option, 18 | current_app_info: Option, 19 | } 20 | 21 | unsafe impl Send for PulseHandle {} 22 | 23 | struct CurrentAppInfo { 24 | sink_input_restore_index: u32, 25 | index: u32, 26 | } 27 | 28 | pub struct BasicSinkInfo { 29 | pub name: String, 30 | pub index: u32, 31 | module: Option, 32 | } 33 | 34 | #[derive(Debug)] 35 | pub struct AudioApplication { 36 | pub name: String, 37 | pub pid: pid, 38 | pub index: u32, 39 | pub sink_index: u32, 40 | } 41 | 42 | #[derive(Debug)] 43 | pub enum PulseInitializationError { 44 | NoAlloc, 45 | LoopStartErr(i32), 46 | ContextConnectErr(i32), 47 | ContextStateErr, 48 | } 49 | 50 | impl std::fmt::Display for PulseInitializationError { 51 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 52 | let str = match self { 53 | PulseInitializationError::NoAlloc => "Unable to allocate".to_string(), 54 | PulseInitializationError::LoopStartErr(code) => format!("Loop start error: {}", code), 55 | PulseInitializationError::ContextConnectErr(code) => { 56 | format!("Context connection error: {}", code) 57 | } 58 | PulseInitializationError::ContextStateErr => "Context state error".to_string(), 59 | }; 60 | f.write_str(&str) 61 | } 62 | } 63 | 64 | #[derive(Debug)] 65 | pub enum PulseCaptureSetupError { 66 | NoPassthrough, 67 | NoDefaultSink, 68 | } 69 | 70 | impl std::fmt::Display for PulseCaptureSetupError { 71 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 72 | f.write_str(match self { 73 | PulseCaptureSetupError::NoPassthrough => "No passthrough sink found", 74 | PulseCaptureSetupError::NoDefaultSink => "No default sink found", 75 | }) 76 | } 77 | } 78 | 79 | #[derive(Debug)] 80 | pub enum PulseCaptureError { 81 | NotSetup, 82 | NoAppWithPid, 83 | } 84 | 85 | impl std::fmt::Display for PulseCaptureError { 86 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 87 | f.write_str(match self { 88 | PulseCaptureError::NotSetup => "Capture not setup", 89 | PulseCaptureError::NoAppWithPid => "No app with given PID found", 90 | }) 91 | } 92 | } 93 | 94 | impl Drop for PulseHandle { 95 | fn drop(&mut self) { 96 | self.stop_capture(); 97 | if self.audio_is_setup { 98 | self.teardown_audio_capture(); 99 | } 100 | 101 | self.mainloop.borrow_mut().lock(); 102 | self.context.borrow_mut().disconnect(); 103 | self.mainloop.borrow_mut().unlock(); 104 | self.mainloop.borrow_mut().stop(); 105 | } 106 | } 107 | 108 | impl PulseHandle { 109 | /// Creates a new Pulse handle 110 | pub fn new() -> Result { 111 | let mainloop = Arc::new(RefCell::new(match Mainloop::new() { 112 | Some(l) => l, 113 | None => return Err(PulseInitializationError::NoAlloc), 114 | })); 115 | 116 | match mainloop.borrow_mut().start() { 117 | Ok(_) => {} 118 | Err(e) => return Err(PulseInitializationError::LoopStartErr(e.0)), 119 | } 120 | 121 | // Lock mainloop to create context 122 | mainloop.borrow_mut().lock(); 123 | 124 | let context = Arc::new(RefCell::new( 125 | match Context::new(mainloop.borrow_mut().deref(), "tuxphones") { 126 | Some(c) => c, 127 | None => { 128 | mainloop.borrow_mut().unlock(); 129 | mainloop.borrow_mut().stop(); 130 | return Err(PulseInitializationError::NoAlloc); 131 | } 132 | }, 133 | )); 134 | 135 | // State callback to wait for connection 136 | { 137 | let ml_ref = Arc::clone(&mainloop); 138 | let context_ref = Arc::clone(&context); 139 | context 140 | .borrow_mut() 141 | .set_state_callback(Some(Box::new(move || { 142 | // Needs to be unsafe to be able to borrow mutably multiple times 143 | match unsafe { (*context_ref.as_ptr()).get_state() } { 144 | State::Ready | State::Failed | State::Terminated => unsafe { 145 | (*ml_ref.as_ptr()).signal(false); 146 | }, 147 | _ => {} 148 | } 149 | }))); 150 | } 151 | 152 | match context 153 | .borrow_mut() 154 | .connect(None, ContextFlagSet::NOFLAGS, None) 155 | { 156 | Ok(_) => {} 157 | Err(e) => { 158 | mainloop.borrow_mut().unlock(); 159 | mainloop.borrow_mut().stop(); 160 | return Err(PulseInitializationError::ContextConnectErr(e.0)); 161 | } 162 | } 163 | 164 | loop { 165 | match context.borrow_mut().get_state() { 166 | State::Ready => break, 167 | State::Failed | State::Terminated => { 168 | mainloop.borrow_mut().unlock(); 169 | mainloop.borrow_mut().stop(); 170 | return Err(PulseInitializationError::ContextStateErr); 171 | } 172 | _ => mainloop.borrow_mut().wait(), 173 | } 174 | } 175 | context.borrow_mut().set_state_callback(None); 176 | 177 | mainloop.borrow_mut().unlock(); 178 | 179 | Ok(PulseHandle { 180 | context: Arc::clone(&context), 181 | mainloop: Arc::clone(&mainloop), 182 | audio_is_setup: false, 183 | tuxphones_sink_module_index: None, 184 | combined_sink_index: None, 185 | combined_sink_module_index: None, 186 | current_app_info: None, 187 | }) 188 | } 189 | 190 | /// Gets the sinks connected to the Pulse server 191 | pub fn get_sinks(&mut self) -> Vec { 192 | self.mainloop.borrow_mut().lock(); 193 | let results = Arc::new(RefCell::new(Some(vec![]))); 194 | 195 | let ml_ref = Arc::clone(&self.mainloop); 196 | let results_ref = Arc::clone(&results); 197 | let op = self 198 | .context 199 | .borrow_mut() 200 | .introspect() 201 | .get_sink_info_list(move |res| match res { 202 | ListResult::Item(info) => { 203 | results_ref 204 | .borrow_mut() 205 | .as_mut() 206 | .unwrap() 207 | .push(BasicSinkInfo { 208 | name: info 209 | .name 210 | .as_ref() 211 | .map_or(String::from("unknown name"), |n| n.to_string()), 212 | index: info.index, 213 | module: info.owner_module, 214 | }) 215 | } 216 | ListResult::End | ListResult::Error => unsafe { 217 | (*ml_ref.as_ptr()).signal(false); 218 | }, 219 | }); 220 | 221 | op_wait(&mut self.mainloop.borrow_mut(), &op); 222 | 223 | self.mainloop.borrow_mut().unlock(); 224 | 225 | let res = results.borrow_mut().take().unwrap(); 226 | res 227 | } 228 | 229 | /// Gets all applications that are producing audio 230 | pub fn get_audio_applications(&mut self) -> Vec { 231 | self.mainloop.borrow_mut().lock(); 232 | 233 | let results = Arc::new(RefCell::new(Some(vec![]))); 234 | 235 | let ml_ref = Arc::clone(&self.mainloop); 236 | let results_ref = Arc::clone(&results); 237 | let op = self 238 | .context 239 | .borrow_mut() 240 | .introspect() 241 | .get_sink_input_info_list(move |res| match res { 242 | ListResult::Item(info) => { 243 | if let Some(pid) = info.proplist.get_str("application.process.id") { 244 | results_ref 245 | .borrow_mut() 246 | .as_mut() 247 | .unwrap() 248 | .push(AudioApplication { 249 | name: info 250 | .proplist 251 | .get_str("application.name") 252 | .unwrap_or_else(|| "NONAME".to_string()), 253 | pid: pid.parse().unwrap(), 254 | index: info.index, 255 | sink_index: info.sink, 256 | }); 257 | } 258 | } 259 | ListResult::End | ListResult::Error => unsafe { 260 | (*ml_ref.as_ptr()).signal(false); 261 | }, 262 | }); 263 | 264 | op_wait(&mut self.mainloop.borrow_mut(), &op); 265 | 266 | self.mainloop.borrow_mut().unlock(); 267 | 268 | let res = results.borrow_mut().take().unwrap(); 269 | res 270 | } 271 | 272 | /// Adds sinks for audio capture 273 | pub fn setup_audio_capture( 274 | &mut self, 275 | passthrough_override: Option<&str>, 276 | ) -> Result<(), PulseCaptureSetupError> { 277 | // Don't do the same thing twice 278 | if self.audio_is_setup { 279 | return Ok(()); 280 | } 281 | 282 | let passthrough_sink = match passthrough_override { 283 | Some(s) => s.to_string(), 284 | None => { 285 | self.mainloop.borrow_mut().lock(); 286 | let result: Arc>> = Arc::new(RefCell::new(None)); 287 | let ml_ref = Arc::clone(&self.mainloop); 288 | let res_ref = Arc::clone(&result); 289 | let op = self 290 | .context 291 | .borrow_mut() 292 | .introspect() 293 | .get_sink_info_by_name("@DEFAULT_SINK@", move |info| unsafe { 294 | match info { 295 | ListResult::Item(sink) => res_ref.borrow_mut().replace( 296 | sink.name 297 | .as_ref() 298 | .map_or(String::from("unknown name"), |n| n.to_string()), 299 | ), 300 | ListResult::End | ListResult::Error => None, 301 | }; 302 | 303 | (*ml_ref.as_ptr()).signal(false); 304 | }); 305 | 306 | op_wait(&mut self.mainloop.borrow_mut(), &op); 307 | self.mainloop.borrow_mut().unlock(); 308 | 309 | let res = match result.borrow_mut().take() { 310 | Some(r) => r, 311 | None => return Err(PulseCaptureSetupError::NoDefaultSink), 312 | }; 313 | res 314 | } 315 | }; 316 | 317 | let mut tux_sink_found = false; 318 | let mut tux_combined_sink_found = false; 319 | let mut passthrough_sink_found = false; 320 | 321 | for sink in self.get_sinks() { 322 | match &sink.name[..] { 323 | "tuxphones" => tux_sink_found = true, 324 | "tuxphones-combined" => tux_combined_sink_found = true, 325 | n if n == passthrough_sink => passthrough_sink_found = true, 326 | _ => {} 327 | } 328 | } 329 | 330 | if !passthrough_sink_found { 331 | return Err(PulseCaptureSetupError::NoPassthrough); 332 | } 333 | 334 | self.mainloop.borrow_mut().lock(); 335 | if !tux_sink_found { 336 | let ml_ref = Arc::clone(&self.mainloop); 337 | let op = self.context.borrow_mut().introspect().load_module( 338 | "module-null-sink", 339 | "sink_name=tuxphones sink_properties=device.description=tuxphones", 340 | move |_| unsafe { 341 | (*ml_ref.as_ptr()).signal(false); 342 | }, 343 | ); 344 | 345 | op_wait(&mut self.mainloop.borrow_mut(), &op); 346 | } 347 | 348 | if !tux_combined_sink_found { 349 | let ml_ref = Arc::clone(&self.mainloop); 350 | // adjust_time=0 prevents a crash for some reason 351 | let op = self.context.borrow_mut().introspect().load_module( 352 | "module-combine-sink", 353 | &format!("sink_name=tuxphones-combined sink_properties=device.description=tuxphones-combined adjust_time=0 slaves=tuxphones,{}", passthrough_sink), 354 | move |_| unsafe { 355 | (*ml_ref.as_ptr()).signal(false); 356 | } 357 | ); 358 | 359 | op_wait(&mut self.mainloop.borrow_mut(), &op); 360 | } 361 | 362 | self.mainloop.borrow_mut().unlock(); 363 | 364 | for sink in self.get_sinks() { 365 | match &sink.name[..] { 366 | "tuxphones" => self.tuxphones_sink_module_index = Some(sink.module.unwrap()), 367 | "tuxphones-combined" => { 368 | self.combined_sink_module_index = Some(sink.module.unwrap()); 369 | self.combined_sink_index = Some(sink.index); 370 | } 371 | _ => {} 372 | } 373 | } 374 | 375 | self.audio_is_setup = true; 376 | Ok(()) 377 | } 378 | 379 | /// Removes audio capture sinks 380 | pub fn teardown_audio_capture(&mut self) { 381 | if !self.audio_is_setup { 382 | return; 383 | } 384 | 385 | self.audio_is_setup = false; 386 | 387 | self.mainloop.borrow_mut().lock(); 388 | 389 | if let Some(idx) = self.tuxphones_sink_module_index { 390 | self.unload_module(idx); 391 | } 392 | 393 | if let Some(idx) = self.combined_sink_module_index { 394 | self.unload_module(idx); 395 | } 396 | 397 | self.tuxphones_sink_module_index = None; 398 | self.combined_sink_index = None; 399 | self.combined_sink_module_index = None; 400 | 401 | self.mainloop.borrow_mut().unlock(); 402 | } 403 | 404 | /// Unloads modules 405 | fn unload_module(&mut self, idx: u32) { 406 | let ml_ref = Arc::clone(&self.mainloop); 407 | let op = self 408 | .context 409 | .borrow_mut() 410 | .introspect() 411 | .unload_module(idx, move |_| unsafe { 412 | (*ml_ref.as_ptr()).signal(false); 413 | }); 414 | 415 | op_wait(&mut self.mainloop.borrow_mut(), &op); 416 | } 417 | 418 | /// Starts capturing audio from the application with the given Pulse PID 419 | pub fn start_capture(&mut self, pid: pid) -> Result<(), PulseCaptureError> { 420 | if !self.audio_is_setup || self.combined_sink_module_index.is_none() { 421 | return Err(PulseCaptureError::NotSetup); 422 | } 423 | 424 | for app in self.get_audio_applications() { 425 | if app.pid == pid { 426 | self.mainloop.borrow_mut().lock(); 427 | 428 | let ml_ref = Arc::clone(&self.mainloop); 429 | self.current_app_info = Some(CurrentAppInfo { 430 | sink_input_restore_index: app.sink_index, 431 | index: app.index, 432 | }); 433 | let op = self 434 | .context 435 | .borrow_mut() 436 | .introspect() 437 | .move_sink_input_by_index( 438 | app.index, 439 | self.combined_sink_index.unwrap(), 440 | Some(Box::new(move |_| unsafe { 441 | (*ml_ref.as_ptr()).signal(false); 442 | })), 443 | ); 444 | 445 | op_wait(&mut self.mainloop.borrow_mut(), &op); 446 | 447 | self.mainloop.borrow_mut().unlock(); 448 | 449 | return Ok(()); 450 | } 451 | } 452 | 453 | Err(PulseCaptureError::NoAppWithPid) 454 | } 455 | 456 | /// Stop capturing audio from application 457 | pub fn stop_capture(&mut self) { 458 | self.mainloop.borrow_mut().lock(); 459 | 460 | if let Some(info) = &self.current_app_info { 461 | let ml_ref = Arc::clone(&self.mainloop); 462 | let op = self 463 | .context 464 | .borrow_mut() 465 | .introspect() 466 | .move_sink_input_by_index( 467 | info.index, 468 | info.sink_input_restore_index, 469 | Some(Box::new(move |_| unsafe { 470 | (*ml_ref.as_ptr()).signal(false); 471 | })), 472 | ); 473 | 474 | op_wait(&mut self.mainloop.borrow_mut(), &op); 475 | } 476 | 477 | self.current_app_info = None; 478 | self.mainloop.borrow_mut().unlock(); 479 | } 480 | } 481 | 482 | /// Wait for operation to complete 483 | fn op_wait(ml: &mut Mainloop, op: &Operation) { 484 | while op.get_state() == libpulse_binding::operation::State::Running { 485 | ml.wait() 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /daemon/src/socket.rs: -------------------------------------------------------------------------------- 1 | use crate::{pid, xid}; 2 | use async_tungstenite::{ 3 | tokio::{accept_async, TokioAdapter}, 4 | tungstenite::{Error, Message}, 5 | WebSocketStream, 6 | }; 7 | use futures_util::{stream::SplitSink, SinkExt, StreamExt}; 8 | use serde::{Deserialize, Serialize}; 9 | use std::{collections::HashMap, fmt::Display, net::SocketAddr, sync::Arc}; 10 | use tokio::{ 11 | net::{TcpListener, TcpStream}, 12 | sync::{mpsc, Mutex}, 13 | task::{self, JoinHandle}, 14 | }; 15 | use tracing::{error, trace}; 16 | 17 | type ConnectionsArc = 18 | Arc>, Message>>>>; 19 | type CommandSender = mpsc::Sender; 20 | 21 | /// Listens on a socket for commands 22 | pub struct WebSocket { 23 | thread: Option>, 24 | connections: ConnectionsArc, 25 | } 26 | 27 | /// Possible errors when creating a `SocketListener` 28 | #[derive(Debug, Clone)] 29 | pub enum SocketListenerCreationError { 30 | // Unable to bind WebSocket to the specified port 31 | UnableToBindPort(u16), 32 | } 33 | 34 | /// Holds information relating to stream resolution 35 | #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] 36 | #[serde(tag = "type")] 37 | pub struct StreamResolutionInformation { 38 | pub width: u16, 39 | pub height: u16, 40 | /// Whether or not the stream resolution can change 41 | pub is_fixed: bool, 42 | } 43 | 44 | /// Holds RTC ICE information 45 | #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] 46 | #[serde(tag = "type")] 47 | pub struct IceData { 48 | pub urls: Vec, 49 | pub username: String, 50 | pub credential: String, 51 | } 52 | 53 | /// Commands that can be received from the client plugin 54 | #[derive(Deserialize, Debug, PartialEq, Eq)] 55 | #[serde(tag = "type")] 56 | pub enum SocketListenerCommand { 57 | /// Starts a new soundshare stream 58 | StartStream { 59 | /// Pulse PID 60 | pid: pid, 61 | /// XID 62 | xid: xid, 63 | /// Target resolution 64 | resolution: StreamResolutionInformation, 65 | /// Target framerate 66 | framerate: u8, 67 | /// RTC Connection ID 68 | rtc_connection_id: String, 69 | /// Secret key for Sodium encryption 70 | secret_key: Vec, 71 | /// The associated voice chat's SSRC 72 | voice_ssrc: u32, 73 | /// The base SSRC for creating a stream, needs to be offset to get video (+1) and RTX (+2) SSRCs 74 | base_ssrc: u32, 75 | ip: String, 76 | port: u16, 77 | }, 78 | /// Stops the currently-running stream 79 | StopStream, 80 | /// Internal stop stream command, notifies client plugin 81 | StopStreamInternal, 82 | /// Gets info on which windows can have sound captured 83 | GetInfo { 84 | /// XIDs available to Discord 85 | xids: Vec, 86 | }, 87 | } 88 | 89 | #[derive(Serialize)] 90 | #[serde(tag = "type")] 91 | struct ApplicationList<'a> { 92 | apps: &'a Vec, 93 | } 94 | 95 | #[derive(Serialize, Debug)] 96 | #[serde(tag = "type")] 97 | pub struct Application { 98 | pub name: String, 99 | pub pid: pid, 100 | pub xid: xid, 101 | } 102 | 103 | #[derive(Serialize, Debug)] 104 | #[serde(tag = "type")] 105 | pub struct StreamStop {} 106 | 107 | #[derive(Serialize, Debug)] 108 | #[serde(tag = "type")] 109 | pub struct StreamPreview { 110 | jpg: String, 111 | } 112 | 113 | impl Display for SocketListenerCreationError { 114 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 115 | match self { 116 | Self::UnableToBindPort(port) => { 117 | f.write_str(&format!("Unable to bind to localhost on port {}", port)) 118 | } 119 | } 120 | } 121 | } 122 | 123 | impl WebSocket { 124 | pub async fn new( 125 | port: u16, 126 | sender: CommandSender, 127 | ) -> Result { 128 | let connections: ConnectionsArc = Arc::new(Mutex::new(HashMap::new())); 129 | 130 | let listener = match TcpListener::bind(format!("127.0.0.1:{}", port)).await { 131 | Ok(l) => l, 132 | Err(_) => return Err(SocketListenerCreationError::UnableToBindPort(port)), 133 | }; 134 | 135 | // Spawn listener thread to check for commands sent to the socket 136 | let conn_arc = connections.clone(); 137 | let thread = task::spawn(async move { 138 | while let Ok((stream, _)) = listener.accept().await { 139 | tokio::spawn(Self::accept_connection( 140 | stream, 141 | conn_arc.clone(), 142 | sender.clone(), 143 | )); 144 | } 145 | }); 146 | 147 | Ok(WebSocket { 148 | thread: Some(thread), 149 | connections, 150 | }) 151 | } 152 | 153 | async fn send(&self, data: &T) -> Result<(), Error> 154 | where 155 | T: ?Sized + Serialize, 156 | { 157 | let mut conn = self.connections.lock().await; 158 | let mut to_remove = Vec::new(); 159 | for (addr, stream) in conn.iter_mut() { 160 | match stream 161 | .send(Message::Text(serde_json::to_string(data).unwrap())) 162 | .await 163 | { 164 | Ok(()) => {} 165 | Err(e) => match e { 166 | Error::ConnectionClosed => { 167 | to_remove.push(*addr); 168 | } 169 | _ => return Err(e), 170 | }, 171 | } 172 | } 173 | 174 | for rm in to_remove { 175 | conn.remove(&rm); 176 | } 177 | 178 | Ok(()) 179 | } 180 | 181 | pub async fn application_info(&self, apps: &Vec) -> Result<(), Error> { 182 | self.send(&ApplicationList { apps }).await 183 | } 184 | 185 | pub async fn stream_stop_internal(&self) -> Result<(), Error> { 186 | self.send(&StreamStop {}).await 187 | } 188 | 189 | pub async fn stream_preview(&self, data: &Vec) -> Result<(), Error> { 190 | self.send(&StreamPreview { 191 | jpg: base64::encode(data), 192 | }) 193 | .await 194 | } 195 | 196 | /// Kills the WebSocket thread and closes everything up 197 | pub async fn abort(&mut self) { 198 | if let Some(thread) = self.thread.take() { 199 | thread.abort(); 200 | } 201 | } 202 | 203 | async fn handle_connection( 204 | stream: TcpStream, 205 | connections: ConnectionsArc, 206 | sender: CommandSender, 207 | ) -> Result<(), Error> { 208 | let addr = stream.peer_addr().unwrap(); 209 | 210 | let ws_stream = match accept_async(stream).await { 211 | Ok(s) => s, 212 | Err(e) => { 213 | error!("{}", e.to_string()); 214 | return Ok(()); 215 | } 216 | }; 217 | 218 | let (write, mut read) = ws_stream.split(); 219 | 220 | connections.lock().await.insert(addr, write); 221 | 222 | while let Some(msg) = read.next().await { 223 | let msg = msg?; 224 | if msg.is_text() { 225 | trace!("Received command: {}", msg.to_text().unwrap()); 226 | 227 | match serde_json::from_str::(msg.to_text().unwrap()) { 228 | Ok(cmd) => match sender.send(cmd).await { 229 | Ok(_) => {} 230 | Err(e) => error!("Failed to send command: {}", e), 231 | }, 232 | Err(e) => error!("Failed to deserialize command: {}", e), 233 | } 234 | } 235 | } 236 | 237 | Ok(()) 238 | } 239 | 240 | async fn accept_connection( 241 | stream: TcpStream, 242 | connections: ConnectionsArc, 243 | sender: CommandSender, 244 | ) { 245 | let addr = stream.peer_addr().unwrap(); 246 | if let Err(e) = Self::handle_connection(stream, connections.clone(), sender).await { 247 | match e { 248 | Error::ConnectionClosed | Error::Protocol(_) | Error::Utf8 => { 249 | connections.lock().await.remove(&addr); 250 | } 251 | err => error!("Error processing connection: {}", err), 252 | } 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /daemon/src/x.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::Cursor, 3 | }; 4 | 5 | // use async_std::{channel::Sender, task}; 6 | use crate::{pid, xid}; 7 | use image::ImageBuffer; 8 | use sysinfo::{PidExt, ProcessExt, SystemExt}; 9 | use xcb::{ 10 | res::{ClientIdMask, ClientIdSpec, QueryClientIds}, 11 | x::{ 12 | self, GetGeometry, GetImage, 13 | }, 14 | }; 15 | 16 | pub struct XServerHandle { 17 | connection: xcb::Connection, 18 | /// List of PIDs that are related to Xorg 19 | xorg_procs: Vec, 20 | } 21 | 22 | #[derive(PartialEq, Eq, Clone, Copy)] 23 | pub struct Size { 24 | pub width: u16, 25 | pub height: u16, 26 | } 27 | 28 | impl XServerHandle { 29 | pub fn new() -> Result { 30 | // Connect to the server 31 | let (conn, _) = xcb::Connection::connect(None)?; 32 | 33 | // Get the current Xorg process to make sure XServer isn't falsely recognizing windows (cached) 34 | let mut system = sysinfo::System::new(); 35 | system.refresh_processes(); 36 | let xorg_procs = system 37 | .processes_by_name("Xorg") 38 | .map(|p| p.pid().as_u32()) 39 | .collect(); 40 | 41 | Ok(XServerHandle { 42 | connection: conn, 43 | /*cache: HashMap::new(), last_cache_wipe: None,*/ xorg_procs, 44 | }) 45 | } 46 | 47 | /// Attempts to derive a PID from an XID 48 | pub fn pid_from_xid(&self, xid: xid) -> Result, xcb::Error> { 49 | // Create request 50 | let cookie = self.connection.send_request(&QueryClientIds { 51 | specs: &[ClientIdSpec { 52 | client: xid, 53 | mask: ClientIdMask::LOCAL_CLIENT_PID, 54 | }], 55 | }); 56 | 57 | let reply = self.connection.wait_for_reply(cookie)?; 58 | 59 | if let Some(val) = reply.ids().next() { 60 | return Ok( 61 | if !val.value().is_empty() && !self.xorg_procs.iter().any(|v| *v == val.value()[0]) 62 | { 63 | Some(val.value()[0]) 64 | } else { 65 | None 66 | }, 67 | ); 68 | } 69 | 70 | Ok(None) 71 | } 72 | 73 | pub fn take_screenshot(&self, xid: xid) -> Result, xcb::Error> { 74 | let size = window_size(&self.connection, xid)?; 75 | 76 | let cookie = self.connection.send_request(&GetImage { 77 | format: x::ImageFormat::ZPixmap, // jpg 78 | drawable: xcb::x::Drawable::Window(unsafe { xcb::XidNew::new(xid) }), 79 | x: 0, 80 | y: 0, 81 | width: size.width, 82 | height: size.height, 83 | plane_mask: u32::MAX, 84 | }); 85 | 86 | let reply = self.connection.wait_for_reply(cookie)?; 87 | 88 | let mut buf: Cursor> = Cursor::new(Vec::new()); 89 | 90 | let mut image: ImageBuffer, _> = ImageBuffer::from_raw( 91 | size.width.into(), 92 | size.height.into(), 93 | reply.data().to_owned(), 94 | ) 95 | .unwrap(); 96 | // Convert BGRA to RGBA 97 | for pixel in image.pixels_mut() { 98 | pixel.0 = [pixel.0[2], pixel.0[1], pixel.0[0], pixel.0[3]]; 99 | } 100 | 101 | let (width, height) = calculate_aspect_ratio_fit(image.width(), image.height(), 512, 512); 102 | 103 | // Resize image to reasonable thumbnail size 104 | let image = 105 | image::imageops::resize(&image, width, height, image::imageops::FilterType::Triangle); 106 | image.write_to(&mut buf, image::ImageFormat::Jpeg).unwrap(); 107 | 108 | Ok(buf.into_inner()) 109 | } 110 | } 111 | 112 | fn calculate_aspect_ratio_fit( 113 | src_width: u32, 114 | src_height: u32, 115 | max_width: u32, 116 | max_height: u32, 117 | ) -> (u32, u32) { 118 | let ratio = f64::min( 119 | max_width as f64 / src_width as f64, 120 | max_height as f64 / src_height as f64, 121 | ); 122 | 123 | ( 124 | (src_width as f64 * ratio).round() as u32, 125 | (src_height as f64 * ratio).round() as u32, 126 | ) 127 | } 128 | 129 | fn window_size(conn: &xcb::Connection, xid: xid) -> Result { 130 | let cookie = conn.send_request(&GetGeometry { 131 | drawable: x::Drawable::Window(unsafe { xcb::XidNew::new(xid) }), 132 | }); 133 | 134 | let reply = conn.wait_for_reply(cookie)?; 135 | 136 | Ok(Size { 137 | width: reply.width(), 138 | height: reply.height(), 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cargo install tuxphones 4 | 5 | BASEDIR=$(dirname "$0") 6 | mkdir -p "$HOME"/.local/share/systemd/user/ 7 | cp "$BASEDIR"/Tuxphones.service "$HOME"/.local/share/systemd/user/ 8 | systemctl --user enable Tuxphones.service 9 | systemctl --user start Tuxphones.service 10 | 11 | cp "$BASEDIR"/bd/builds/Tuxphones.plugin.js "$HOME"/.config/BetterDiscord/plugins/ -------------------------------------------------------------------------------- /updateDaemon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cargo install tuxphones 4 | 5 | systemctl --user restart Tuxphones.service --------------------------------------------------------------------------------