diff --git a/.gitmodules b/.gitmodules index 71d3247f..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "xdg-desktop-portal-wlr"] - path = xdg-desktop-portal-wlr - url = https://github.com/waycrate/xdg-desktop-portal-wlr diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 89bc0066..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -aakashsensharma@gmail.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 8fb8dfde..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,25 +0,0 @@ -# Contributing to wayshot - -1. Fork the repo and create your branch from `main`. -1. Make sure to write tests for the functions you make. -1. Run `make check` once you're done and ensure the test suite passes. - -## Any contributions you make will be under the BSD-2-Clause Software License -In short, when you submit code changes, your submissions are understood to be under the same BSD-2-Clause License that covers the project. Feel free to contact the maintainers if that's a concern. - -## Use a Consistent Coding Style -I'm again borrowing these from [Facebook's Guidelines](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) - -* 4 spaces for indentation. -* You can run `make check` for style unification. - -## Proper Commit Messages -Make sure to write proper commit messages. - -Example: `[refactor] wayshot.rs, per output selection`. - -## License -By contributing, you agree that your contributions will be licensed under its BSD-2-Clause License. - -## References -This document was adapted from [GitHub Gist](https://gist.github.com/briandk/3d2e8b3ec8daf5a27a62). diff --git a/Cargo.lock b/Cargo.lock index c9877ccd..15672975 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "atomic_refcell" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" + [[package]] name = "atty" version = "0.2.14" @@ -49,6 +61,16 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +[[package]] +name = "cfg-expr" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "215c0072ecc28f92eeb0eea38ba63ddfcb65c2828c46311d646f1a3ff5f9841c" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -64,7 +86,7 @@ dependencies = [ "atty", "bitflags", "clap_lex", - "indexmap", + "indexmap 1.9.3", "strsim", "termcolor", "textwrap", @@ -85,6 +107,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "crc32fast" version = "1.3.2" @@ -94,6 +122,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + [[package]] name = "dlib" version = "0.5.0" @@ -120,6 +161,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + [[package]] name = "fdeflate" version = "0.3.0" @@ -139,12 +186,285 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.22", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gio-sys" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ccf87c30a12c469b6d958950f6a9c09f2be20b7773f7e70d20b867fdf2628c3" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fad45ba8d4d2cea612b432717e834f48031cd8853c8aaf43b2c79fec8d144b" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca5c79337338391f1ab8058d6698125034ce8ef31b72a442437fa6c8580de26" +dependencies = [ + "anyhow", + "heck", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "glib-sys" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d80aa6ea7bba0baac79222204aa786a6293078c210abe69ef1336911d4bdc4f0" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd34c3317740a6358ec04572c1bcfd3ac0b5b6529275fae255b237b314bb8062" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3113531138b4e41968e33fd003a0d1a635ef6e0cbc309dd5004123000863ac54" +dependencies = [ + "bitflags", + "cfg-if", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "gstreamer-sys", + "libc", + "muldiv", + "num-integer", + "num-rational", + "once_cell", + "option-operations", + "paste", + "pretty-hex", + "smallvec", + "thiserror", +] + +[[package]] +name = "gstreamer-app" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa1550d18fe8d97900148cc97d63a3212c3d53169c8469b9bf617de8953c05a8" +dependencies = [ + "bitflags", + "futures-core", + "futures-sink", + "glib", + "gstreamer", + "gstreamer-app-sys", + "gstreamer-base", + "libc", + "once_cell", +] + +[[package]] +name = "gstreamer-app-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cb5375aa8c23012ec5fadde1a37b972e87680776772669d2628772867056e2" +dependencies = [ + "glib-sys", + "gstreamer-base-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-base" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8ff5dfbf7bcaf1466a385b836bad0d8da25759f121458727fdda1f771c69b3" +dependencies = [ + "atomic_refcell", + "bitflags", + "cfg-if", + "glib", + "gstreamer", + "gstreamer-base-sys", + "libc", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26114ed96f6668380f5a1554128159e98e06c3a7a8460f216d7cd6dce28f928c" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56fe047adef7d47dbafa8bc1340fddb53c325e16574763063702fc94b5786d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-video" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce97769effde2d779dc4f7037b37106457b74e53f2a711bddc90b30ffeb7e06" +dependencies = [ + "bitflags", + "cfg-if", + "futures-channel", + "glib", + "gstreamer", + "gstreamer-base", + "gstreamer-video-sys", + "libc", + "once_cell", +] + +[[package]] +name = "gstreamer-video-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66ddb6112d438aac0004d2db6053a572f92b1c5e0e9d6ff6c71d9245f7f73e46" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-base-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -182,7 +502,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", ] [[package]] @@ -274,6 +604,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "muldiv" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" + [[package]] name = "nix" version = "0.26.2" @@ -318,12 +654,39 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "option-operations" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" +dependencies = [ + "paste", +] + [[package]] name = "os_str_bytes" version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + [[package]] name = "pin-utils" version = "0.1.0" @@ -349,11 +712,51 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "pretty-hex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] @@ -369,25 +772,64 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "simd-adler32" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -406,6 +848,47 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1c7f239eb94671427157bd93b3694320f3668d4e1eff08c7285366fd777fac" + [[package]] name = "termcolor" version = "1.2.0" @@ -421,12 +904,78 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.22", +] + +[[package]] +name = "toml" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wayland-backend" version = "0.1.2" @@ -502,13 +1051,18 @@ dependencies = [ ] [[package]] -name = "wayshot" -version = "1.2.2" +name = "waystream" +version = "0.1.0" dependencies = [ + "anyhow", "clap", + "derive_more", "env_logger", "flate2", - "image", + "gstreamer", + "gstreamer-app", + "gstreamer-video", + "gstreamer-video-sys", "libwayshot", "log", "wayland-client", @@ -611,3 +1165,12 @@ name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 70b3ace4..b6e65d70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] members = [ - "wayshot", + "waystream", "libwayshot", ] diff --git a/README.md b/README.md index 4124b31b..3f11dd34 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,61 @@ -

- wayshot -

A native, blazing-fast 🚀🚀🚀 screenshot tool for wlroots based compositors such as sway and river written in Rust.

+# waystream - Wayland Desktop Streaming +waystream streams your Wayland desktop session over the network + +It was originally forked from [wayshot](https://github.com/waycrate/wayshot) - a screenshot tool for Wayland +and has since been turned into a Wayland desktop streaming application -

- - - - - -
- -

-

-# Some usage examples: +> **Note** +> Note that it is currently limited to compositors implementing zwlr_screencopy_v1 -NOTE: Read `man 7 wayshot` for more examples. - -NOTE: Read `man wayshot` for flag information. - -Region Selection: - -```bash -wayshot -s "$(slurp)" +## Usage +Stream raw video to some host over UDP, show framerate data as overlay ``` - -Fullscreen: - -```bash -wayshot +$ waystream --udphost 127.0.0.1 --udpport 2342 --showfps ``` - -Screenshot and copy to clipboard: - -```bash -wayshot --stdout | wl-copy +Stream raw video to some host over UDP, show framerate data as overlay, +scale video to width and height ``` - -Pick a hex color code, using ImageMagick: - -```bash -wayshot -s "$(slurp -p)" --stdout | convert - -format '%[pixel:p{0,0}]' txt:-|egrep "#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})" -o +$ waystream --udphost 127.0.0.1 --udpport 2342 --showfps --width 320 --height 240 ``` - -# Installation - -## AUR: - -`wayshot-git` & `wayshot-bin` have been packaged. - -## Compile time dependencies: - -- scdoc (If present, man-pages will be generated.) -- rustup -- make -- meson (Required to build XDPW.) -- ninja (Required to build XDPW.) - -## Compiling: - -- `git clone https://github.com/waycrate/wayshot && cd wayshot` -- `make setup` -- `make` -- `sudo make install` - -# Support: - -1. https://matrix.to/#/#waycrate-tools:matrix.org -2. https://discord.gg/KKZRDYrRYW - -# Smithay Developers: - -Massive thanks to smithay developer Cmeissl and Victor Berger. Without them this project won't be possible as my wayland knowledge is limited. +Show usage +``` +$ waystream --help + +waystream 0.1.0 +Streaming tool for Wayland compositors implementing zwlr_screencopy_v1. + +USAGE: + waystream [OPTIONS] --udphost --udpport + +OPTIONS: + -c, --cursor Enable cursor in stream + -d, --debug Enable debug mode + -h, --udphost Set the host to stream to + -h, --udpport Set the port to stream to + --help Print help information + -l, --listoutputs List all valid outputs + -o, --output Choose a particular display to stream + -r, --showfps Show framerate + -s, --slurp Select a portion of display to stream using slurp + --stdout Output the image data to standard out + -V, --version Print version information + -x, --width Set the target video width + -y, --height Set the target video height +``` +## Authors +Björn Busse +Wladimir Leuschner +Shinyzenith (wayshot/libwayshot) + +## Contributors + +## Dependencies +glib2-devel +gstreamer1-devel + +## References +[wayshot](https://github.com/waycrate/wayshot) +[Wayland Protocol](https://wayland.freedesktop.org/) +[Wayland Protocol in Wikipedia](https://en.wikipedia.org/wiki/Wayland_(protocol)) +[wlroots compositor](https://gitlab.freedesktop.org/wlroots/wlroots) diff --git a/contrib/PKGBUILD/bin/PKGBUILD b/contrib/PKGBUILD/bin/PKGBUILD index 08e562bc..50316c0f 100644 --- a/contrib/PKGBUILD/bin/PKGBUILD +++ b/contrib/PKGBUILD/bin/PKGBUILD @@ -1,18 +1,18 @@ # Maintainer: Aakash Sen Sharma -pkgname=wayshot-bin -_pkgname=wayshot -pkgver=1.2.2 +pkgname=waystream-bin +_pkgname=waystream +pkgver=0.1.0 pkgrel=2 -pkgdesc="A screenshot tool for wlroots compositors." -url="https://github.com/waycrate/wayshot" +pkgdesc="A streaming tool for wlroots based compositors" +url="https://github.com/bbusse/waystream" license=('BSD') arch=('x86_64') optdepends=('slurp: for area selection') makedepends=('scdoc' 'rust') -provides=('wayshot-bin') -conflicts=('wayshot-git' 'wayshot-musl-git') +provides=('waystream-bin') +conflicts=('waystream-git' 'waystream-musl-git') source=("$_pkgname-bin::$url/releases/download/$pkgver/$_pkgname" - "$_pkgname-src::git+https://git.sr.ht/~shinyzenith/$_pkgname" + "$_pkgname-src::git+https://github.com/bbusse/$_pkgname" ) sha256sums=('SKIP' 'SKIP' diff --git a/contrib/PKGBUILD/git/PKGBUILD b/contrib/PKGBUILD/git/PKGBUILD index bd78e968..bfcd5c39 100644 --- a/contrib/PKGBUILD/git/PKGBUILD +++ b/contrib/PKGBUILD/git/PKGBUILD @@ -1,16 +1,16 @@ -# Maintainer: Aakash Sharma -pkgname='wayshot-git' -_pkgname="wayshot" +# Maintainer: Aakash Sharma +pkgname='waystream-git' +_pkgname="waystream" pkgver=1.1.9.98.g538caac pkgrel=1 arch=('x86_64') -url="https://git.sr.ht/~shinyzenith/wayshot" -pkgdesc="A screenshot tool for wlroots compositors." +url="https://github.com/bbusse/waystream" +pkgdesc="A streaming tool for wlroots based compositors" license=('BSD') optdepends=('slurp: for area selection') makedepends=('rustup' 'git' 'scdoc') -conflicts=('wayshot-bin' 'wayshot-musl-git') -source=("$_pkgname::git+https://git.sr.ht/~shinyzenith/$_pkgname") +conflicts=('waystream-bin' 'waystream-musl-git') +source=("$_pkgname::git+https://github.com/bbusse/$_pkgname") sha256sums=('SKIP') build(){ @@ -20,9 +20,9 @@ build(){ package() { cd "$_pkgname" - install -Dm 755 ./target/release/wayshot "$pkgdir/usr/bin/wayshot" - install -Dm 644 ./docs/wayshot.1.gz "$pkgdir/usr/share/man/man1/wayshot.1.gz" - install -Dm 644 ./docs/wayshot.7.gz "$pkgdir/usr/share/man/man7/wayshot.7.gz" + install -Dm 755 ./target/release/waystream "$pkgdir/usr/bin/waystream" + install -Dm 644 ./docs/waystream.1.gz "$pkgdir/usr/share/man/man1/waystream.1.gz" + install -Dm 644 ./docs/waystream.7.gz "$pkgdir/usr/share/man/man7/waystream.7.gz" } pkgver() { diff --git a/docs/assets/wayshot.png b/docs/assets/wayshot.png deleted file mode 100644 index 25e1c032..00000000 Binary files a/docs/assets/wayshot.png and /dev/null differ diff --git a/docs/wayshot.1.scd b/docs/wayshot.1.scd deleted file mode 100644 index c45b5819..00000000 --- a/docs/wayshot.1.scd +++ /dev/null @@ -1,64 +0,0 @@ -wayshot(1) "github.com/waycrate/wayshot" "General Commands Manual" - -# NAME - -Wayshot - Screenshot tool for compositors implementing zwlr_screencopy_v1 such as sway and river - -# SYNOPSIS - -*wayshot* [_options_] - -# OPTIONS - -*-h*, *--help* - Print help message and quit. - -*-V*, *--version* - Print version information. - -*-d*, *--debug* - Enable debug mode. - -*-c*, *--cursor* - Enable cursor visibility in screenshots. - -*-e*, *--extension* - Set the image encoder. - Valid arguments: - - jpeg - - jpg - - png (Default encoder) - - ppm - -*-f*, *--file* - Set a custom file path. The default path is `./{current_unix_timestamp}-wayshot.{encoder}` - eg: 1659034753-wayshot.png - -*-l*, *--listoutputs* - List all valid output names. This flag is generally used in combination with *-o* flag. - -*-o*, *--output* - Choose a particular display (wl_output) to screenshot. - -*-s*, *--slurp* - Choose a portion of your display to screenshot using the slurp program. - https://github.com/emersion/slurp . Valid arguments have the form - "%x %y %w %h" or "%x,%y %wx%h", where for example "%w" is an integer giving - the width of the region. - -*--stdout* - Emit image data to stdout. The following flag is helpful to pipe image data - to other programs. - -# KNOWN BUGS - -Feel free to send patches for the following: -- *--slurp* flag does not work as intended on multi monitor systems. After multiple attempts at fixing this I have failed time and again. - -# SEE ALSO - - wayshot(7) - -# AUTHORS - -Maintained by Shinyzenith . -For more information about development, see . diff --git a/docs/wayshot.7.scd b/docs/wayshot.7.scd deleted file mode 100644 index 6ac3f114..00000000 --- a/docs/wayshot.7.scd +++ /dev/null @@ -1,46 +0,0 @@ -wayshot(7) "github.com/waycrate/wayshot" "Miscellaneous Information Manual" - -# NAME - -Wayshot - Screenshot tool for compositors implementing zwlr_screencopy_v1 such as sway and river - -# SYNOPSIS - -*wayshot* [_options_] - -# REGION SELECTION -wayshot -s "$(slurp -f '%x %y %w %h')" - -# FULLSCREEN - -wayshot - -# CUSTOM FILE PATH AND EXTENSION - -wayshot -f ../screenshot.png --extension ppm - -# SCREENSHOT AND COPY TO CLIPBOARD - -wayshot --stdout -e jpeg | wl-copy - -# SCREENSHOT A PARTICULAR DISPLAY - -wayshot -l # Pick any output name from the following. We use eDP-1 for this example. -wayshot -o eDP-1 - -# PICK A HEX COLOR CODE, USING IMAGEMAGICk - -wayshot -s "$(slurp -p -f '%x %y %w %h')" --stdout | convert - -format '%[pixel:p{0,0}]' txt:-|egrep "#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})" -o - -# PICK A HEX COLOR CODE WITHOUT USING IMAGEMAGICK - -wayshot -s "$(slurp -p -f '%x %y %w %h')" --stdout -e ppm | tail -c 3 | od -An -tuC | xargs printf '#%02X%02X%02X\n' - -# PICK A COLOR, USING IMAGEMAGICK - -wayshot -s "$(slurp -p -f '%x %y %w %h')" --stdout | convert - -format '%[pixel:p{0,0}]' txt:- - -# AUTHORS - -Maintained by Shinyzenith . -For more information about development, see . diff --git a/flake.lock b/flake.lock deleted file mode 100644 index b7660898..00000000 --- a/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1687793116, - "narHash": "sha256-6xRgZ2E9r/BNam87vMkHJ/0EPTTKzeNwhw3abKilEE4=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "9e4e0807d2142d17f463b26a8b796b3fe20a3011", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index d4730425..00000000 --- a/flake.nix +++ /dev/null @@ -1,53 +0,0 @@ -{ - description = "Wayshot devel"; - - inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; }; - - outputs = { self, nixpkgs, ... }: - let - pkgsFor = system: - import nixpkgs { - inherit system; - overlays = [ ]; - }; - - targetSystems = [ "aarch64-linux" "x86_64-linux" ]; - in { - devShells = nixpkgs.lib.genAttrs targetSystems (system: - let pkgs = pkgsFor system; - in { - default = pkgs.mkShell { - name = "Wayshot-devel"; - nativeBuildInputs = with pkgs; [ - # Compilers - clang - cmake - meson - ninja - cargo - rustc - scdoc - - # Libs - inih - pipewire - wayland - systemd - mesa - wayland-protocols - - # Tools - rustfmt - clippy - pkg-config - gdb - gnumake - rust-analyzer - strace - valgrind - wayland-scanner - ]; - }; - }); - }; -} diff --git a/libwayshot/src/convert.rs b/libwayshot/src/convert.rs index 6df3e655..7da4f9ba 100644 --- a/libwayshot/src/convert.rs +++ b/libwayshot/src/convert.rs @@ -40,7 +40,7 @@ impl Convert for ConvertNone { impl Convert for ConvertRGB8 { fn convert_inplace(&self, data: &mut [u8]) -> ColorType { for chunk in data.chunks_exact_mut(4) { - chunk.swap(0, 2); + //chunk.swap(0, 2); } ColorType::Rgba8 } diff --git a/libwayshot/src/lib.rs b/libwayshot/src/lib.rs index 2bcec3d5..6032bb37 100644 --- a/libwayshot/src/lib.rs +++ b/libwayshot/src/lib.rs @@ -287,6 +287,8 @@ pub fn capture_output_frame( // Copy the pixel data advertised by the compositor into the buffer we just created. frame.copy(&buffer); + buffer.destroy(); + shm_pool.destroy(); // On copy the Ready / Failed events are fired by the frame object, so here we check for them. loop { diff --git a/wayshot/Cargo.toml b/wayshot/Cargo.toml deleted file mode 100644 index f8a2e535..00000000 --- a/wayshot/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -authors = ["Shinyzenith "] -description = "Screenshot tool for wlroots based compositors implementing the zwlr_screencopy_v1 protocol." -documentation = "https://docs.rs/crate/wayshot/latest" -edition = "2021" -homepage = "https://waycrate.github.io" -keywords = ["screenshot", "wayland", "wlroots"] -license = "BSD-2-Clause" -name = "wayshot" -repository = "https://git.sr.ht/~shinyzenith/wayshot" -version = "1.2.2" -exclude = [ - "CODE_OF_CONDUCT.md", - "CONTRIBUTING.md", - "contrib/*", - "docs/assets/*", - "release.sh", -] - -[build-dependencies] -flate2 = "1.0.24" - -[dependencies] -clap = "3.1.18" - -env_logger = { version = "0.9.0", default-features = false, features = ["atty", "termcolor"] } -log = "0.4.17" - -wayland-client = "0.30.0" -wayland-protocols = { version = "0.30.0", features=["client", "unstable"] } - -libwayshot = { path = "../libwayshot" } - -image = { version = "0.24", default-features = false, features = ["jpeg", "png", "pnm"] } - -[[bin]] -name = "wayshot" -path = "src/wayshot.rs" diff --git a/wayshot/src/clap.rs b/wayshot/src/clap.rs deleted file mode 100644 index 397ec281..00000000 --- a/wayshot/src/clap.rs +++ /dev/null @@ -1,60 +0,0 @@ -use clap::{arg, Command}; - -pub fn set_flags() -> Command<'static> { - let app = Command::new("wayshot") - .version(env!("CARGO_PKG_VERSION")) - .author(env!("CARGO_PKG_AUTHORS")) - .about("Screenshot tool for compositors implementing zwlr_screencopy_v1.") - .arg( - arg!(-d - -debug) - .required(false) - .takes_value(false) - .help("Enable debug mode"), - ) - .arg( - arg!(-s --slurp ) - .required(false) - .takes_value(true) - .help("Choose a portion of your display to screenshot using slurp"), - ) - .arg( - arg!(-f - -file ) - .required(false) - .conflicts_with("stdout") - .takes_value(true) - .help("Mention a custom file path"), - ) - .arg( - arg!(-c - -cursor) - .required(false) - .takes_value(false) - .help("Enable cursor in screenshots"), - ) - .arg( - arg!(--stdout) - .required(false) - .conflicts_with("file") - .takes_value(false) - .help("Output the image data to standard out"), - ) - .arg( - arg!(-e --extension ) - .required(false) - .takes_value(true) - .help("Set image encoder (Png is default)"), - ) - .arg( - arg!(-l - -listoutputs) - .required(false) - .takes_value(false) - .help("List all valid outputs"), - ) - .arg( - arg!(-o --output ) - .required(false) - .takes_value(true) - .conflicts_with("slurp") - .help("Choose a particular display to screenshot"), - ); - app -} diff --git a/wayshot/src/wayshot.rs b/wayshot/src/wayshot.rs deleted file mode 100644 index 8ca8fa87..00000000 --- a/wayshot/src/wayshot.rs +++ /dev/null @@ -1,298 +0,0 @@ -use std::{ - cmp, env, - error::Error, - fs::File, - io::{stdout, BufWriter, Cursor, Write}, - process::exit, - time::{SystemTime, UNIX_EPOCH}, -}; - -use image::imageops::overlay; -use libwayshot::CaptureRegion; -use wayland_client::{ - globals::{registry_queue_init, GlobalListContents}, - protocol::{wl_output::WlOutput, wl_registry}, - Connection, QueueHandle, -}; - -mod clap; -mod output; - -// TODO: Create a xdg-shell surface, check for the enter event, grab the output from it. - -struct WayshotState {} - -impl wayland_client::Dispatch for WayshotState { - fn event( - _: &mut WayshotState, - _: &wl_registry::WlRegistry, - _: wl_registry::Event, - _: &GlobalListContents, - _: &Connection, - _: &QueueHandle, - ) { - } -} - -struct IntersectingOutput { - output: WlOutput, - region: CaptureRegion, -} - -enum CaptureInfo { - Region(CaptureRegion), - Output(WlOutput), -} - -fn main() -> Result<(), Box> { - let args = clap::set_flags().get_matches(); - env::set_var("RUST_LOG", "wayshot=info"); - - if args.is_present("debug") { - env::set_var("RUST_LOG", "wayshot=trace"); - } - - env_logger::init(); - log::trace!("Logger initialized."); - - let mut conn = Connection::connect_to_env().unwrap(); - let (mut globals, _) = registry_queue_init::(&conn).unwrap(); - - if args.is_present("listoutputs") { - let valid_outputs = output::get_all_outputs(&mut globals, &mut conn); - for output in valid_outputs { - log::info!("{:#?}", output.name); - } - exit(1); - } - - let mut cursor_overlay: i32 = 0; - if args.is_present("cursor") { - cursor_overlay = 1; - } - - let capture_area = if args.is_present("slurp") { - CaptureInfo::Region(match parse_geometry(args.value_of("slurp").unwrap()) { - Some(region) => region, - None => { - log::error!("Invalid geometry specification"); - exit(1); - } - }) - } else if args.is_present("output") { - CaptureInfo::Output(output::get_wloutput( - args.value_of("output").unwrap().trim().to_string(), - output::get_all_outputs(&mut globals, &mut conn), - )) - } else { - let mut start_x = 0; - let mut start_y = 0; - - let mut end_x = 0; - let mut end_y = 0; - - let output_infos = output::get_all_outputs(&mut globals, &mut conn); - for outputinfo in output_infos { - if outputinfo.dimensions.x < start_x { - start_x = outputinfo.dimensions.x; - } - if outputinfo.dimensions.y < start_y { - start_y = outputinfo.dimensions.y; - } - if outputinfo.dimensions.x + outputinfo.dimensions.width > end_x { - end_x = outputinfo.dimensions.x + outputinfo.dimensions.width; - } - if outputinfo.dimensions.y + outputinfo.dimensions.height > end_y { - end_y = outputinfo.dimensions.y + outputinfo.dimensions.height; - } - } - CaptureInfo::Region(CaptureRegion { - x_coordinate: start_x, - y_coordinate: start_y, - width: end_x - start_x, - height: end_y - start_y, - }) - }; - - let frame_copy: (Vec, Option<(i32, i32)>) = match capture_area { - CaptureInfo::Region(region) => { - let mut framecopys = Vec::new(); - - let outputs = output::get_all_outputs(&mut globals, &mut conn); - let mut intersecting_outputs: Vec = Vec::new(); - for output in outputs.iter() { - let x1: i32 = cmp::max(output.dimensions.x, region.x_coordinate); - let y1: i32 = cmp::max(output.dimensions.y, region.y_coordinate); - let x2: i32 = cmp::min( - output.dimensions.x + output.dimensions.width, - region.x_coordinate + region.width, - ); - let y2: i32 = cmp::min( - output.dimensions.y + output.dimensions.height, - region.y_coordinate + region.height, - ); - - let width = x2 - x1; - let height = y2 - y1; - - if !(width <= 0 || height <= 0) { - let true_x = region.x_coordinate - output.dimensions.x; - let true_y = region.y_coordinate - output.dimensions.y; - let true_region = CaptureRegion { - x_coordinate: true_x, - y_coordinate: true_y, - width: region.width, - height: region.height, - }; - intersecting_outputs.push(IntersectingOutput { - output: output.wl_output.clone(), - region: true_region, - }); - } - } - if intersecting_outputs.is_empty() { - log::error!("Provided capture region doesn't intersect with any outputs!"); - exit(1); - } - - for ouput_info in intersecting_outputs { - framecopys.push(libwayshot::capture_output_frame( - &mut globals, - &mut conn, - cursor_overlay, - ouput_info.output.clone(), - Some(ouput_info.region), - )?); - } - (framecopys, Some((region.width, region.height))) - } - CaptureInfo::Output(output) => ( - vec![libwayshot::capture_output_frame( - &mut globals, - &mut conn, - cursor_overlay, - output, - None, - )?], - None, - ), - }; - let extension = if args.is_present("extension") { - let ext: &str = &args.value_of("extension").unwrap().trim().to_lowercase(); - log::debug!("Using custom extension: {:#?}", ext); - - match ext { - "jpeg" | "jpg" => libwayshot::EncodingFormat::Jpg, - "png" => libwayshot::EncodingFormat::Png, - "ppm" => libwayshot::EncodingFormat::Ppm, - _ => { - log::error!("Invalid extension provided.\nValid extensions:\n1) jpeg\n2) jpg\n3) png\n4) ppm"); - exit(1); - } - } - } else { - libwayshot::EncodingFormat::Png - }; - - if frame_copy.0.len() == 1 { - let frame_copy = &frame_copy.0[0]; - if args.is_present("stdout") { - let stdout = stdout(); - let mut writer = BufWriter::new(stdout.lock()); - libwayshot::write_to_file(&mut writer, extension, frame_copy)?; - } else { - let path = if args.is_present("file") { - args.value_of("file").unwrap().trim().to_string() - } else { - get_default_file_name(extension) - }; - - libwayshot::write_to_file(&mut File::create(path)?, extension, frame_copy)?; - } - } else { - let mut images = Vec::new(); - let (frame_copy, region) = frame_copy; - let (width, height) = region.unwrap(); - for frame_copy in frame_copy { - let mut buff = Cursor::new(Vec::new()); - libwayshot::write_to_file(&mut buff, extension, &frame_copy)?; - let image = image::load_from_memory(buff.get_ref())?; - let image = image::imageops::resize( - &image, - width as u32, - height as u32, - image::imageops::FilterType::Gaussian, - ); - images.push(image); - } - let mut image_bottom = images[0].clone(); - for image in images { - overlay(&mut image_bottom, &image, 0, 0); - } - if args.is_present("stdout") { - let stdout = stdout(); - let mut buff = Cursor::new(Vec::new()); - - let mut writer = BufWriter::new(stdout.lock()); - image_bottom.write_to(&mut buff, extension)?; - - writer.write_all(buff.get_ref())?; - } else { - let path = if args.is_present("file") { - args.value_of("file").unwrap().trim().to_string() - } else { - get_default_file_name(extension) - }; - - image_bottom.save(path)?; - } - } - Ok(()) -} - -fn get_default_file_name(extension: libwayshot::EncodingFormat) -> String { - let time = match SystemTime::now().duration_since(UNIX_EPOCH) { - Ok(n) => n.as_secs().to_string(), - Err(_) => { - log::error!("SystemTime before UNIX EPOCH!"); - exit(1); - } - }; - - time + "-wayshot." + extension.into() -} - -fn parse_geometry(g: &str) -> Option { - let tail = g.trim(); - let x_coordinate: i32; - let y_coordinate: i32; - let width: i32; - let height: i32; - - if tail.contains(',') { - // this accepts: "%d,%d %dx%d" - let (head, tail) = tail.split_once(',')?; - x_coordinate = head.parse::().ok()?; - let (head, tail) = tail.split_once(' ')?; - y_coordinate = head.parse::().ok()?; - let (head, tail) = tail.split_once('x')?; - width = head.parse::().ok()?; - height = tail.parse::().ok()?; - } else { - // this accepts: "%d %d %d %d" - let (head, tail) = tail.split_once(' ')?; - x_coordinate = head.parse::().ok()?; - let (head, tail) = tail.split_once(' ')?; - y_coordinate = head.parse::().ok()?; - let (head, tail) = tail.split_once(' ')?; - width = head.parse::().ok()?; - height = tail.parse::().ok()?; - } - - Some(libwayshot::CaptureRegion { - x_coordinate, - y_coordinate, - width, - height, - }) -} diff --git a/waystream/Cargo.toml b/waystream/Cargo.toml new file mode 100644 index 00000000..a1ea2150 --- /dev/null +++ b/waystream/Cargo.toml @@ -0,0 +1,46 @@ +[package] +authors = [ + "Björn Busse ", + "Wladimir Leuschner ", + "Shinyzenith " +] +description = "Streaming tool for wlroots based compositors implementing the zwlr_screencopy_v1 protocol" +documentation = "https://docs.rs/crate/waystream/latest" +edition = "2021" +homepage = "https://github.com/bbusse/waystream" +keywords = ["stream", "video", "wayland", "wlroots"] +license = "BSD-2-Clause" +name = "waystream" +repository = "https://github.com/bbusse/waystream" +version = "0.1.0" +exclude = [ + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "contrib/*", + "docs/assets/*", + "release.sh", +] + +[build-dependencies] +flate2 = "1.0.24" + +[dependencies] +anyhow = "1.0" +clap = "3.1.18" +derive_more = "0.99.5" +gstreamer = "0.20.2" +gstreamer-app = "0.20.0" +gstreamer-video = "0.20.4" +gstreamer-video-sys = "0.20.0" + +env_logger = { version = "0.9.0", default-features = false, features = ["atty", "termcolor"] } +log = "0.4.17" + +wayland-client = "0.30.0" +wayland-protocols = { version = "0.30.0", features=["client", "unstable"] } + +libwayshot = { path = "../libwayshot" } + +[[bin]] +name = "waystream" +path = "src/waystream.rs" diff --git a/wayshot/build.rs b/waystream/build.rs similarity index 100% rename from wayshot/build.rs rename to waystream/build.rs diff --git a/waystream/src/clap.rs b/waystream/src/clap.rs new file mode 100644 index 00000000..dcd5c1ab --- /dev/null +++ b/waystream/src/clap.rs @@ -0,0 +1,73 @@ +use clap::{arg, Command}; + +// https://github.com/clap-rs/clap/issues/4869 +// 4.0 regression: dashes are not accepted any more #4869 + +pub fn set_flags() -> Command<'static> { + let app = Command::new("waystream") + .version(env!("CARGO_PKG_VERSION")) + .author(env!("CARGO_PKG_AUTHORS")) + .about("Streaming tool for Wayland compositors implementing zwlr_screencopy_v1") + .arg( + arg!(--debug) + .required(false) + .takes_value(false) + .help("Enable debug mode"), + ) + .arg( + arg!(--slurp ) + .required(false) + .takes_value(true) + .help("Select a portion of display to stream using slurp"), + ) + .arg( + arg!(--showfps ) + .required(false) + .takes_value(false) + .help("Show framerate"), + ) + .arg( + arg!(--cursor) + .required(false) + .takes_value(false) + .help("Enable cursor in stream"), + ) + .arg( + arg!(--udphost ) + .required(true) + .takes_value(true) + .help("Set the host to stream to"), + ) + .arg( + arg!(--udpport ) + .required(true) + .takes_value(true) + .help("Set the port to stream to"), + ) + .arg( + arg!(--height ) + .required(false) + .takes_value(true) + .help("Set the target video height"), + ) + .arg( + arg!(--width ) + .required(false) + .takes_value(true) + .help("Set the target video width"), + ) + .arg( + arg!(--listoutputs) + .required(false) + .takes_value(false) + .help("List all outputs"), + ) + .arg( + arg!(--output ) + .required(false) + .takes_value(true) + .conflicts_with("slurp") + .help("Select a particular display to stream (Not implemented yet)"), + ); + app +} diff --git a/wayshot/src/output.rs b/waystream/src/output.rs similarity index 100% rename from wayshot/src/output.rs rename to waystream/src/output.rs diff --git a/waystream/src/waystream.rs b/waystream/src/waystream.rs new file mode 100644 index 00000000..52769318 --- /dev/null +++ b/waystream/src/waystream.rs @@ -0,0 +1,450 @@ +use std::{ + cmp, env, + error::Error, + process::exit, + time::{SystemTime, UNIX_EPOCH, Duration}, +}; + +use libwayshot::CaptureRegion; +use wayland_client::{ + globals::{registry_queue_init, GlobalList, GlobalListContents}, + protocol::{wl_output::WlOutput, wl_registry}, + Connection, QueueHandle, +}; + +use anyhow::Error as aError; +use derive_more::{Display, Error}; +use gstreamer::glib; +use gstreamer::prelude::*; +use gstreamer_video; + +#[derive(Debug, Display, Error)] +#[display(fmt = "Missing element {}", _0)] +struct MissingElement(#[error(not(source))] &'static str); + +#[derive(Debug, Display, Error)] +#[display(fmt = "Received error from {}: {} (debug: {:?})", src, error, debug)] +struct ErrorMessage { + src: glib::GString, + error: glib::Error, + debug: Option, +} + +struct PipeOptions { + width: usize, + height: usize, + target_width: i32, + target_height: i32, + show_fps: bool, +} + +mod clap; +mod output; + +// TODO: Create a xdg-shell surface, check for the enter event, grab the output from it. + +struct WaystreamState {} + +impl wayland_client::Dispatch for WaystreamState { + fn event( + _: &mut WaystreamState, + _: &wl_registry::WlRegistry, + _: wl_registry::Event, + _: &GlobalListContents, + _: &Connection, + _: &QueueHandle, + ) { + } +} + +struct IntersectingOutput { + output: WlOutput, + region: CaptureRegion, +} +#[derive(Clone)] +#[derive(Debug)] +enum CaptureInfo { + Region(CaptureRegion), + Output(WlOutput), +} + +fn parse_geometry(g: &str) -> Option { + let tail = g.trim(); + let x_coordinate: i32; + let y_coordinate: i32; + let width: i32; + let height: i32; + + if tail.contains(',') { + // this accepts: "%d,%d %dx%d" + let (head, tail) = tail.split_once(',')?; + x_coordinate = head.parse::().ok()?; + let (head, tail) = tail.split_once(' ')?; + y_coordinate = head.parse::().ok()?; + let (head, tail) = tail.split_once('x')?; + width = head.parse::().ok()?; + height = tail.parse::().ok()?; + } else { + // this accepts: "%d %d %d %d" + let (head, tail) = tail.split_once(' ')?; + x_coordinate = head.parse::().ok()?; + let (head, tail) = tail.split_once(' ')?; + y_coordinate = head.parse::().ok()?; + let (head, tail) = tail.split_once(' ')?; + width = head.parse::().ok()?; + height = tail.parse::().ok()?; + } + + Some(libwayshot::CaptureRegion { + x_coordinate, + y_coordinate, + width, + height, + }) +} + +fn create_pipeline(mut conn: Connection, + mut globals: GlobalList, + area: CaptureInfo, + pipe_opts: PipeOptions, + cursor_overlay: i32) -> Result { + + gstreamer::init()?; + + let pipeline = gstreamer::Pipeline::default(); + + let video_info = gstreamer_video::VideoInfo::builder(gstreamer_video::VideoFormat::Rgbx, pipe_opts.width as u32, pipe_opts.height as u32) + //.fps(gstreamer::Fraction::new(25, 1)) + .build() + .expect("Failed to create video info"); + + let appsrc = gstreamer_app::AppSrc::builder() + .caps(&video_info.to_caps().unwrap()) + .format(gstreamer::Format::Time) + .build(); + + // Convert for each sink + let videoconvert_0 = gstreamer::ElementFactory::make("videoconvert").build()?; + let videoconvert_1 = gstreamer::ElementFactory::make("videoconvert").build()?; + + let videosink = gstreamer::ElementFactory::make("waylandsink").build()?; + let fpssink = gstreamer::ElementFactory::make("fpsdisplaysink").property("video-sink", &videosink) + .property("text-overlay", pipe_opts.show_fps) + .build()?; + + let hlssink = gstreamer::ElementFactory::make("hlssink2") + .name("hlssink") + .property("target-duration", 2u32) + .property("playlist-length", 2u32) + .property("max-files", 2u32) + .build() + .expect("Unable to instantiate hlssink"); + + let netsink = gstreamer::ElementFactory::make("udpsink").property("host", "127.0.0.1") + .property("port", 2490) + .build()?; + + let x264enc = gstreamer::ElementFactory::make("x264enc").build() + .expect("Unable to instantiate x264enc"); + + let h264parse = gstreamer::ElementFactory::make("h264parse").build() + .expect("Unable to instantiate h264parse"); + + let scale = gstreamer::ElementFactory::make("videoscale") + .name("scale") + .build() + .expect("Could not create convert element"); + + let video_caps_scale = gstreamer::Caps::builder("video/x-raw") + .field("width", pipe_opts.target_width) + .field("height", pipe_opts.target_height) + .build(); + + let filter = gstreamer::ElementFactory::make("capsfilter") + .name("caps") + .build() + .expect("Could not create caps element"); + + if pipe_opts.target_height > 0 && pipe_opts.target_height > 0 { + filter.set_property("caps", &video_caps_scale); + } + + let audio_tee = gstreamer::ElementFactory::make("tee") + .property("allow-not-linked", true) + .build()?; + + let video_tee = gstreamer::ElementFactory::make("tee") + .property("allow-not-linked", true) + .build()?; + + let video_tee_queue_0 = gstreamer::ElementFactory::make("queue") + .build()?; + let video_tee_queue_1 = gstreamer::ElementFactory::make("queue") + .build()?; + let video_tee_queue_2 = gstreamer::ElementFactory::make("queue") + .build()?; + + pipeline.add_many(&[appsrc.upcast_ref(), &scale, + &filter, + &video_tee, + &video_tee_queue_0, + &video_tee_queue_1, + //&video_tee_queue_2, + //&hlssink, + &netsink, + &fpssink])?; + + gstreamer::Element::link_many(&[appsrc.upcast_ref(), &scale, &filter, &video_tee])?; + gstreamer::Element::link_many(&[&video_tee, &video_tee_queue_0, &netsink])?; + gstreamer::Element::link_many(&[&video_tee, &video_tee_queue_1, &fpssink])?; + //gstreamer::Element::link_many(&[&video_tee, &video_tee_queue_2, &hlssink])?; + + let mut current_frame = 0; + let mut t: u64 = 0; + appsrc.set_callbacks( + gstreamer_app::AppSrcCallbacks::builder() + .need_data(move |appsrc, _| { + //log::info!("Frame {current_frame}"); + let t0 = SystemTime::now(); + + let frame_copy: (Vec, Option<(i32, i32)>) = match &area { + CaptureInfo::Region(region) => { + let mut framecopys = Vec::new(); + + let outputs = output::get_all_outputs(&mut globals, &mut conn); + let mut intersecting_outputs: Vec = Vec::new(); + for output in outputs.iter() { + let x1: i32 = cmp::max(output.dimensions.x, region.x_coordinate); + let y1: i32 = cmp::max(output.dimensions.y, region.y_coordinate); + let x2: i32 = cmp::min( + output.dimensions.x + output.dimensions.width, + region.x_coordinate + region.width, + ); + let y2: i32 = cmp::min( + output.dimensions.y + output.dimensions.height, + region.y_coordinate + region.height, + ); + + let width = x2 - x1; + let height = y2 - y1; + + if !(width <= 0 || height <= 0) { + let true_x = region.x_coordinate - output.dimensions.x; + let true_y = region.y_coordinate - output.dimensions.y; + let true_region = CaptureRegion { + x_coordinate: true_x, + y_coordinate: true_y, + width: region.width, + height: region.height, + }; + intersecting_outputs.push(IntersectingOutput { + output: output.wl_output.clone(), + region: true_region, + }); + } + } + if intersecting_outputs.is_empty() { + log::error!("Provided capture region doesn't intersect with any outputs!"); + exit(1); + } + + for ouput_info in intersecting_outputs { + framecopys.push(libwayshot::capture_output_frame( + &mut globals, + &mut conn, + cursor_overlay, + ouput_info.output.clone(), + Some(ouput_info.region), + ).unwrap()); + } + (framecopys, Some((region.width, region.height))) + } + CaptureInfo::Output(output) => ( + vec![libwayshot::capture_output_frame( + &mut globals, + &mut conn, + cursor_overlay, + output.to_owned(), + None, + ).unwrap()], + None, + ), + }; + + // Create the buffer that can hold exactly one RGBx/BGRx frame + let mut buffer = gstreamer::Buffer::with_size(video_info.size()).unwrap(); + { + let buffer = buffer.get_mut().unwrap(); + buffer.set_pts(current_frame * 20 * gstreamer::ClockTime::MSECOND); + + let mut vframe = + gstreamer_video::VideoFrameRef::from_buffer_ref_writable(buffer, &video_info) + .unwrap(); + + let width = vframe.width() as usize; + let height = vframe.height() as usize; + let stride = vframe.plane_stride()[0] as usize; + + let mut npixel: usize = 0; + + for line in vframe + .plane_data_mut(0) + .unwrap() + .chunks_exact_mut(stride) + .take(height) + { + for pixel in line[..(4 * width)].chunks_exact_mut(4) { + pixel[0] = frame_copy.0[0].frame_mmap[npixel]; + pixel[1] = frame_copy.0[0].frame_mmap[npixel+1]; + pixel[2] = frame_copy.0[0].frame_mmap[npixel+2]; + pixel[3] = frame_copy.0[0].frame_mmap[npixel+3]; + npixel += 4 + } + } + } + current_frame += 1; + //println!("{:?}", t0.elapsed()); + let _ = appsrc.push_buffer(buffer); + }) + .build(), + ); + Ok(pipeline) +} + +fn stream(pipeline: gstreamer::Pipeline) -> Result<(), aError> { + pipeline.set_state(gstreamer::State::Playing)?; + + let bus = pipeline + .bus() + .expect("Pipeline without bus. Shouldn't happen!"); + + for msg in bus.iter_timed(gstreamer::ClockTime::NONE) { + use gstreamer::MessageView; + + match msg.view() { + MessageView::Eos(..) => break, + MessageView::Error(err) => { + pipeline.set_state(gstreamer::State::Null)?; + return Err(ErrorMessage { + src: msg + .src() + .map(|s| s.path_string()) + .unwrap_or_else(|| glib::GString::from("UNKNOWN")), + error: err.error(), + debug: err.debug(), + } + .into()); + } + _ => (), + } + } + + pipeline.set_state(gstreamer::State::Null)?; + + Ok(()) +} + +fn main() -> Result<(), Box> { + let args = clap::set_flags().get_matches(); + env::set_var("RUST_LOG", "waystream=info"); + + if args.is_present("debug") { + env::set_var("RUST_LOG", "waystream=trace"); + } + + let mut pipe_opts = PipeOptions { + width: 1366, + height: 768, + target_width: 0, + target_height: 0, + show_fps: false, + }; + + if args.is_present("showfps") { + pipe_opts.show_fps = true; + } + + if args.is_present("width") { + pipe_opts.target_width = args.value_of("width").expect("Int").parse::().unwrap(); + } + + if args.is_present("height") { + pipe_opts.target_height = args.value_of("height").expect("Int").parse::().unwrap(); + } + + env_logger::init(); + + let mut conn = Connection::connect_to_env().unwrap(); + let (mut globals, _) = registry_queue_init::(&conn).unwrap(); + + if args.is_present("listoutputs") { + let valid_outputs = output::get_all_outputs(&mut globals, &mut conn); + for output in valid_outputs { + log::info!("{:#?}", output.name); + } + exit(1); + } + + let output: WlOutput = if args.is_present("output") { + output::get_wloutput( + args.value_of("output").unwrap().trim().to_string(), + output::get_all_outputs(&mut globals, &mut conn), + ) + } else { output::get_all_outputs(&mut globals, &mut conn) .first() + .unwrap() + .wl_output + .clone() + }; + + let mut cursor_overlay: i32 = 0; + if args.is_present("cursor") { + cursor_overlay = 1; + } + + let capture_area = { + let mut start_x = 0; + let mut start_y = 0; + + let mut end_x = 0; + let mut end_y = 0; + + let output_infos = output::get_all_outputs(&mut globals, &mut conn); + for outputinfo in output_infos { + if outputinfo.dimensions.x < start_x { + start_x = outputinfo.dimensions.x; + } + if outputinfo.dimensions.y < start_y { + start_y = outputinfo.dimensions.y; + } + if outputinfo.dimensions.x + outputinfo.dimensions.width > end_x { + end_x = outputinfo.dimensions.x + outputinfo.dimensions.width; + } + if outputinfo.dimensions.y + outputinfo.dimensions.height > end_y { + end_y = outputinfo.dimensions.y + outputinfo.dimensions.height; + } + } + CaptureInfo::Region(CaptureRegion { + x_coordinate: start_x, + y_coordinate: start_y, + width: end_x - start_x, + height: end_y - start_y, + }) + }; + + if let CaptureInfo::Region(r) = capture_area { + pipe_opts.height = usize::try_from(r.height).unwrap(); + pipe_opts.width = usize::try_from(r.width).unwrap(); + }; + + + match create_pipeline(conn, + globals, + capture_area, + pipe_opts, + cursor_overlay).and_then(stream) { + Ok(r) => r, + Err(e) => eprintln!("Error running pipeline: {e}"), + } + + Ok(()) +} diff --git a/xdg-desktop-portal-wlr b/xdg-desktop-portal-wlr deleted file mode 160000 index 58a8c0cd..00000000 --- a/xdg-desktop-portal-wlr +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 58a8c0cd54cb9143033aae84217ba73c090b1735