Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:jlkDE
swww
swww-0.9.5.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File swww-0.9.5.obscpio of Package swww
07070110381EE9000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000001300000000swww-0.9.5/.config07070110381EEE000081A400000000000000000000000166342683000009A6000000080000000100000000000000000000002000000000swww-0.9.5/.config/nextest.toml# This is the default config used by nextest. It is embedded in the binary at # build time. It may be used as a template for .config/nextest.toml. [store] # The directory under the workspace root at which nextest-related files are # written. Profile-specific storage is currently written to dir/<profile-name>. dir = "target/nextest" # This section defines the default nextest profile. Custom profiles are layered # on top of the default profile. [profile.default] # "retries" defines the number of times a test should be retried. If set to a # non-zero value, tests that succeed on a subsequent attempt will be marked as # non-flaky. Can be overridden through the `--retries` option. retries = 0 # Show these test statuses in the output. # # The possible values this can take are: # * none: no output # * fail: show failed (including exec-failed) tests # * retry: show flaky and retried tests # * slow: show slow tests # * pass: show passed tests # * skip: show skipped tests (most useful for CI) # * all: all of the above # # Each value includes all the values above it; for example, "slow" includes # failed and retried tests. # # Can be overridden through the `--status-level` flag. status-level = "pass" # "failure-output" defines when test failures are output to standard output. # Accepted values are # * "immediate": output failures as soon as they happen # * "final": output failures at the end of the test run # * "immediate-final": output failures as soon as they happen and at the end of # the test run # * "never": don't output failures at all # # For large test suites and CI it is generally useful to use "immediate-final". # # Can be overridden through the `--failure-output` option. failure-output = "immediate" # "success-output" controls output on success. This should generally be set to # "never". success-output = "never" # Cancel the test run on the first failure. For CI runs, consider setting this # to false. #fail-fast = true fail-fast = false # Treat a test that takes longer than this as slow, and print a message. slow-timeout = "60s" [profile.default.junit] # Output a JUnit report into the given file inside 'store.dir/<profile-name>'. # If unspecified, JUnit is not written out. # path = "junit.xml" path = "report.xml" # The name of the top-level "report" element in JUnit report. If aggregating # reports across different test runs, it may be useful to provide separate names # for each report. report-name = "nextest-run" 07070130196BD7000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000001D00000000swww-0.9.5/.github/workflows07070130196BDE000081A400000000000000000000000166342683000002DD000000080000000100000000000000000000002700000000swww-0.9.5/.github/workflows/check.ymlname: Check on: workflow_call: env: CARGO_TERM_COLOR: always SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" jobs: clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mozilla-actions/sccache-action@v0.0.3 - uses: dtolnay/rust-toolchain@stable with: components: clippy - run: cargo clippy --version - run: cargo clippy --workspace --locked --tests fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mozilla-actions/sccache-action@v0.0.3 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - run: cargo fmt --version - run: cargo fmt --all --check 07070130196BDF000081A400000000000000000000000166342683000000F6000000080000000100000000000000000000002400000000swww-0.9.5/.github/workflows/ci.ymlname: CI on: pull_request: merge_group: push: branches: - main jobs: check: uses: ./.github/workflows/check.yml dependencies: uses: ./.github/workflows/dependencies.yml test: uses: ./.github/workflows/test.yml 07070130196BE3000081A400000000000000000000000166342683000004B3000000080000000100000000000000000000002E00000000swww-0.9.5/.github/workflows/dependencies.ymlname: Dependencies on: workflow_call: env: CARGO_TERM_COLOR: always SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" jobs: advisories: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mozilla-actions/sccache-action@v0.0.3 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-deny - run: cargo deny check advisories bans: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mozilla-actions/sccache-action@v0.0.3 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-deny - run: cargo deny check bans licences: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mozilla-actions/sccache-action@v0.0.3 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-deny - run: cargo deny check licenses sources: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mozilla-actions/sccache-action@v0.0.3 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-deny - run: cargo deny check sources 07070130196BE5000081A4000000000000000000000001663426830000073C000000080000000100000000000000000000002600000000swww-0.9.5/.github/workflows/test.ymlname: Test on: workflow_call: env: CARGO_TERM_COLOR: always SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" jobs: nightly: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mozilla-actions/sccache-action@v0.0.3 - uses: dtolnay/rust-toolchain@nightly with: components: llvm-tools-preview - uses: taiki-e/install-action@cargo-nextest - uses: taiki-e/install-action@cargo-llvm-cov - name: Run tests run: cargo llvm-cov --workspace --locked nextest --html - name: Upload test report uses: actions/upload-artifact@v3 if: always() with: name: report path: target/nextest/default/report.xml retention-days: 30 - name: Upload coverage results uses: actions/upload-artifact@v3 if: always() with: name: coverage path: target/llvm-cov/ retention-days: 30 stable: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mozilla-actions/sccache-action@v0.0.3 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-nextest - run: cargo build --workspace --locked --verbose - run: cargo nextest run --workspace --locked msrv: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: mozilla-actions/sccache-action@v0.0.3 - uses: SebRollen/toml-action@v1.0.2 id: msrv with: file: 'Cargo.toml' field: 'package.rust-version' - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ steps.msrv.outputs.value }} - uses: taiki-e/install-action@cargo-nextest - run: cargo build --workspace --locked --verbose - run: cargo nextest run --workspace --locked 07070100186F22000081A40000000000000000000000016634268300000025000000080000000100000000000000000000001600000000swww-0.9.5/.gitignoretarget/* completions/* test_images/* 0707010018A8CC000081A40000000000000000000000016634268300004300000000080000000100000000000000000000001800000000swww-0.9.5/CHANGELOG.md### Unreleased ### 0.9.5 This is mostly just fixes and small improvements. #### Fixes * fixed wallpaper never setting `configured` to 'true' * fixed fractional scaling rounding incorrectly * fixed scaling for vertical monitors (thanks, @AhJi26) * fixed the annoying black screen on login issue (finally) #### Additions * add --no-cache option to `swww-daemon`, by @lucasreis1 #### Internal improvements * specialized transition for `--transition-type none` (previously it was an alias to `simple` with special values) * remove an extra call to `thread::sleep` when loading the cache * no longer using an `event_fd` to wake up the main thread ### 0.9.4 Fractional Scaling is finally implemented! Woooo!! That also fixes a nasty problem some people were having with fractionally scaled outputs. Apologies for spaming small releases like these in short order. Hopefully this will be the last one for a while. ### 0.9.3 Quick release to fix a scaling error that might affect a lot of people. #### Fixes * fix wrong scale calculation #### Internal Improvements * deleted leftover `/proc` traversal code in the client * no longer setting nonblocking mode for daemon socket, since we are already polling it * better IPC structs between client and daemon ### 0.9.2 #### Fixes * fix stack overflow on some systems, by @iynaix * make sure we start the daemon even when the socket file already exists * fix build in 32bit x86, by @Calandracas606 * fix image resize when image is larger than monitor * fix transitions performance that had regressed from versions 0.8.* * added SIGHUP to the list of signals we catch to exit properly * allow `swww` to run on a nested wayland environment, by @Fuyukai. #### Improvements * we no longer traverse `/proc` to detect whether the daemon is running, we just try pinging it instead, by @Fuyukai * many internal refactors: - client now sends images in the format requested by the daemon (previously we were transforming the images in the daemon itself) - simplified the daemon's transitions - using `bitcode` instead of `rkyv` for serialization - using `rustix` instead of `nix` for unix stuff - The Big One: we've eliminated our dependency on `smithay-client-toolkit`, now make calls directly to `wayland-client`. This gives us more control over our code for the price of a little extra verbosity. ### 0.9.1 My bad everyone, `0.9.0` wasn't loading the cache, so I am publishing this quick fix. ### 0.9.0 #### BREAKING CHANGES MSRV is now 1.74.0. #### Deprecated `swww init` is now considered deprecated. Use `swww-daemon` instead. To run it in the background, simply do `swww-daemon &`. #### Fixes * fix the `let_underscore_lock` error. Note that all 0.8.* will probably no longer build with newer Rust versions due to that error. By @akida32 * fixed webp and gifs that are only a static image * fixed busy waiting for WlBuffers to be released. This was a big one, and it involved rewriting a ton of stuff. We've implemented our own memory pool and are using frame callbacks to know when to draw now. A big thanks to @YaLTeR, @jeLee6gi for their patience and help with debugging and testing this thing. * properly removing all cache contents on `clean-cache` * always center images that are larger than the monitor * animations no longer overlap when sending two animated images in succession * fix randomize script trying to use directories as images. Fix was suggested by @MRSS02 * waiting for child swww process when loading the cache, preventing zombie processes * waiting for daemon initialization before certain requests. By @musjj #### Improvements * New, better compression function implementations. We are now using some SIMD code to accelerate the frame compression functions, leading to some nice speedups in some cases. Thanks to @Akida31 for their help in verying my unsafe code. * We are using 3 channel color formats for some nice perf and memory improvements. Unfortunately, it seems to not work for some people on some notebooks (see [Known Issues](#known-issues)). * Implemented a way to force the use of a specific wayland_shm format, as a workaround for [Known Issues](#known-issues). Also went ahead and implemented some cli options for the daemon. * Support for animated pngs * Support for animations when piping images from standard input * Fps is now a `u16`, so we can support newest monitors framerates * Created a `restore` command to manually restore the cache. By @musjj * GitHub actions! Big thanks to @MichaelOultram! #### Known Issues Some people are having some problems with the 3 channel color formats (see issue #233). Currently, initializing the daemon with `swww-daemon --format xrgb` is a workaround to that. ### 0.8.2 NOTE ALL 0.8.* VERSIONS WILL PROBABLY NO LONGER BUILD WITH NEWER RUST VERSIONS. This is because Rust promoted `let_underscore_lock` to a hard error, which wasn't the case at the time we've published these versions. #### **ATTENTION PACKAGE MAINTAINERS** I've changed my username from `Horus645` to `LGFae`. This means you will have to update the remote url to `https://github.com/LGFae/swww`. Anyone who also has direct links to the old address should update them. I've done this because mostly to make it more professional looking. I've considered using two github accounts instead, but that would involve setting up multiple ssh keys on my machine and included other complications. I deeply apologize for the inconvenience. #### Changes: * update MSRV in README.md, by @micielski * implemented a `--no-cache` flag for `swww init` * fixes to the build script, by @m4rch3n1ng * client waits for daemon to be ready on `swww init` * more accurate image fit implementation * added MSRV to Cargo.toml, by @akida31 * fix some documentation typos and inacuracies * fix timings for transition frames * fix capacity of Vec in `image_pad` function, by @MichaelOultram * implement animated WebP Support, by @MichaelOultram * some other memory optimizations, by @MichaelOultram * implement `clear-cache` command * we also automatically clear the cache from old `swww` versions now! Also, we have udpdated all crates versions so that they all match. This should help some package maintainers that were having difficulty setting `swww` up for e.g. Debian, I believe. ### 0.8.1 Pretty a much a near-exclusive bug fix release: * Fixed `swww clear` causing the daemon to exit * The cache is once again being correctly load during `swww init` * Fixed glitches happening to animated gifs (frames were being loaded in the wrong order) * Fixed `swww-daemon` sometimes not drawing to the whole screen (forgot to set the exclusive zone to -2) * Fixed an issue where the daemon would hang if multiple images were sent in quick succession #### Additions: * `--transition-type none`, which is an alias to `--trasition-type simple` `--transition-step 255` ### 0.8.0 #### BREAKING CHANGE: CACHE HAS CHANGED: I have changed the way we are caching the images / animations. **I would recommend users to delete the previous cache directory once they install this new version**: ```bash rm -r $XDG_CACHE_HOME/swww # OR rm -r $HOME/.cache/swww ``` #### BREAKING CHANGE: NUKED `--sync` flag: We now sync everything automatically, every time. So we've eliminated that flag. #### Update to [`sctk 0.17`](https://github.com/Smithay/client-toolkit): Updating to [`sctk 0.17`](https://github.com/Smithay/client-toolkit) unlocked many, many improvements: * We managed to ditch and extra `clone` when calculating the transitions * We now draw the images directly into the wayland buffer, instead of having to send an intermidiary buffer through a channel. * Because of the above, we can now send from the client a `bgr` image, instead of a `bgra` one. This lets us seriaze and write roughly 3/4 of what we were doing previously. #### Moving from `serde` to `rkyv`: We have changed our serialization strategy from [`serde`](https://github.com/serde-rs/serde) to [`rkyv`](https://github.com/rkyv/rkyv). This lead to even further memory usage reductions, since `rkyv` does not use an intermidate buffer to deserialize its structures. #### Additions: We have reworked the way we do synchronization between monitors. It should sync all monitors animations automatically. * New transition, `fade`, that is essentially `simple` with beziers (@flick0) * `invert_y` flag (@flick0) * New option to resize to fit, padding only what was left (@SignalWalker) * @RiedleroD fixed a type :) #### Summary: With all of the changes above, **I've managed to reduce memory consumption almost by a factor of 3**. The price to pay was a full rewrite of the wayland implementation part of the daemon, a partial rewrite of the way we do ipc, and all the code adaptions necessary to make all that work. Unfortunately, because I had to rewrite so much stuff, it is possible that old bugs will resurface. I've tried my best to test and validate it with every thing that blew up in the past, but it is probably inevitable that some stuff slipped by. Apologies in advance, and keep this in mind when upgrading. ### 0.7.3 Fixes: * Missing `/` when using `$HOME/.cache/swww`, by @max-ishere * `--transition-step` with `simple` has saner defaults * correctly splitting outputs argument with ',', by @potatoattack Improvements: * we only send the image after we've finished processing the whole animation. This diminishes the weird lag that can sometimes happen when sending a large gif. * sending a status update to systemd when daemon has initialized, by @b3nj4m1n Internals: * we have benchmarks for our compression functions! This will be useful for later once I start poking things to see if I can make them more efficient. Unfortunately, there are a few bugs people have reported that I still haven't been able to fix. Sorry about that. In any case, the immediate plan for the future is actually to update sctk to version 0.17.0. I am actually studying the possibility of using sctk with wgpu, since that should bring many improvements (most noticeably, we would be able to store an rgb vector, instead of rgba, so we could potentially cut memory usage by 3/4. In theory, of course, I still have to actually measure it to see if there's any difference. It will probably still take a while). ### 0.7.2 Improvements: * Images (and animations) are now cached at `$XDG_CACHE_HOME` #65 * We now have man-pages! They must be installed manually in your system by moving the files in `doc/generated` to the appropriate location. Typically, you can figure out where that is by running `manpath`. The script `doc/gen.sh` will create the above directory and all the relevant manpages. Note that the script depends on `scdoc` being installed in the system. Have a look at it for more details. * We now also have automated spell checking. This let us fix a number in typos in our documentation, both internal and user-oriented. * New option for `swww-img`: `--sync`. This syncs the animations in all your monitors. Note that *all monitors must be displaying animations* in order for it to work. Internal: * Integration tests are not run by default. You must now use `cargo test -- --ignored` to run them. This will make it possible for some people (like the ones trying to package `swww` at Nix) to run some of the tests in a sandboxed environment where they don't have access to the wayland server. If anyone is interested in running *all* tests, they can do that with `cargo test -- --include-ignored`. ### 0.7.1 Improvements: * you can now use absolute screen coordinates with `--transition-pos` (@flick0) Fixes: * `swww query` not returning the image being displayed * document `--no_resize` and `--fill_color` options for `swww img` * reading img from stdin (now with a proper integration test to make sure it doesn't happen again) (#42) Internal: * fixed `tests/integration_tests.rs` calling the wrong `swww-daemon` binary ### 0.7.0 **BREAKING CHANGES** * **ATTENTION, PACKAGE MAINTAINERS** - `swww` is now composed of two separate binaries: `swww` and `swww-daemon`. **Both** must be installed on the user's system in order for `swww` to work correctly. Doing this allowed for major improvements in terms of overall memory usage, among other things (#52). Improvements: * separate client and daemon (see above). * we don't try to animate `gif` files that have only one frame * we can read images from stdin (not this does not work for animated gifs; we simply display the image's first frame) (#42) * `--no-resize` option (pads the outer part of the image with `fill-color`) (#37) * new transition: `wave`, by @flick0 * reading image format properly (instead of using file extension) (#74) Fixes: * fixed panic with on gif that had identical frames (#68) * fixed panic with fractional-scaling (#73) (by @thedmm) Non-breaking Changes: * @flick0 changed the default `transition-step` Internal: * Many improvements to the README.md (@aouerfelli and @flick0) ### 0.6.0 **BREAKING CHANGES** * `transition-speed` no longer exists. Now, speed is controlled through a bezier curve (`transition-bezier`), and duration (`transition-duration`) flags (note this also applies to the env var, SWWW_TRANSITION_SPEED). A warning was added when we detect the presence of the SWWW_TRANSITION_SPEED environment variable. This warning will go away in the next release, it is only there as a means of making sure everyone knows the variable has been superseded, and having it in your configs no longer does anything. Improvements: * New grow transition. Grow and outer transition now accept a --transition-pos command line argument. By @flick0. * Transitions `grow` and `outer` now both work with bezier curves (see breaking changes, above). This allows for finer control in animation speed than before. Also by @flick0. * Very slightly faster decompression routine ### 0.5.0 **BREAKING CHANGES**: * `swww query` now formats its output as `<output>: ...`, instead of `<output> = ...`. This will break your scripts if you relied on the output's format. Improvements: * Fixed `swww` getting stuck on a futex when a new monitor was connected (#26) * New `wipe` transition by @flick0 * Several small code improvements by @WhyNotHugo * Typo fix (@thebenperson) ### 0.4.3 * Check to see if daemon is REALLY running when we see that socket already exists (#11) * Fix dpi scaling (#22) ### 0.4.2 * Fixed #13. * Improved error message when daemon isn't running (#18) ### 0.4.1 * Fixed regression where the image was stretched on resize (#16) ### 0.4.0 * implemented the new transition effects * refactored socket code * refactored event loop initialization code, handling errors properly now * BREAKING CHANGE: we are using fast_image_resize to resize our images now. This makes resizing much faster (enough to smoothly play animations before caching is done), but it makes it so that the `Gaussian` and `Triangle` filters no longer exist. Furthermore, the filters `Bilinear` and `Mitchell` were added. * deleted previously deprecated `init -i` and `init -c` options ### 0.3.0 * Limited image formats to: `gif`, `jpeg`, `jpeg_rayon`, `png`, `pnm`, `tga`, `tiff`, `webp`, `bmp`, `farbfeld` * Bumped rust edition to 2021 * Our custom compression is now even faster * I did a rewrite of the way the code that handled animations was structured. This made caching a LOT faster, but it incurs in more memory usage, since we spawn an extra thread to make a pipeline. That said, since this also greatly simplified the code itself, I considered it an overall positive change. * Fixed a bug where the animation wouldn't stop until it had processed all the frames, even when it was told to. * Setting a custom names and stack sizes to our threads. The custom name will help in debugging in the future, and the custom stack sizes lets us push the memory usage even lower. * Did all the preparatory work for us to start writing new transition effects. Ideally they should come in the next version, which should hopefully also be our first release (since then I will consider swww to be pretty much feature complete). ### 0.2.0 Using unsafe to speed up decompression. Also, `swww init -i` and `swww init -c` may now be considered deprecated. It was originally created to bypass `swww init && swww img <path/to/img>` not working. Now, however, it seems to be working properly. In hindsight, it was probably already working for a while, but I failed to test it properly and thought it was still a problem. The `swww init -i` and `swww init -c` options shall remain for now, for compatibility and just in case a regression happens. Once I am confident enough, they will be eliminated (that will let me erase around 50 lines of code, I think). ### 0.1.0 Initial release. 0707010018A8CD000081A4000000000000000000000001663426830000C4DE000000080000000100000000000000000000001600000000swww-0.9.5/Cargo.lock# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "aligned-vec" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "anyhow" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" [[package]] name = "arg_enum_proc_macro" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "assert_cmd" version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" dependencies = [ "anstyle", "bstr", "doc-comment", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] [[package]] name = "autocfg" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "av1-grain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" dependencies = [ "anyhow", "arrayvec", "log", "nom", "num-rational", "v_frame", ] [[package]] name = "avif-serialize" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" dependencies = [ "arrayvec", ] [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitcode" version = "0.6.0" source = "git+https://github.com/SoftbearStudios/bitcode.git?rev=5f25a59#5f25a59be3e66deef721e7eb2369deb1aa32d263" dependencies = [ "bitcode_derive", "bytemuck", ] [[package]] name = "bitcode_derive" version = "0.6.0" source = "git+https://github.com/SoftbearStudios/bitcode.git?rev=5f25a59#5f25a59be3e66deef721e7eb2369deb1aa32d263" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitstream-io" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06c9989a51171e2e81038ab168b6ae22886fe9ded214430dbb4f41c28cf176da" [[package]] name = "bstr" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "built" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41bfbdb21256b87a8b5e80fab81a8eed158178e812fd7ba451907518b2742f16" [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "byteorder-lite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" dependencies = [ "jobserver", "libc", "once_cell", ] [[package]] name = "cfg-expr" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", "terminal_size", ] [[package]] name = "clap_complete" version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" dependencies = [ "clap", ] [[package]] name = "clap_derive" version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "crc32fast" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools 0.10.5", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools 0.10.5", ] [[package]] name = "crossbeam-deque" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "dlib" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ "libloading", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "downcast-rs" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "either" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "exr" version = "1.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" dependencies = [ "bit_field", "flume", "half", "lebe", "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", ] [[package]] name = "fast_image_resize" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9d450fac8a334ad72825596173f0f7767ff04dd6e3d59c49c894c4bc2957e8b" dependencies = [ "cfg-if", "num-traits", "thiserror", ] [[package]] name = "fdeflate" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "flume" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "spin", ] [[package]] name = "getrandom" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gif" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ "color_quant", "weezl", ] [[package]] name = "half" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "image" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" dependencies = [ "bytemuck", "byteorder", "color_quant", "exr", "gif", "image-webp", "num-traits", "png", "qoi", "ravif", "rayon", "rgb", "tiff", "zune-core", "zune-jpeg", ] [[package]] name = "image-webp" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" dependencies = [ "byteorder-lite", "thiserror", ] [[package]] name = "imgref" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" [[package]] name = "indexmap" version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "interpolate_name" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "is-terminal" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] [[package]] name = "is_terminal_polyfill" version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "keyframe" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60708bf7981518d09095d6f5673ce5cf6a64f1e0d9708b554f670e6d9d2bd9a9" dependencies = [ "mint", "num-traits", ] [[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libfuzzer-sys" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" dependencies = [ "arbitrary", "cc", "once_cell", ] [[package]] name = "libloading" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", "windows-targets 0.52.5", ] [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "loop9" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" dependencies = [ "imgref", ] [[package]] name = "maybe-rayon" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", "rayon", ] [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", "simd-adler32", ] [[package]] name = "mint" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "noop_proc_macro" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "num-bigint" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-rational" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", ] [[package]] name = "num_threads" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plotters" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] name = "png" version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" dependencies = [ "anstyle", "difflib", "predicates-core", ] [[package]] name = "predicates-core" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] name = "predicates-tree" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" dependencies = [ "predicates-core", "termtree", ] [[package]] name = "proc-macro2" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", "syn", ] [[package]] name = "qoi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ "bytemuck", ] [[package]] name = "quick-error" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rav1e" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" dependencies = [ "arbitrary", "arg_enum_proc_macro", "arrayvec", "av1-grain", "bitstream-io", "built", "cfg-if", "interpolate_name", "itertools 0.12.1", "libc", "libfuzzer-sys", "log", "maybe-rayon", "new_debug_unreachable", "noop_proc_macro", "num-derive", "num-traits", "once_cell", "paste", "profiling", "rand", "rand_chacha", "simd_helpers", "system-deps", "thiserror", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", "rayon", "rgb", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "regex" version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rgb" version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" dependencies = [ "bytemuck", ] [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sd-notify" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "621e3680f3e07db4c9c2c3fb07c6223ab2fab2e54bd3c04c3ae037990f428c32" [[package]] name = "serde" version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simd_helpers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" dependencies = [ "quote", ] [[package]] name = "simplelog" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" dependencies = [ "log", "termcolor", "time", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "spin_sleep" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368a978649eaf70006b082e79c832bd72556ac1393eaf564d686e919dca2347f" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "swww" version = "0.9.5" dependencies = [ "assert_cmd", "clap", "clap_complete", "fast_image_resize", "image", "rand", "utils", ] [[package]] name = "swww-daemon" version = "0.9.5" dependencies = [ "bitcode", "keyframe", "libc", "log", "rand", "rayon", "rustix", "sd-notify", "simplelog", "spin_sleep", "utils", "wayland-client", "wayland-protocols", "wayland-protocols-wlr", ] [[package]] name = "syn" version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ "rustix", "windows-sys 0.48.0", ] [[package]] name = "termtree" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiff" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" dependencies = [ "flate2", "jpeg-decoder", "weezl", ] [[package]] name = "time" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "libc", "num-conv", "num_threads", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "toml" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "utils" version = "0.9.5" dependencies = [ "bitcode", "criterion", "pkg-config", "rand", ] [[package]] name = "v_frame" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" dependencies = [ "aligned-vec", "num-traits", "wasm-bindgen", ] [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wayland-backend" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" dependencies = [ "cc", "downcast-rs", "rustix", "scoped-tls", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" dependencies = [ "bitflags 2.5.0", "log", "rustix", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-scanner", ] [[package]] name = "wayland-protocols-wlr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" dependencies = [ "proc-macro2", "quick-xml", "quote", ] [[package]] name = "wayland-sys" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" dependencies = [ "dlib", "log", "pkg-config", ] [[package]] name = "web-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "weezl" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "winapi-util" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.5", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm 0.52.5", "windows_aarch64_msvc 0.52.5", "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", "windows_i686_msvc 0.52.5", "windows_x86_64_gnu 0.52.5", "windows_x86_64_gnullvm 0.52.5", "windows_x86_64_msvc 0.52.5", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] name = "windows_i686_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] [[package]] name = "zune-core" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-inflate" version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] [[package]] name = "zune-jpeg" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" dependencies = [ "zune-core", ] 0707010018B69A000081A4000000000000000000000001663426830000037F000000080000000100000000000000000000001600000000swww-0.9.5/Cargo.toml[workspace] members = ["daemon"] default-members = [".", "daemon"] [package] name = "swww" version = "0.9.5" authors = ["Leonardo Gibrowski Faé <leonardo.fae44@gmail.com>"] edition = "2021" rust-version = "1.74" # Enable some optimizations in debug mode. Otherwise, it is a pain to test it [profile.dev] opt-level = 1 # Enable high optimizations for dependencies, but not for our code: [profile.dev.package."*"] opt-level = 3 [profile.release] debug = 0 lto = true opt-level = 3 codegen-units = 1 strip = true [profile.bench] lto = "thin" debug = 1 strip = false [dependencies] image = "0.25" fast_image_resize = "3.0" clap = { version = "4.5", features = ["derive", "wrap_help", "env"] } rand = "0.8" utils = { version = "0.9.5", path = "utils" } [dev-dependencies] assert_cmd = "2.0" [build-dependencies] clap = { version = "4.5", features = ["derive", "env"] } clap_complete = "4.5" 07070100187F4C000081A4000000000000000000000001663426830000894D000000080000000100000000000000000000001300000000swww-0.9.5/LICENSE GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: <program> Copyright (C) <year> <name of author> This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/licenses/why-not-lgpl.html>. 0707010018BB81000081A40000000000000000000000016634268300001FF2000000080000000100000000000000000000001500000000swww-0.9.5/README.md# A Solution to your Wayland Wallpaper Woes ### Efficient animated wallpaper daemon for wayland, controlled at runtime ![animated gif demonstration](https://i.imgur.com/Leuh6wm.gif) ![image transition demonstration](../demos/assets/grow.gif) ## Dependencies - a compositor that implements: * wlr-layer-shell (typically wlroots based compositors) * xdg-output - [lz4](https://github.com/lz4/lz4) (for compressing frames when animating) **Note that this means `swww` will not run on Gnome, because it does not implement the `wlr-layer-shell` protocol**. ## Build <a href="https://repology.org/project/swww/versions"> <img src="https://repology.org/badge/vertical-allrepos/swww.svg" alt="Packaging status" align="right"> </a> ### Dependencies: - Up to date stable rustc compiler and cargo (specifically, MSRV is 1.74.0) To build, clone this repository and run: ``` cargo build --release ``` Then, put **both binaries** `target/release/swww` and `target/release/swww-daemon` in your path. Optionally, autocompletion scripts for bash, zsh, fish and elvish are offered in the `completions` directory. #### Man pages: In order to generate the man pages, **you must have `scdoc` installed**. Run ``` ./doc/gen.sh ``` The man pages will be in `doc/generated`. To install them, you must move them to to the appropriate location in your system. You should be able to figure out where that is by running `manpath`. ## Features - Display animated gifs on your desktop - Display any image in the formats: * jpeg * png * gif * pnm * tga * tiff * webp * bmp * farbfeld - Clear the screen with an arbitrary rrggbb color - Smooth transition effect when you switch images - Do all of that without having to shutdown and reinitialize the daemon ## Why There are two main reasons that compelled me to make this: the first is that [`oguri`](https://github.com/vilhalmer/oguri) is unmaintained and archived, despite there being serious problems with excess of memory use while displaying certain gifs (see [this](https://github.com/vilhalmer/oguri/issues/38), for example). The best alternative I've found for `oguri` was [`mpvpaper`](https://github.com/GhostNaN/mpvpaper), but if felt overkill for my purposes. Comparing to `oguri`, `swww` uses less cpu power to animate once it has cached all the frames in the animation. It should also be **significantly** more memory efficient. The second is that, to my knowledge, there is no wallpaper daemon for wayland that allows you to change the wallpaper at runtime. That is, in order to, for example, cycle through the images of a directory, you'd have to kill the daemon and restart it. Not only does it make simple shell scripts a pain to write, it makes switching from one image to the next to happen very abruptly. ## Usage Start by initializing the daemon: ``` swww-daemon ``` Then, in a different terminal, simply pass the image you want to display: ``` swww img <path/to/img> # You can also specify outputs: swww img -o <outputs> <path/to/img> # Control how smoothly the transition will happen and/or it's frame rate # For the step, smaller values = more smooth. Default = 20 # For the frame rate, default is 30. swww img <path/to/img> --transition-step <1 to 255> --transition-fps <1 to 255> # There are also many different transition effects: swww img <path/to/img> --transition-type center # Note you may also control the above by setting up the SWWW_TRANSITION_FPS, # SWWW_TRANSITION_STEP, and SWWW_TRANSITION environment variables. # To see all options, run swww img --help ``` If you would like to know the valid values for *\<outputs\>*, you can query the daemon. This will also tell you what the current image being displayed is, as well as the dimensions detected for the outputs. If you need more detailed information, I would recommend using [`wlr-randr`](https://sr.ht/~emersion/wlr-randr/). ``` swww query ``` Finally, to stop the daemon, kill it: ``` swww kill ``` For a more complete description, run `swww --help` or `swww <subcommand> --help`. Finally, to get a feel for what you can do with some shell scripting, check out the [example_scripts](/example_scripts/) folder. It can help you get started. ## Transitions #### Example wipe transition: > wipe transition with angle set to 30 deg ![top transition demonstration](../demos/assets/wipe.gif) The `left`, `right`, `top` and `bottom` transitions all work similarly. #### Example outer transition ![outer transition demonstration](../demos/assets/outer.gif) The `center` transition is the opposite: it starts from the center and goes towards the edges. There is also `simple`, which simply fades into the new image, `any`, which starts at a random point with either `center` of `outer` transitions, and `random`, which selects a transition effect at random. ## Troubleshooting ### The image looks tilted and in grayscale on my laptop See #233. Current workaround is to use `swww-daemon --format xrgb` when starting the daemon. ### High cpu usage during caching of a gif's frames `swww` will use a non-insignificant amount of cpu power while caching the images. This will be specially noticeable if the images need to be resized before being displayed. So, if you have a very large gif, I would recommend resizing it **before** sending it to `swww`. That would make the caching phase much faster, and thus ultimately reduce power consumption. I can personally recommend [`gifsicle`](https://github.com/kohler/gifsicle) for this purpose. ### Wallpaper disappears when reconnecting monitor `swww` used to cache its images so that it could reload the current the last displayed image automatically. This lead to many problems and also proved to be very annoying to keep working with when we updated to [`sctk 0.17`](https://github.com/Smithay/client-toolkit). So I decided to nuke it. If you want a wallpaper to be set automatically when you reconnect to a monitor, you should use a combination of scripts and a program that lets you run commands when a new output is connected, like [`kanshi`](https://sr.ht/~emersion/kanshi/). ## About new features Broadly speaking, **NEW FEATURES WILL NOT BE ADDED, UNLESS THEY ARE EGREGIOUSLY SIMPLE**. I made `swww` with the specific usecase of making shell scripts in mind. So, for example, stuff like timed wallpapers, or a setup that loads a different image at different times of the day, and so on, should all be done by combining `swww` with other programs (see the [example_scripts](/example_scripts/) for some examples). If you really want some new feature within `swww` itself, I would recommend forking the repository. ## Alternatives `swww` isn't really the simplest, mostest minimalest software you could find for managing wallpapers. If you are looking for something simpler, have a look at the [awesome-wayland repository list of wallpaper programs ](https://github.com/natpen/awesome-wayland#wallpaper). I can personally recommend: - [`wbg`](https://codeberg.org/dnkl/wbg) - probably the simplest of them all. Strongly recommend if you just care about setting a single png as your permanent wallpaper on something like a laptop. - [`swaybg`](https://github.com/swaywm/swaybg) - made by the wlroots gods themselves. - [`mpvpaper`](https://github.com/GhostNaN/mpvpaper) - if you want to display videos as your wallpapers. This is also what I used for gifs before making `swww`. ## Acknowledgments A huge thanks to everyone involved in the [smithay](https://github.com/Smithay) project. Making this program would not have been possible without it. In fact, the first versions of swww were quite literally copy pasted from the [layer shell example in the client-toolkit ](https://github.com/Smithay/client-toolkit/blob/master/examples/layer_shell.rs). A big thank-you also to [HakierGrzonzo](https://github.com/HakierGrzonzo), for setting up the AUR package. ### Wallpapers used in this README Pixel Art, by Waneella - https://www.patreon.com/waneella Gradient - https://www.behance.net/gallery/86128681/Free-Unicorn-Vector-Gradients Silhouette of Skyway - https://unsplash.com/photos/silhouette-of-skyway-UUJzCuHUfYI 07070100187F6E000081A400000000000000000000000166342683000000CB000000080000000100000000000000000000001000000000swww-0.9.5/TODOFully automated, complete testing, for every option This is annoying because it would involve changing the output scaling and testing many things with different setups. Maybe we can script it somehow? 070701001881E8000081A400000000000000000000000166342683000002EB000000080000000100000000000000000000001400000000swww-0.9.5/build.rsuse std::io::Error; use clap::CommandFactory; use clap_complete::{generate_to, Shell}; include!("src/cli.rs"); const COMPLETION_DIR: &str = "completions"; const APP_NAME: &str = "swww"; fn main() -> Result<(), Error> { let outdir = completion_dir()?; let mut app = Swww::command(); let shells = [Shell::Bash, Shell::Zsh, Shell::Fish, Shell::Elvish]; for shell in shells { let comp_file = generate_to(shell, &mut app, APP_NAME, &outdir)?; println!("cargo:warning=generated shell completion file: {comp_file:?}"); } Ok(()) } fn completion_dir() -> std::io::Result<PathBuf> { let path = PathBuf::from(COMPLETION_DIR); if !path.is_dir() { std::fs::create_dir(&path)?; } Ok(path) } 070701300141DF000041ED0000000000000000000000036634268300000000000000080000000100000000000000000000001200000000swww-0.9.5/daemon070701300141F7000081A40000000000000000000000016634268300000461000000080000000100000000000000000000001D00000000swww-0.9.5/daemon/Cargo.toml[package] name = "swww-daemon" version = "0.9.5" authors = ["Leonardo Gibrowski Faé <leonardo.fae44@gmail.com>"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] log = { version = "0.4", features = ["max_level_debug", "release_max_level_info"] } simplelog = "0.12" wayland-client = { version = "0.31", default-features = false, features = [ "log" ]} wayland-protocols = { version = "0.31", default-features = false, features = [ "client", "staging" ]} wayland-protocols-wlr = { version = "0.2", default-features = false, features = [ "client" ]} # use specific git version for Duration implementation. We will do this until the next bitcode release bitcode = { git = "https://github.com/SoftbearStudios/bitcode.git", rev = "5f25a59", default-features = false } rustix = { version = "0.38", default-features = false, features = [ "event", "shm", "mm" ] } libc = "0.2" keyframe = "1.1" rayon = "1.10" spin_sleep = "1.2" sd-notify = { version = "0.4.1" } utils = { version = "0.9.5", path = "../utils" } [dev-dependencies] rand = "0.8" 07070141FCBDA6000041ED0000000000000000000000036634268300000000000000080000000100000000000000000000001600000000swww-0.9.5/daemon/src07070100188F5D000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000002100000000swww-0.9.5/daemon/src/animations07070100188F6A000081A40000000000000000000000016634268300000A69000000080000000100000000000000000000003100000000swww-0.9.5/daemon/src/animations/anim_barrier.rsuse std::{ marker::PhantomData, ptr::NonNull, sync::{ atomic::{self, AtomicUsize, Ordering}, Condvar, Mutex, }, time::Duration, }; ///This is a barrier that lets us dynamically set the amount of threads that have to wait. We use ///this in order to sync the animations, because outputs may be created or deleted during runtime /// ///It automatically keeps track of how many threads own it pub struct ArcAnimBarrier { ptr: NonNull<AnimBarrier>, phantom: PhantomData<AnimBarrier>, } impl ArcAnimBarrier { pub fn new() -> Self { let boxed = Box::new(AnimBarrier { rc: AtomicUsize::new(1), count: Mutex::new(0), cvar: Condvar::new(), }); Self { ptr: NonNull::new(Box::into_raw(boxed)).unwrap(), phantom: PhantomData, } } pub fn wait(&self, timeout: Duration) { self.get().wait(timeout); } #[inline] fn get(&self) -> &AnimBarrier { unsafe { self.ptr.as_ref() } } } impl Clone for ArcAnimBarrier { fn clone(&self) -> Self { let inner = self.get(); let _ = inner.rc.fetch_add(1, Ordering::Relaxed); Self { ptr: self.ptr, phantom: PhantomData, } } } impl Drop for ArcAnimBarrier { fn drop(&mut self) { let inner = self.get(); if inner.rc.fetch_sub(1, Ordering::Relaxed) != 1 { // When someone leaves, increase the counter by 1, since otherwise the other threads // might wait forever let mut count = inner.count.lock().unwrap(); *count += 1; if inner.rc.load(Ordering::SeqCst) - 1 <= *count { *count = 0; inner.cvar.notify_all(); } return; } // This fence is needed to prevent reordering of the use and deletion // of the data. atomic::fence(Ordering::Acquire); let _ = unsafe { Box::from_raw(self.ptr.as_ptr()) }; } } unsafe impl Send for ArcAnimBarrier {} unsafe impl Sync for ArcAnimBarrier {} struct AnimBarrier { // should ALWAYS be equals to threads rc: AtomicUsize, count: Mutex<usize>, cvar: Condvar, } impl AnimBarrier { fn wait(&self, timeout: Duration) { let mut count = self.count.lock().unwrap(); *count += 1; if self.rc.load(Ordering::SeqCst) - 1 > *count { let _ = self .cvar .wait_timeout_while(count, timeout, |count| *count != 0); } else { *count = 0; self.cvar.notify_all(); } } } 07070100189596000081A400000000000000000000000166342683000019D2000000080000000100000000000000000000002800000000swww-0.9.5/daemon/src/animations/mod.rsuse log::error; use std::{ sync::Arc, thread::{self, Scope}, time::Duration, }; use utils::{ compression::Decompressor, ipc::{self, Animation, Answer, BgImg, Img}, }; use crate::wallpaper::{AnimationToken, Wallpaper}; mod anim_barrier; mod transitions; use transitions::Transition; use self::anim_barrier::ArcAnimBarrier; ///The default thread stack size of 2MiB is way too overkill for our purposes const STACK_SIZE: usize = 1 << 17; //128KiB pub(super) struct Animator { anim_barrier: ArcAnimBarrier, } impl Animator { pub(super) fn new() -> Self { Self { anim_barrier: ArcAnimBarrier::new(), } } fn spawn_transition_thread<'a, 'b>( scope: &'a Scope<'b, '_>, transition: &'b ipc::Transition, img: &'b [u8], path: &'b String, mut wallpapers: Vec<Arc<Wallpaper>>, ) where 'a: 'b, { if let Err(e) = thread::Builder::new() .name("transition".to_string()) //Name our threads for better log messages .stack_size(STACK_SIZE) //the default of 2MB is way too overkill for this .spawn_scoped(scope, move || { if wallpapers.is_empty() { return; } for w in wallpapers.iter_mut() { w.set_img_info(BgImg::Img(path.to_string())); } let dimensions = wallpapers[0].get_dimensions(); let expected_len = dimensions.0 as usize * dimensions.1 as usize * crate::pixel_format().channels() as usize; if img.len() == expected_len { Transition::new(wallpapers, dimensions, transition).execute(img); } else { error!( "image is of wrong size! Image len: {}, expected len: {expected_len}", img.len(), ); } }) { error!("failed to spawn 'transition' thread: {}", e); } } pub(super) fn transition( &mut self, transition: ipc::Transition, imgs: Box<[Img]>, wallpapers: Vec<Vec<Arc<Wallpaper>>>, ) -> Answer { match thread::Builder::new() .stack_size(1 << 15) .name("transition spawner".to_string()) .spawn(move || { thread::scope(|s| { for (Img { img, path }, wallpapers) in imgs.iter().zip(wallpapers) { Self::spawn_transition_thread(s, &transition, img, path, wallpapers); } }); }) { Ok(_) => Answer::Ok, Err(e) => Answer::Err(e.to_string()), } } fn spawn_animation_thread<'a, 'b>( scope: &'a Scope<'b, '_>, animation: &'b Animation, mut wallpapers: Vec<Arc<Wallpaper>>, barrier: ArcAnimBarrier, ) where 'a: 'b, { if let Err(e) = thread::Builder::new() .name("animation".to_string()) //Name our threads for better log messages .stack_size(STACK_SIZE) //the default of 2MB is way too overkill for this .spawn_scoped(scope, move || { /* We only need to animate if we have > 1 frame */ if animation.animation.len() <= 1 { return; } log::debug!("Starting animation"); let mut tokens: Vec<AnimationToken> = wallpapers .iter() .map(|w| w.create_animation_token()) .collect(); for (wallpaper, token) in wallpapers.iter().zip(&tokens) { loop { if !wallpaper.has_animation_id(token) || token.is_transition_done() { break; } let duration: Duration = animation.animation[0].1; std::thread::sleep(duration / 2); } } let mut now = std::time::Instant::now(); let mut decompressor = Decompressor::new(); for (frame, duration) in animation.animation.iter().cycle() { barrier.wait(duration.div_f32(2.0)); let mut i = 0; while i < wallpapers.len() { let token = &tokens[i]; if !wallpapers[i].has_animation_id(token) { wallpapers.swap_remove(i); tokens.swap_remove(i); continue; } let result = wallpapers[i].canvas_change(|canvas| { decompressor.decompress(frame, canvas, crate::pixel_format()) }); if let Err(e) = result { error!("failed to unpack frame: {e}"); wallpapers.swap_remove(i); tokens.swap_remove(i); continue; } i += 1; } if wallpapers.is_empty() { return; } for wallpaper in &wallpapers { wallpaper.draw(); } let timeout = duration.saturating_sub(now.elapsed()); spin_sleep::sleep(timeout); crate::flush_wayland(); now = std::time::Instant::now(); } }) { error!("failed to spawn 'animation' thread: {}", e); } } pub(super) fn animate( &mut self, animations: Box<[Animation]>, wallpapers: Vec<Vec<Arc<Wallpaper>>>, ) -> Answer { let barrier = self.anim_barrier.clone(); match thread::Builder::new() .stack_size(1 << 15) .name("animation spawner".to_string()) .spawn(move || { thread::scope(|s| { for (animation, wallpapers) in animations.iter().zip(wallpapers) { let barrier = barrier.clone(); Self::spawn_animation_thread(s, animation, wallpapers, barrier); } }); }) { Ok(_) => Answer::Ok, Err(e) => Answer::Err(e.to_string()), } } } 07070100189700000081A4000000000000000000000001663426830000389D000000080000000100000000000000000000003000000000swww-0.9.5/daemon/src/animations/transitions.rsuse std::{ sync::Arc, time::{Duration, Instant}, }; use rayon::prelude::*; use log::debug; use utils::ipc::{Position, TransitionType}; use crate::wallpaper::{AnimationToken, Wallpaper}; use keyframe::{ functions::BezierCurve, keyframes, mint::Vector2, num_traits::Pow, AnimationSequence, }; pub(super) struct Transition { animation_tokens: Vec<AnimationToken>, wallpapers: Vec<Arc<Wallpaper>>, dimensions: (u32, u32), transition_type: TransitionType, duration: f32, step: u8, fps: Duration, angle: f64, pos: Position, bezier: BezierCurve, wave: (f32, f32), invert_y: bool, } /// All transitions return whether or not they completed impl Transition { pub(super) fn new( wallpapers: Vec<Arc<Wallpaper>>, dimensions: (u32, u32), transition: &utils::ipc::Transition, ) -> Self { Transition { animation_tokens: wallpapers .iter() .map(|w| w.create_animation_token()) .collect(), wallpapers, dimensions, transition_type: transition.transition_type, duration: transition.duration, step: transition.step.get(), fps: Duration::from_nanos(1_000_000_000 / transition.fps as u64), angle: transition.angle, pos: transition.pos.clone(), bezier: BezierCurve::from( Vector2 { x: transition.bezier.0, y: transition.bezier.1, }, Vector2 { x: transition.bezier.2, y: transition.bezier.3, }, ), wave: transition.wave, invert_y: transition.invert_y, } } pub(super) fn execute(mut self, new_img: &[u8]) { debug!("Starting transitions"); match self.transition_type { TransitionType::None => self.none(new_img), TransitionType::Simple => self.simple(new_img), TransitionType::Wipe => self.wipe(new_img), TransitionType::Grow => self.grow(new_img), TransitionType::Outer => self.outer(new_img), TransitionType::Wave => self.wave(new_img), TransitionType::Fade => self.fade(new_img), }; debug!("Transitions finished"); for (wallpaper, token) in self.wallpapers.iter().zip(self.animation_tokens) { token.set_transition_done(wallpaper); } } fn updt_wallpapers(&mut self, now: &mut Instant) { let mut i = 0; while i < self.wallpapers.len() { let token = &self.animation_tokens[i]; if !self.wallpapers[i].has_animation_id(token) { self.wallpapers.swap_remove(i); self.animation_tokens.swap_remove(i); continue; } i += 1; } self.wallpapers.iter().for_each(|w| w.draw()); let timeout = self.fps.saturating_sub(now.elapsed()); spin_sleep::sleep(timeout); crate::flush_wayland(); *now = Instant::now(); } fn bezier_seq(&self, start: f32, end: f32) -> (AnimationSequence<f32>, Instant) { ( keyframes![(start, 0.0, self.bezier), (end, self.duration, self.bezier)], Instant::now(), ) } fn none(&mut self, new: &[u8]) { for wallpaper in self.wallpapers.iter() { wallpaper.canvas_change(|canvas| canvas.copy_from_slice(new)); } for wallpaper in self.wallpapers.iter() { wallpaper.draw(); } crate::flush_wayland(); } fn simple(&mut self, new_img: &[u8]) { let step = self.step; let mut now = Instant::now(); let mut done = false; while !done { done = true; for wallpaper in self.wallpapers.iter() { wallpaper.canvas_change(|canvas| { for (old, new) in canvas.iter_mut().zip(new_img) { if old.abs_diff(*new) < step { *old = *new; } else if *old > *new { *old -= step; done = false; } else { *old += step; done = false; } } }); } self.updt_wallpapers(&mut now); } } fn fade(&mut self, new_img: &[u8]) { let mut step = 0.0; let (mut seq, start) = self.bezier_seq(0.0, 1.0); let channels = crate::pixel_format().channels() as usize; let mut now = Instant::now(); while start.elapsed().as_secs_f64() < seq.duration() { self.parallel_draw_all(channels, new_img, |_, old, new| { for i in 0..channels { let old = unsafe { old.get_unchecked_mut(i) }; let new = unsafe { new.get_unchecked(i) }; *old = (*old as f64 * (1.0 - step) + *new as f64 * step) as u8; } }); self.updt_wallpapers(&mut now); step = seq.now() as f64; seq.advance_to(start.elapsed().as_secs_f64()); } self.step = 4 + self.step / 4; self.simple(new_img) } fn wave(&mut self, new_img: &[u8]) { let width = self.dimensions.0; let height = self.dimensions.1; let mut now = Instant::now(); let center = (width / 2, height / 2); let screen_diag = ((width.pow(2) + height.pow(2)) as f64).sqrt(); let angle = self.angle.to_radians(); let (scale_x, scale_y) = (self.wave.0 as f64, self.wave.1 as f64); let circle_radius = screen_diag / 2.0; let f = |x: f64| (x / scale_x).sin() * scale_y; // graph: https://www.desmos.com/calculator/wunde042es // // checks if a pixel is to the left or right of the line let is_low = |x: f64, y: f64, offset: f64| { let x = x - center.0 as f64; let y = y - center.1 as f64; let lhs = y * angle.cos() - x * angle.sin(); let rhs = f(x * angle.cos() + y * angle.sin()) + circle_radius - offset; lhs >= rhs }; // find the offset to start the transition at let mut offset = { let mut offset = 0.0; for x in 0..width { for y in 0..height { if is_low(x as f64, y as f64, offset) { offset += 1.0; break; } } } offset }; let max_offset = 2.0 * circle_radius - offset; let (width, height) = (width as usize, height as usize); let (mut seq, start) = self.bezier_seq(offset as f32, max_offset as f32); let step = self.step; let channels = crate::pixel_format().channels() as usize; while start.elapsed().as_secs_f64() < seq.duration() { self.parallel_draw_all(channels, new_img, |i, old, new| { let pix_x = i % width; let pix_y = height - i / width; if is_low(pix_x as f64, pix_y as f64, offset) { change_byte(channels, step, old, new); } }); self.updt_wallpapers(&mut now); offset = seq.now() as f64; seq.advance_to(start.elapsed().as_secs_f64()); } self.step = 4 + self.step / 4; self.simple(new_img) } fn wipe(&mut self, new_img: &[u8]) { let width = self.dimensions.0; let height = self.dimensions.1; let mut now = Instant::now(); let center = (width / 2, height / 2); let screen_diag = ((width.pow(2) + height.pow(2)) as f64).sqrt(); let circle_radius = screen_diag / 2.0; let max_offset = circle_radius.pow(2) * 2.0; let angle = self.angle.to_radians(); let mut offset = { let (x, y) = angle.sin_cos(); (x.abs() * width as f64 / 2.0 + y.abs() * height as f64 / 2.0).abs() }; // line formula: (x-h)*a + (y-k)*b + C = r^2 // https://www.desmos.com/calculator/vpvzk12yar // // checks if a pixel is to the left or right of the line let is_low = |pix_x: f64, pix_y: f64, offset: f64, radius: f64| { let a = radius * angle.cos(); let b = radius * angle.sin(); let x = pix_x - center.0 as f64; let y = pix_y - center.1 as f64; let res = x * a + y * b + offset; res >= radius.pow(2) }; let (width, height) = (width as usize, height as usize); let (mut seq, start) = self.bezier_seq(0.0, max_offset as f32); let step = self.step; let channels = crate::pixel_format().channels() as usize; while start.elapsed().as_secs_f64() < seq.duration() { self.parallel_draw_all(channels, new_img, |i, old, new| { let pix_x = i % width; let pix_y = height - i / width; if is_low(pix_x as f64, pix_y as f64, offset, circle_radius) { change_byte(channels, step, old, new); } }); self.updt_wallpapers(&mut now); offset = seq.now() as f64; seq.advance_to(start.elapsed().as_secs_f64()); } self.step = 4 + self.step / 4; self.simple(new_img) } fn grow(&mut self, new_img: &[u8]) { let (width, height) = (self.dimensions.0 as f32, self.dimensions.1 as f32); let (center_x, center_y) = self.pos.to_pixel(self.dimensions, self.invert_y); let mut dist_center: f32 = 0.0; let dist_end: f32 = { let mut x = center_x; let mut y = center_y; if x < width / 2.0 { x = width - 1.0 - x; } if y < height / 2.0 { y = height - 1.0 - y; } f32::sqrt(x.pow(2) + y.pow(2)) }; let (width, height) = (width as usize, height as usize); let (center_x, center_y) = (center_x as usize, center_y as usize); let step = self.step; let channels = crate::pixel_format().channels() as usize; let (mut seq, start) = self.bezier_seq(0.0, dist_end); let mut now = Instant::now(); while start.elapsed().as_secs_f64() < seq.duration() { self.parallel_draw_all(channels, new_img, |i, old, new| { let pix_x = i % width; let pix_y = height - i / width; let diff_x = pix_x.abs_diff(center_x); let diff_y = pix_y.abs_diff(center_y); let pix_center_dist = f32::sqrt((diff_x.pow(2) + diff_y.pow(2)) as f32); if pix_center_dist <= dist_center { let step = step.saturating_add((dist_center - pix_center_dist).log2() as u8); change_byte(channels, step, old, new); } }); self.updt_wallpapers(&mut now); dist_center = seq.now(); seq.advance_to(start.elapsed().as_secs_f64()); } self.step = 4 + self.step / 4; self.simple(new_img) } fn outer(&mut self, new_img: &[u8]) { let (width, height) = (self.dimensions.0 as f32, self.dimensions.1 as f32); let (center_x, center_y) = self.pos.to_pixel(self.dimensions, self.invert_y); let mut dist_center = { let mut x = center_x; let mut y = center_y; if x < width / 2.0 { x = width - 1.0 - x; } if y < height / 2.0 { y = height - 1.0 - y; } f32::sqrt(x.pow(2) + y.pow(2)) }; let (width, height) = (width as usize, height as usize); let (center_x, center_y) = (center_x as usize, center_y as usize); let step = self.step; let channels = crate::pixel_format().channels() as usize; let (mut seq, start) = self.bezier_seq(dist_center, 0.0); let mut now = Instant::now(); while start.elapsed().as_secs_f64() < seq.duration() { self.parallel_draw_all(channels, new_img, |i, old, new| { let pix_x = i % width; let pix_y = height - i / width; let diff_x = pix_x.abs_diff(center_x); let diff_y = pix_y.abs_diff(center_y); let pix_center_dist = f32::sqrt((diff_x.pow(2) + diff_y.pow(2)) as f32); if pix_center_dist >= dist_center { let step = step.saturating_add((pix_center_dist - dist_center).log2() as u8); change_byte(channels, step, old, new); } }); self.updt_wallpapers(&mut now); dist_center = seq.now(); seq.advance_to(start.elapsed().as_secs_f64()); } self.step = 4 + self.step / 4; self.simple(new_img) } /// Runs pixels_change_fn for every byte in the old img, in parallel #[inline(always)] fn parallel_draw_all<F>(&self, channels: usize, new_img: &[u8], f: F) where F: FnOnce(usize, &mut [u8], &[u8]) + Copy + Sync, { self.wallpapers.iter().for_each(|wallpaper| { wallpaper.canvas_change(|canvas| { canvas .par_chunks_exact_mut(channels) .zip_eq(new_img.par_chunks_exact(channels)) .enumerate() .for_each(|(i, (old, new))| f(i, old, new)); }); }); } } #[inline(always)] fn change_byte(channels: usize, step: u8, old: &mut [u8], new: &[u8]) { // this check improves the assembly generation slightly, by making the compiler not assume // channels can be arbitrarily large if channels != 3 && channels != 4 { log::error!("weird channel size of: {channels}"); return; } for i in 0..channels { let old = unsafe { old.get_unchecked_mut(i) }; let new = unsafe { new.get_unchecked(i) }; if old.abs_diff(*new) < step { *old = *new; } else if *old > *new { *old -= step; } else { *old += step; } } } 07070141FCBDA7000081A4000000000000000000000001663426830000297B000000080000000100000000000000000000002300000000swww-0.9.5/daemon/src/bump_pool.rsuse std::{ io, os::unix::prelude::{AsFd, OwnedFd}, sync::{ atomic::{AtomicBool, Ordering}, Arc, }, time::{SystemTime, UNIX_EPOCH}, }; use rustix::{ io::Errno, mm::{mmap, munmap, MapFlags, ProtFlags}, shm::{Mode, ShmOFlags}, }; use wayland_client::{ backend::ObjectData, protocol::{ wl_buffer::WlBuffer, wl_shm::{self, WlShm}, wl_shm_pool::{self, WlShmPool}, }, Proxy, WEnum, }; #[derive(Debug)] struct ReleaseFlag(AtomicBool); impl ReleaseFlag { fn is_released(&self) -> bool { self.0.load(Ordering::Acquire) } fn set_released(&self) { self.0.store(true, Ordering::Release) } fn unset_released(&self) { self.0.store(false, Ordering::Release) } } impl ObjectData for ReleaseFlag { fn event( self: Arc<Self>, _: &wayland_client::backend::Backend, msg: wayland_client::backend::protocol::Message<wayland_client::backend::ObjectId, OwnedFd>, ) -> Option<Arc<(dyn ObjectData + 'static)>> { if msg.opcode == wayland_client::protocol::wl_buffer::Event::Release.opcode() { self.set_released(); } None } fn destroyed(&self, _: wayland_client::backend::ObjectId) {} } #[derive(Debug)] struct Buffer { inner: WlBuffer, released: Arc<ReleaseFlag>, } impl Buffer { fn new( pool: &WlShmPool, offset: i32, width: i32, height: i32, stride: i32, format: wl_shm::Format, ) -> Self { let released = Arc::new(ReleaseFlag(AtomicBool::new(true))); let inner = pool .send_constructor( wl_shm_pool::Request::CreateBuffer { offset, width, height, stride, format: WEnum::Value(format), }, released.clone(), ) .expect("WlShmPool failed to create buffer"); Self { inner, released } } } impl Drop for Buffer { fn drop(&mut self) { self.inner.destroy(); } } #[derive(Debug)] struct Mmap { fd: OwnedFd, ptr: *mut std::ffi::c_void, len: usize, } impl Mmap { const PROT: ProtFlags = ProtFlags::WRITE.union(ProtFlags::READ); const FLAGS: MapFlags = MapFlags::SHARED; fn new(len: usize) -> Self { let fd = create_shm_fd().unwrap(); loop { match rustix::fs::ftruncate(&fd, len as u64) { Err(Errno::INTR) => continue, otherwise => break otherwise.unwrap(), } } let ptr = unsafe { mmap(std::ptr::null_mut(), len, Self::PROT, Self::FLAGS, &fd, 0).unwrap() }; Self { fd, ptr, len } } fn remap(&mut self, new_len: usize) { if let Err(e) = unsafe { munmap(self.ptr, self.len) } { log::error!("ERROR WHEN UNMAPPING MEMORY: {e}"); } self.len = new_len; loop { match rustix::fs::ftruncate(&self.fd, self.len as u64) { Err(Errno::INTR) => continue, otherwise => break otherwise.unwrap(), } } self.ptr = unsafe { mmap( std::ptr::null_mut(), self.len, Self::PROT, Self::FLAGS, &self.fd, 0, ) .unwrap() }; } fn as_mut(&mut self) -> &mut [u8] { unsafe { std::slice::from_raw_parts_mut(self.ptr.cast(), self.len) } } } impl Drop for Mmap { fn drop(&mut self) { if let Err(e) = unsafe { munmap(self.ptr, self.len) } { log::error!("ERROR WHEN UNMAPPING MEMORY: {e}"); } } } #[derive(Debug)] /// A pool implementation that only gives buffers of a fixed size, creating new ones if none of /// them are freed. It also takes care of copying the previous buffer's content over to the new one /// for us pub(crate) struct BumpPool { pool: WlShmPool, mmap: Mmap, buffers: Vec<Buffer>, width: i32, height: i32, last_used_buffer: Option<usize>, } impl BumpPool { /// We assume `width` and `height` have already been multiplied by their scale factor pub(crate) fn new(width: i32, height: i32, shm: &WlShm) -> Self { let len = width as usize * height as usize * crate::pixel_format().channels() as usize; let (pool, mmap) = new_pool(len, shm); let buffers = vec![]; Self { pool, mmap, buffers, width, height, last_used_buffer: None, } } #[inline] fn buffer_len(&self) -> usize { self.width as usize * self.height as usize * crate::pixel_format().channels() as usize } #[inline] fn buffer_offset(&self, buffer_index: usize) -> usize { self.buffer_len() * buffer_index } #[inline] fn occupied_bytes(&self) -> usize { self.buffer_offset(self.buffers.len()) } /// resizes the pool and creates a new WlBuffer at the next free offset fn grow(&mut self) { //TODO: CHECK IF WE HAVE SIZE let len = self.buffer_len(); let new_len = self.occupied_bytes() + len; if new_len > self.mmap.len { self.mmap.remap(new_len); self.pool.resize(new_len as i32); } let new_buffer_index = self.buffers.len(); self.buffers.push(Buffer::new( &self.pool, self.buffer_offset(new_buffer_index).try_into().unwrap(), self.width, self.height, self.width * crate::pixel_format().channels() as i32, crate::wl_shm_format(), )); log::info!( "BumpPool with: {} buffers. Size: {}Kb", self.buffers.len(), self.mmap.len / 1024 ); } /// Returns a drawable surface. If we can't find a free buffer, we request more memory /// /// This function automatically handles copying the previous buffer over onto the new one pub(crate) fn get_drawable(&mut self) -> &mut [u8] { let (i, buf) = match self .buffers .iter() .enumerate() .find(|(_, b)| b.released.is_released()) { Some((i, buf)) => (i, buf), None => { self.grow(); (self.buffers.len() - 1, self.buffers.last().unwrap()) } }; let len = self.buffer_len(); let offset = self.buffer_offset(i); buf.released.unset_released(); if let Some(i) = self.last_used_buffer { let last_offset = self.buffer_offset(i); self.mmap .as_mut() .copy_within(last_offset..last_offset + len, offset); } self.last_used_buffer = Some(i); &mut self.mmap.as_mut()[offset..offset + len] } /// gets the last buffer we've drawn to /// /// This may return None if there was a resize request in-between the last call to get_drawable #[inline] pub(crate) fn get_commitable_buffer(&self) -> Option<&WlBuffer> { self.last_used_buffer.map(|i| &self.buffers[i].inner) } /// We assume `width` and `height` have already been multiplied by their scale factor #[inline] pub(crate) fn resize(&mut self, width: i32, height: i32) { self.width = width; self.height = height; self.last_used_buffer = None; self.buffers.clear(); } } impl Drop for BumpPool { fn drop(&mut self) { self.pool.destroy(); } } fn new_pool(len: usize, shm: &WlShm) -> (WlShmPool, Mmap) { let mmap = Mmap::new(len); let pool = shm .send_constructor( wl_shm::Request::CreatePool { fd: mmap.fd.as_fd(), size: len as i32, }, Arc::new(ShmPoolData), ) .expect("failed to create WlShmPool object"); (pool, mmap) } fn create_shm_fd() -> io::Result<OwnedFd> { #[cfg(target_os = "linux")] { match create_memfd() { Ok(fd) => return Ok(fd), // Not supported, use fallback. Err(Errno::NOSYS) => (), Err(err) => return Err(Into::<io::Error>::into(err)), }; } let time = SystemTime::now(); let mut mem_file_handle = format!( "/swww-daemon-{}", time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() ); let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; let mode = Mode::RUSR | Mode::WUSR; loop { match rustix::shm::shm_open(mem_file_handle.as_str(), flags, mode) { Ok(fd) => match rustix::shm::shm_unlink(mem_file_handle.as_str()) { Ok(_) => return Ok(fd), Err(errno) => { return Err(errno.into()); } }, Err(Errno::EXIST) => { // Change the handle if we happen to be duplicate. let time = SystemTime::now(); mem_file_handle = format!( "/swww-daemon-{}", time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() ); continue; } Err(Errno::INTR) => continue, Err(err) => return Err(err.into()), } } } #[cfg(target_os = "linux")] fn create_memfd() -> rustix::io::Result<OwnedFd> { use rustix::fs::{MemfdFlags, SealFlags}; use std::ffi::CStr; let name = CStr::from_bytes_with_nul(b"swww-daemon\0").unwrap(); let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC; loop { match rustix::fs::memfd_create(name, flags) { Ok(fd) => { // We only need to seal for the purposes of optimization, ignore the errors. let _ = rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL); return Ok(fd); } Err(Errno::INTR) => continue, Err(err) => return Err(err), } } } #[derive(Debug)] struct ShmPoolData; impl ObjectData for ShmPoolData { fn event( self: Arc<Self>, _: &wayland_client::backend::Backend, _: wayland_client::backend::protocol::Message<wayland_client::backend::ObjectId, OwnedFd>, ) -> Option<Arc<(dyn ObjectData + 'static)>> { unreachable!("wl_shm_pool has no events") } fn destroyed(&self, _: wayland_client::backend::ObjectId) {} } 07070141FCBDA8000081A40000000000000000000000016634268300000C21000000080000000100000000000000000000001D00000000swww-0.9.5/daemon/src/cli.rsuse utils::ipc::PixelFormat; pub struct Cli { pub format: Option<PixelFormat>, pub quiet: bool, pub no_cache: bool, } impl Cli { pub fn new() -> Self { let mut quiet = false; let mut no_cache = false; let mut format = None; let mut args = std::env::args(); args.next(); // skip the first argument while let Some(arg) = args.next() { match arg.as_str() { "-f" | "--format" => match args.next().as_deref() { Some("xrgb") => format = Some(PixelFormat::Xrgb), Some("xbgr") => format = Some(PixelFormat::Xbgr), Some("rgb") => format = Some(PixelFormat::Rgb), Some("bgr") => format = Some(PixelFormat::Bgr), _ => { eprintln!("`--format` command line option must be one of: 'xrgb', 'xbgr', 'rgb' or 'bgr'"); std::process::exit(-2); } }, "-q" | "--quiet" => quiet = true, "--no-cache" => no_cache = true, "-h" | "--help" => { println!("swww-daemon"); println!(); println!("Options:"); println!(); println!(" -f|--format <xrgb|xbgr|rgb|bgr>"); println!(" force the use of a specific wl_shm format."); println!(); println!( " It is generally better to let swww-daemon chose for itself." ); println!(" Only use this as a workaround when you run into problems."); println!(" Whatever you chose, make sure you compositor actually supports it!"); println!(" 'xrgb' is the most compatible one."); println!(); println!(" --no-cache"); println!( " Don't search the cache for the last wallpaper for each output." ); println!(" Useful if you always want to select which image 'swww' loads manually using 'swww img'"); println!(); println!(" -q|--quiet will only log errors"); println!(" -h|--help print help"); println!(" -V|--version print version"); std::process::exit(0); } "-V" | "--version" => { println!("swww-daemon {}", env!("CARGO_PKG_VERSION")); std::process::exit(0); } s => { eprintln!("Unrecognized command line argument: {s}"); eprintln!("Run -h|--help to know what arguments are recognized!"); std::process::exit(-1); } } } Self { format, quiet, no_cache, } } } 07070141FCBDA9000081A40000000000000000000000016634268300007171000000080000000100000000000000000000001E00000000swww-0.9.5/daemon/src/main.rs//! All expects in this program must be carefully chosen on purpose. The idea is that if any of //! them fail there is no point in continuing. All of the initialization code, for example, is full //! of `expects`, **on purpose**, because we **want** to unwind and exit when they happen mod animations; pub mod bump_pool; mod cli; mod wallpaper; use log::{debug, error, info, warn, LevelFilter}; use rustix::{ event::{poll, PollFd, PollFlags}, path::Arg, }; use simplelog::{ColorChoice, TermLogger, TerminalMode, ThreadLogMode}; use wallpaper::Wallpaper; use wayland_protocols::wp::{ fractional_scale::v1::client::{ wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, wp_fractional_scale_v1::WpFractionalScaleV1, }, viewporter::client::{wp_viewport::WpViewport, wp_viewporter::WpViewporter}, }; use std::{ fs, num::NonZeroI32, os::unix::net::{UnixListener, UnixStream}, path::PathBuf, sync::{ atomic::{AtomicBool, Ordering}, Arc, OnceLock, }, }; use wayland_client::{ backend::WeakBackend, globals::{registry_queue_init, GlobalList, GlobalListContents}, protocol::{ wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output, wl_region::WlRegion, wl_registry::WlRegistry, wl_shm::{self, WlShm}, wl_surface::{self, WlSurface}, }, Connection, Dispatch, Proxy, QueueHandle, }; use utils::ipc::{ connect_to_socket, get_socket_path, read_socket, AnimationRequest, Answer, BgInfo, ImageRequest, PixelFormat, Request, Scale, }; use animations::Animator; pub type LayerShell = wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1; pub type LayerSurface = wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; // We need this because this might be set by signals, so we can't keep it in the daemon static EXIT: AtomicBool = AtomicBool::new(false); #[inline] fn exit_daemon() { EXIT.store(true, Ordering::Release); } #[inline] fn should_daemon_exit() -> bool { EXIT.load(Ordering::Acquire) } static PIXEL_FORMAT: OnceLock<PixelFormat> = OnceLock::new(); static BACKEND: OnceLock<WeakBackend> = OnceLock::new(); #[inline] pub fn wl_shm_format() -> wl_shm::Format { match pixel_format() { PixelFormat::Xrgb => wl_shm::Format::Xrgb8888, PixelFormat::Xbgr => wl_shm::Format::Xbgr8888, PixelFormat::Rgb => wl_shm::Format::Rgb888, PixelFormat::Bgr => wl_shm::Format::Bgr888, } } #[inline] pub fn flush_wayland() { debug_assert!(BACKEND.get().is_some()); BACKEND.get().unwrap().upgrade().unwrap().flush().unwrap(); } #[inline] pub fn pixel_format() -> PixelFormat { debug_assert!(PIXEL_FORMAT.get().is_some()); *PIXEL_FORMAT.get().unwrap_or(&PixelFormat::Xrgb) } extern "C" fn signal_handler(_s: libc::c_int) { exit_daemon(); } fn main() -> Result<(), String> { let cli = cli::Cli::new(); make_logger(cli.quiet); if let Some(format) = cli.format { PIXEL_FORMAT.set(format).unwrap(); info!("Forced usage of wl_shm format: {:?}", wl_shm_format()); } rayon::ThreadPoolBuilder::default() .thread_name(|i| format!("rayon thread {i}")) .stack_size(1 << 19) // 512KiB; we do not need a large stack .build_global() .expect("failed to configure rayon global thread pool"); let listener = SocketWrapper::new()?; setup_signals(); let conn = Connection::connect_to_env().expect("failed to connect to the wayland server"); BACKEND.set(conn.backend().downgrade()).unwrap(); // Enumerate the list of globals to get the protocols the server implements. let (globals, mut event_queue) = registry_queue_init(&conn).expect("failed to initialize the event queue"); let qh = event_queue.handle(); let mut daemon = Daemon::new(&globals, &qh, cli.no_cache); // roundtrip to get the shm formats before setting up the outputs event_queue.roundtrip(&mut daemon).unwrap(); for global in globals.contents().clone_list() { if global.interface == "wl_output" { if global.version >= 2 { let output = globals .registry() .bind(global.name, global.version, &qh, ()); daemon.new_output(&qh, output, global.name); } else { error!("wl_output must be at least version 2 for swww-daemon!") } } } if let Ok(true) = sd_notify::booted() { if let Err(e) = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]) { error!("Error sending status update to systemd: {}", e.to_string()); } } info!("Initialization succeeded! Starting main loop..."); while !should_daemon_exit() { // Process wayland events event_queue .flush() .expect("failed to flush the event queue"); let read_guard = event_queue .prepare_read() .expect("failed to prepare the event queue's read"); let events = { let connection_fd = read_guard.connection_fd(); let mut fds = [ PollFd::new(&connection_fd, PollFlags::IN), PollFd::new(&listener.0, PollFlags::IN), ]; match poll(&mut fds, -1) { Ok(_) => (), Err(e) => match e { rustix::io::Errno::INTR => (), _ => panic!("failed to poll file descriptors: {e}"), }, }; [fds[0].revents(), fds[1].revents()] }; if !events[0].is_empty() { match read_guard.read() { Ok(_) => { event_queue .dispatch_pending(&mut daemon) .expect("failed to dispatch events"); } Err(e) => match e { wayland_client::backend::WaylandError::Io(io) => match io.kind() { std::io::ErrorKind::WouldBlock => { warn!("failed to read wayland events because it would block") } _ => panic!("Io error when reading wayland events: {io}"), }, wayland_client::backend::WaylandError::Protocol(e) => { panic!("{e}") } }, } } else { drop(read_guard); } if !events[1].is_empty() { match listener.0.accept() { Ok((stream, _adr)) => daemon.recv_socket_msg(stream), Err(e) => match e.kind() { std::io::ErrorKind::WouldBlock => (), _ => return Err(format!("failed to accept incoming connection: {e}")), }, } } } info!("Goodbye!"); Ok(()) } fn setup_signals() { // C data structure, expected to be zeroed out. let mut sigaction: libc::sigaction = unsafe { std::mem::zeroed() }; unsafe { libc::sigemptyset(std::ptr::addr_of_mut!(sigaction.sa_mask)) }; #[cfg(not(target_os = "aix"))] { sigaction.sa_sigaction = signal_handler as usize; } #[cfg(target_os = "aix")] { sigaction.sa_union.__su_sigaction = handler; } for signal in [libc::SIGINT, libc::SIGQUIT, libc::SIGTERM, libc::SIGHUP] { let ret = unsafe { libc::sigaction(signal, std::ptr::addr_of!(sigaction), std::ptr::null_mut()) }; if ret != 0 { error!("Failed to install signal handler!") } } } /// This is a wrapper that makes sure to delete the socket when it is dropped struct SocketWrapper(UnixListener); impl SocketWrapper { fn new() -> Result<Self, String> { let socket_addr = get_socket_path(); if socket_addr.exists() { if is_daemon_running(&socket_addr)? { return Err( "There is an swww-daemon instance already running on this socket!".to_string(), ); } else { warn!( "socket file {} was not deleted when the previous daemon exited", socket_addr.to_string_lossy() ); if let Err(e) = std::fs::remove_file(&socket_addr) { return Err(format!("failed to delete previous socket: {e}")); } } } let runtime_dir = match socket_addr.parent() { Some(path) => path, None => return Err("couldn't find a valid runtime directory".to_owned()), }; if !runtime_dir.exists() { match fs::create_dir(runtime_dir) { Ok(()) => (), Err(e) => return Err(format!("failed to create runtime dir: {e}")), } } let listener = match UnixListener::bind(socket_addr.clone()) { Ok(address) => address, Err(e) => return Err(format!("couldn't bind socket: {e}")), }; debug!( "Made socket in {:?} and initialized logger. Starting daemon...", listener.local_addr().unwrap() //this should always work if the socket connected correctly ); Ok(Self(listener)) } } impl Drop for SocketWrapper { fn drop(&mut self) { let socket_addr = get_socket_path(); if let Err(e) = fs::remove_file(&socket_addr) { error!("Failed to remove socket at {socket_addr:?}: {e}"); } info!("Removed socket at {:?}", socket_addr); } } struct Daemon { // Wayland stuff layer_shell: LayerShell, compositor: WlCompositor, shm: WlShm, pixel_format: PixelFormat, shm_format: wl_shm::Format, viewporter: WpViewporter, fractional_scale_manager: Option<WpFractionalScaleManagerV1>, // swww stuff wallpapers: Vec<Arc<Wallpaper>>, animator: Animator, use_cache: bool, } impl Daemon { fn new(globals: &GlobalList, qh: &QueueHandle<Self>, no_cache: bool) -> Self { // The compositor (not to be confused with the server which is commonly called the compositor) allows // configuring surfaces to be presented. let compositor: WlCompositor = globals .bind(qh, 1..=6, ()) .expect("wl_compositor is not available"); let layer_shell: LayerShell = globals .bind(qh, 1..=4, ()) .expect("layer shell is not available"); let shm: WlShm = globals .bind(qh, 1..=1, ()) .expect("wl_shm is not available"); let pixel_format = PixelFormat::Xrgb; let shm_format = wl_shm::Format::Xrgb8888; let viewporter = globals .bind(qh, 1..=1, ()) .expect("viewported not available"); let fractional_scale_manager = globals.bind(qh, 1..=1, ()).ok(); Self { layer_shell, compositor, shm, pixel_format, shm_format, viewporter, fractional_scale_manager, wallpapers: Vec::new(), animator: Animator::new(), use_cache: !no_cache, } } fn recv_socket_msg(&mut self, stream: UnixStream) { let bytes = match utils::ipc::read_socket(&stream) { Ok(bytes) => bytes, Err(e) => { error!("FATAL: cannot read socket: {e}. Exiting..."); exit_daemon(); return; } }; let request = Request::receive(&bytes); let answer = match request { Request::Animation(AnimationRequest { animations, outputs, }) => { let mut wallpapers = Vec::new(); for names in outputs.iter() { wallpapers.push(self.find_wallpapers_by_names(names)); } self.animator.animate(animations, wallpapers) } Request::Clear(clear) => { let wallpapers = self.find_wallpapers_by_names(&clear.outputs); let color = clear.color; match std::thread::Builder::new() .stack_size(1 << 15) .name("clear".to_string()) .spawn(move || { for wallpaper in &wallpapers { wallpaper.stop_animations(); } for wallpaper in wallpapers { wallpaper.set_img_info(utils::ipc::BgImg::Color(color)); wallpaper.clear(color); wallpaper.draw(); } }) { Ok(_) => Answer::Ok, Err(e) => Answer::Err(format!("failed to spawn `clear` thread: {e}")), } } Request::Ping => Answer::Ping( self.wallpapers .iter() .all(|w| w.configured.load(std::sync::atomic::Ordering::Acquire)), ), Request::Kill => { exit_daemon(); Answer::Ok } Request::Query => Answer::Info(self.wallpapers_info()), Request::Img(ImageRequest { transition, imgs, outputs, }) => { let mut used_wallpapers = Vec::new(); for names in outputs.iter() { let mut wallpapers = self.find_wallpapers_by_names(names); for wallpaper in wallpapers.iter_mut() { wallpaper.stop_animations(); } used_wallpapers.push(wallpapers); } self.animator.transition(transition, imgs, used_wallpapers) } }; if let Err(e) = answer.send(&stream) { error!("error sending answer to client: {e}"); } } fn wallpapers_info(&self) -> Box<[BgInfo]> { self.wallpapers .iter() .map(|wallpaper| wallpaper.get_bg_info()) .collect() } fn find_wallpapers_by_names(&self, names: &[String]) -> Vec<Arc<Wallpaper>> { self.wallpapers .iter() .filter_map(|wallpaper| { if names.is_empty() || names.iter().any(|n| wallpaper.has_name(n)) { return Some(Arc::clone(wallpaper)); } None }) .collect() } fn new_output(&mut self, qh: &QueueHandle<Self>, output: wl_output::WlOutput, name: u32) { if PIXEL_FORMAT.get().is_none() { assert!(PIXEL_FORMAT.set(self.pixel_format).is_ok()); log::info!("Selected wl_shm format: {:?}", self.shm_format); } let surface = self.compositor.create_surface(qh, ()); // Wayland clients are expected to render the cursor on their input region. // By setting the input region to an empty region, the compositor renders the // default cursor. Without this, an empty desktop won't render a cursor. let region = self.compositor.create_region(qh, ()); surface.set_input_region(Some(®ion)); let layer_surface = self.layer_shell.get_layer_surface( &surface, Some(&output), wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::Layer::Background, "swww-daemon".to_string(), qh, (), ); let wp_viewport = self.viewporter.get_viewport(&surface, qh, ()); let wp_fractional = self .fractional_scale_manager .as_ref() .map(|f| f.get_fractional_scale(&surface, qh, surface.clone())); debug!("New output: {}", output.id()); self.wallpapers.push(Arc::new(Wallpaper::new( output, name, surface, wp_viewport, wp_fractional, layer_surface, &self.shm, qh, ))); } } impl Dispatch<wl_output::WlOutput, ()> for Daemon { fn event( state: &mut Self, proxy: &wl_output::WlOutput, event: <wl_output::WlOutput as wayland_client::Proxy>::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { for wallpaper in state.wallpapers.iter_mut() { if wallpaper.has_output(proxy) { match event { wl_output::Event::Geometry { x, y, transform, .. } => { debug!("output {} position: {x},{y}", proxy.id()); match transform { wayland_client::WEnum::Value(v) => match v { wl_output::Transform::_90 | wl_output::Transform::_270 | wl_output::Transform::Flipped90 | wl_output::Transform::Flipped270 => wallpaper.set_vertical(), wl_output::Transform::Normal | wl_output::Transform::_180 | wl_output::Transform::Flipped | wl_output::Transform::Flipped180 => wallpaper.set_horizontal(), e => warn!("unprocessed transform: {e:?}"), }, wayland_client::WEnum::Unknown(u) => { error!("received unknown transform from compositor: {u}") } } } wl_output::Event::Mode { flags: _flags, width, height, .. } => wallpaper.set_dimensions(width, height), wl_output::Event::Done => wallpaper.commit_surface_changes(state.use_cache), wl_output::Event::Scale { factor } => match NonZeroI32::new(factor) { Some(factor) => wallpaper.set_scale(Scale::Whole(factor)), None => error!("received scale factor of 0 from compositor"), }, wl_output::Event::Name { name } => wallpaper.set_name(name), wl_output::Event::Description { description } => { wallpaper.set_desc(description) } e => error!("unrecognized WlOutput event: {e:?}"), } return; } } warn!("received event for non-existing output") } } impl Dispatch<WlCompositor, ()> for Daemon { fn event( _state: &mut Self, _proxy: &WlCompositor, _event: <WlCompositor as wayland_client::Proxy>::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { error!("WlCompositor has no events"); } } impl Dispatch<WlSurface, ()> for Daemon { fn event( state: &mut Self, proxy: &WlSurface, event: <WlSurface as wayland_client::Proxy>::Event, _data: &(), _conn: &Connection, _qh: &QueueHandle<Self>, ) { match event { wl_surface::Event::Enter { output } => debug!("Output {}: Surface Enter", output.id()), wl_surface::Event::Leave { output } => debug!("Output {}: Surface Leave", output.id()), wl_surface::Event::PreferredBufferScale { factor } => { for wallpaper in state.wallpapers.iter_mut() { if wallpaper.has_surface(proxy) { match NonZeroI32::new(factor) { Some(factor) => { wallpaper.set_scale(Scale::Whole(factor)); wallpaper.commit_surface_changes(state.use_cache); } None => error!("received scale factor of 0 from compositor"), } return; } } warn!("received new scale factor for non-existing surface") } wl_surface::Event::PreferredBufferTransform { .. } => { warn!("Received transform. We currently ignore those") } e => error!("unrecognized WlSurface event: {e:?}"), } } } impl Dispatch<WlRegion, ()> for Daemon { fn event( _state: &mut Self, _proxy: &WlRegion, _event: <WlRegion as wayland_client::Proxy>::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { error!("WlRegion has no events") } } impl Dispatch<WlShm, ()> for Daemon { fn event( state: &mut Self, _proxy: &WlShm, event: <WlShm as wayland_client::Proxy>::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { match event { wl_shm::Event::Format { format: wenum } => match wenum { wayland_client::WEnum::Value(format) => { if format == wl_shm::Format::Bgr888 { state.shm_format = wl_shm::Format::Bgr888; state.pixel_format = PixelFormat::Bgr; } else if format == wl_shm::Format::Rgb888 && state.pixel_format != PixelFormat::Bgr { state.shm_format = wl_shm::Format::Rgb888; state.pixel_format = PixelFormat::Rgb; } else if format == wl_shm::Format::Xbgr8888 && state.pixel_format == PixelFormat::Xrgb { state.shm_format = wl_shm::Format::Xbgr8888; state.pixel_format = PixelFormat::Xbgr; } } wayland_client::WEnum::Unknown(v) => { error!("Received unknown shm format number {v} from server") } }, e => warn!("unhandled WlShm event: {e:?}"), } } } impl Dispatch<WlCallback, WlSurface> for Daemon { fn event( state: &mut Self, _proxy: &WlCallback, event: <WlCallback as wayland_client::Proxy>::Event, data: &WlSurface, _conn: &Connection, _qh: &QueueHandle<Self>, ) { match event { wayland_client::protocol::wl_callback::Event::Done { callback_data } => { for wallpaper in state.wallpapers.iter_mut() { if wallpaper.has_surface(data) { wallpaper.frame_callback_completed(callback_data); return; } } warn!("received callback for non-existing surface!") } e => error!("unrecognized WlCallback event: {e:?}"), } } } impl Dispatch<WlRegistry, GlobalListContents> for Daemon { fn event( state: &mut Self, proxy: &WlRegistry, event: <WlRegistry as wayland_client::Proxy>::Event, _data: &GlobalListContents, _conn: &Connection, qh: &QueueHandle<Self>, ) { match event { wayland_client::protocol::wl_registry::Event::Global { name, interface, version, } => { if interface.as_str() == "wl_output" { if version < 2 { error!("your compositor must support at least version 2 of wl_output"); } else { let output = proxy.bind(name, version, qh, ()); state.new_output(qh, output, name) } } } wayland_client::protocol::wl_registry::Event::GlobalRemove { name } => { state.wallpapers.retain(|w| !w.has_output_name(name)); debug!("Destroyed output with id: {name}"); } e => error!("unrecognized WlRegistry event: {e:?}"), } } } impl Dispatch<WpViewporter, ()> for Daemon { fn event( _state: &mut Self, _proxy: &WpViewporter, _event: <WpViewporter as Proxy>::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { error!("WpViewporter has no events"); } } impl Dispatch<WpViewport, ()> for Daemon { fn event( _state: &mut Self, _proxy: &WpViewport, _event: <WpViewport as Proxy>::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { error!("WpViewport has no events"); } } impl Dispatch<WpFractionalScaleManagerV1, ()> for Daemon { fn event( _state: &mut Self, _proxy: &WpFractionalScaleManagerV1, _event: <WpFractionalScaleManagerV1 as Proxy>::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { error!("WpFractionalScaleManagerV1 has no events"); } } impl Dispatch<WpFractionalScaleV1, WlSurface> for Daemon { fn event( state: &mut Self, _proxy: &WpFractionalScaleV1, event: <WpFractionalScaleV1 as Proxy>::Event, data: &WlSurface, _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1; match event { wp_fractional_scale_v1::Event::PreferredScale { scale } => { for wallpaper in state.wallpapers.iter_mut() { if wallpaper.has_surface(data) { match NonZeroI32::new(scale as i32) { Some(factor) => { wallpaper.set_scale(Scale::Fractional(factor)); wallpaper.commit_surface_changes(state.use_cache); } None => error!("received scale factor of 0 from compositor"), } return; } } warn!("received new fractional scale factor for non-existing surface") } e => error!("unrecognized WpFractionalScaleV1 event: {e:?}"), } } } impl Dispatch<LayerShell, ()> for Daemon { fn event( _state: &mut Self, _proxy: &LayerShell, _event: <LayerShell as Proxy>::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { error!("LayerShell has no events"); } } impl Dispatch<LayerSurface, ()> for Daemon { fn event( state: &mut Self, proxy: &LayerSurface, event: <LayerSurface as Proxy>::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle<Self>, ) { use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::Event; match event { Event::Configure { serial, .. } => { for w in &mut state.wallpapers { if w.has_layer_surface(proxy) { proxy.ack_configure(serial); return; } } } Event::Closed => { state.wallpapers.retain(|w| !w.has_layer_surface(proxy)); } e => error!("unrecognized LayerSurface event: {e:?}"), } } } fn make_logger(quiet: bool) { let config = simplelog::ConfigBuilder::new() .set_thread_level(LevelFilter::Error) // let me see where the processing is happening .set_thread_mode(ThreadLogMode::Both) .build(); TermLogger::init( if quiet { LevelFilter::Error } else { LevelFilter::Debug }, config, TerminalMode::Stderr, ColorChoice::AlwaysAnsi, ) .expect("Failed to initialize logger. Cancelling..."); } pub fn is_daemon_running(addr: &PathBuf) -> Result<bool, String> { let sock = match connect_to_socket(addr, 5, 100) { Ok(s) => s, // likely a connection refused; either way, this is a reliable signal there's no surviving // daemon. Err(_) => return Ok(false), }; Request::Ping.send(&sock)?; let answer = Answer::receive(&read_socket(&sock)?); match answer { Answer::Ping(_) => Ok(true), _ => Err("Daemon did not return Answer::Ping, as expected".to_string()), } } 07070141FCBDAA000081A400000000000000000000000166342683000036F6000000080000000100000000000000000000002300000000swww-0.9.5/daemon/src/wallpaper.rsuse log::{debug, error, warn}; use utils::ipc::{BgImg, BgInfo, Scale}; use wayland_protocols::wp::{ fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, viewporter::client::wp_viewport::WpViewport, }; use std::{ num::NonZeroI32, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, Condvar, Mutex, RwLock, }, }; use wayland_client::{ protocol::{wl_output::WlOutput, wl_shm::WlShm, wl_surface::WlSurface}, Proxy, QueueHandle, }; use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::{ Anchor, KeyboardInteractivity, }; use crate::{bump_pool::BumpPool, Daemon, LayerSurface}; #[derive(Debug)] struct AnimationState { id: AtomicUsize, transition_finished: Arc<AtomicBool>, } #[derive(Debug)] pub(super) struct AnimationToken { id: usize, transition_done: Arc<AtomicBool>, } impl AnimationToken { pub(super) fn is_transition_done(&self) -> bool { self.transition_done.load(Ordering::Acquire) } pub(super) fn set_transition_done(&self, wallpaper: &Wallpaper) { if wallpaper.has_animation_id(self) { self.transition_done.store(true, Ordering::Release); } } } struct FrameCallbackHandler { cvar: Condvar, /// This time doesn't really mean anything. We don't really use it for frame timing, but we /// store it for the sake of signaling when the compositor emitted the last frame callback time: Mutex<Option<u32>>, } /// Owns all the necessary information for drawing. #[derive(Clone, Debug)] struct WallpaperInner { name: Option<String>, desc: Option<String>, width: NonZeroI32, height: NonZeroI32, scale_factor: Scale, is_vertical: bool, } impl Default for WallpaperInner { fn default() -> Self { Self { name: None, desc: None, width: unsafe { NonZeroI32::new_unchecked(4) }, height: unsafe { NonZeroI32::new_unchecked(4) }, scale_factor: Scale::Whole(unsafe { NonZeroI32::new_unchecked(1) }), is_vertical: false, } } } pub(super) struct Wallpaper { output: WlOutput, output_name: u32, wl_surface: WlSurface, wp_viewport: WpViewport, #[allow(unused)] wp_fractional: Option<WpFractionalScaleV1>, layer_surface: LayerSurface, inner: RwLock<WallpaperInner>, inner_staging: Mutex<WallpaperInner>, animation_state: AnimationState, pub configured: AtomicBool, qh: QueueHandle<Daemon>, frame_callback_handler: FrameCallbackHandler, img: Mutex<BgImg>, pool: Mutex<BumpPool>, } impl Wallpaper { #[allow(clippy::too_many_arguments)] pub(crate) fn new( output: WlOutput, output_name: u32, wl_surface: WlSurface, wp_viewport: WpViewport, wp_fractional: Option<WpFractionalScaleV1>, layer_surface: LayerSurface, shm: &WlShm, qh: &QueueHandle<Daemon>, ) -> Self { let inner = RwLock::default(); let inner_staging = Mutex::default(); let frame_callback_handler = FrameCallbackHandler { cvar: Condvar::new(), time: Mutex::new(Some(0)), // we do not have to wait for the first frame }; // Configure the layer surface layer_surface.set_anchor(Anchor::all()); layer_surface.set_exclusive_zone(-1); layer_surface.set_margin(0, 0, 0, 0); layer_surface.set_keyboard_interactivity(KeyboardInteractivity::None); //layer_surface.set_size(4, 4); wl_surface.set_buffer_scale(1); // commit so that the compositor send the initial configuration wl_surface.commit(); wl_surface.frame(qh, wl_surface.clone()); let pool = Mutex::new(BumpPool::new(256, 256, shm)); Self { output, output_name, wl_surface, wp_viewport, wp_fractional, layer_surface, inner, inner_staging, animation_state: AnimationState { id: AtomicUsize::new(0), transition_finished: Arc::new(AtomicBool::new(false)), }, configured: AtomicBool::new(false), qh: qh.clone(), frame_callback_handler, img: Mutex::new(BgImg::Color([0, 0, 0])), pool, } } #[inline] pub fn get_bg_info(&self) -> BgInfo { let inner = self.inner.read().unwrap(); BgInfo { name: inner.name.clone().unwrap_or("?".to_string()), dim: (inner.width.get() as u32, inner.height.get() as u32), scale_factor: inner.scale_factor, img: self.img.lock().unwrap().clone(), pixel_format: crate::pixel_format(), } } #[inline] pub fn set_name(&self, name: String) { debug!("Output {} name: {name}", self.output.id()); self.inner_staging.lock().unwrap().name = Some(name); } #[inline] pub fn set_desc(&self, desc: String) { debug!("Output {} description: {desc}", self.output.id()); self.inner_staging.lock().unwrap().desc = Some(desc) } #[inline] pub fn set_dimensions(&self, width: i32, height: i32) { let mut lock = self.inner_staging.lock().unwrap(); let (width, height) = lock.scale_factor.div_dim(width, height); match NonZeroI32::new(width) { Some(width) => lock.width = width, None => { error!( "dividing width {width} by scale_factor {} results in width 0!", lock.scale_factor ) } } match NonZeroI32::new(height) { Some(height) => lock.height = height, None => { error!( "dividing height {height} by scale_factor {} results in height 0!", lock.scale_factor ) } } if lock.is_vertical { if lock.width > lock.height { let t = lock.width; lock.width = lock.height; lock.height = t; } } else if lock.width < lock.height { let t = lock.width; lock.width = lock.height; lock.height = t; } } #[inline] pub fn set_vertical(&self) { let mut lock = self.inner_staging.lock().unwrap(); lock.is_vertical = true; } #[inline] pub fn set_horizontal(&self) { let mut lock = self.inner_staging.lock().unwrap(); lock.is_vertical = false; } #[inline] pub fn set_scale(&self, scale: Scale) { let mut lock = self.inner_staging.lock().unwrap(); if matches!(lock.scale_factor, Scale::Fractional(_)) && matches!(scale, Scale::Whole(_)) { return; } let (old_width, old_height) = lock .scale_factor .mul_dim(lock.width.get(), lock.height.get()); lock.scale_factor = scale; let (width, height) = lock.scale_factor.div_dim(old_width, old_height); match NonZeroI32::new(width) { Some(width) => lock.width = width, None => { error!( "dividing width {width} by scale_factor {} results in width 0!", lock.scale_factor ) } } match NonZeroI32::new(height) { Some(height) => lock.height = height, None => { error!( "dividing height {height} by scale_factor {} results in height 0!", lock.scale_factor ) } } } #[inline] pub fn commit_surface_changes(&self, use_cache: bool) { let mut inner = self.inner.write().unwrap(); let staging = self.inner_staging.lock().unwrap(); if inner.name != staging.name && use_cache { let name = staging.name.clone().unwrap_or("".to_string()); if let Err(e) = std::thread::Builder::new() .name("cache loader".to_string()) .stack_size(1 << 14) .spawn(move || { if let Err(e) = utils::cache::load(&name) { warn!("failed to load cache: {e}"); } }) { warn!("failed to spawn `cache loader` thread: {e}"); } } if staging.scale_factor != inner.scale_factor { match staging.scale_factor { Scale::Whole(i) => { // unset destination self.wp_viewport.set_destination(-1, -1); self.wl_surface.set_buffer_scale(i.get()); } Scale::Fractional(_) => { self.wl_surface.set_buffer_scale(1); self.wp_viewport .set_destination(staging.width.get(), staging.height.get()); } } } if (inner.width, inner.height) == (staging.width, staging.height) { inner.scale_factor = staging.scale_factor; inner.name = staging.name.clone(); inner.desc = staging.desc.clone(); return; } //otherwise, everything changed *inner = staging.clone(); let (width, height, scale_factor) = (staging.width, staging.height, staging.scale_factor); drop(inner); drop(staging); self.stop_animations(); self.layer_surface .set_size(width.get() as u32, height.get() as u32); let (w, h) = scale_factor.mul_dim(width.get(), height.get()); self.pool.lock().unwrap().resize(w, h); *self.frame_callback_handler.time.lock().unwrap() = Some(0); self.wl_surface.commit(); self.wl_surface.frame(&self.qh, self.wl_surface.clone()); self.configured .store(true, std::sync::atomic::Ordering::Release); } #[inline] pub(super) fn has_name(&self, name: &str) -> bool { match self.inner.read().unwrap().name.as_ref() { Some(n) => n == name, None => false, } } #[inline] pub(super) fn has_output(&self, output: &WlOutput) -> bool { self.output == *output } #[inline] pub(super) fn has_output_name(&self, name: u32) -> bool { self.output_name == name } #[inline] pub(super) fn has_animation_id(&self, token: &AnimationToken) -> bool { self.animation_state .id .load(std::sync::atomic::Ordering::Acquire) == token.id } #[inline] pub(super) fn has_surface(&self, wl_surface: &WlSurface) -> bool { self.wl_surface == *wl_surface } #[inline] pub(super) fn has_layer_surface(&self, layer_surface: &LayerSurface) -> bool { self.layer_surface == *layer_surface } pub(super) fn get_dimensions(&self) -> (u32, u32) { let inner = self.inner.read().unwrap(); let dim = inner .scale_factor .mul_dim(inner.width.get(), inner.height.get()); (dim.0 as u32, dim.1 as u32) } #[inline] pub(super) fn canvas_change<F, T>(&self, f: F) -> T where F: FnOnce(&mut [u8]) -> T, { f(self.pool.lock().unwrap().get_drawable()) } #[inline] pub(super) fn create_animation_token(&self) -> AnimationToken { let id = self.animation_state.id.load(Ordering::Acquire); AnimationToken { id, transition_done: Arc::clone(&self.animation_state.transition_finished), } } #[inline] pub(super) fn frame_callback_completed(&self, time: u32) { *self.frame_callback_handler.time.lock().unwrap() = Some(time); self.frame_callback_handler.cvar.notify_all(); } /// Stops all animations with the current id, by increasing that id #[inline] pub(super) fn stop_animations(&self) { self.animation_state.id.fetch_add(1, Ordering::AcqRel); self.animation_state .transition_finished .store(false, Ordering::Release); } pub(super) fn clear(&self, color: [u8; 3]) { self.canvas_change(|canvas| { for pixel in canvas.chunks_exact_mut(crate::pixel_format().channels().into()) { pixel[0..3].copy_from_slice(&color); } }) } pub(super) fn set_img_info(&self, img_info: BgImg) { debug!( "output {:?} - drawing: {}", self.inner.read().unwrap().name, img_info ); *self.img.lock().unwrap() = img_info; } pub(super) fn draw(&self) { { let mut time = self.frame_callback_handler.time.lock().unwrap(); while time.is_none() { debug!("waiting for condvar"); time = self.frame_callback_handler.cvar.wait(time).unwrap(); } *time = None; } let inner = self.inner.read().unwrap(); if let Some(buf) = self.pool.lock().unwrap().get_commitable_buffer() { let (width, height) = inner .scale_factor .mul_dim(inner.width.get(), inner.height.get()); let surface = &self.wl_surface; surface.attach(Some(buf), 0, 0); drop(inner); surface.damage_buffer(0, 0, width, height); surface.commit(); surface.frame(&self.qh, surface.clone()); } else { drop(inner); // commit and send another frame request, since we consumed the previous one let surface = &self.wl_surface; surface.commit(); surface.frame(&self.qh, surface.clone()); } } } impl Drop for Wallpaper { fn drop(&mut self) { self.output.release() } } unsafe impl Sync for Wallpaper {} unsafe impl Send for Wallpaper {} 0707010018BB8D000081A40000000000000000000000016634268300000356000000080000000100000000000000000000001500000000swww-0.9.5/deny.toml[advisories] db-path = "~/.cargo/advisory-db" db-urls = ["https://github.com/rustsec/advisory-db"] yanked = "deny" ignore = [] [bans] multiple-versions = "warn" wildcards = "warn" highlight = "all" # Certain crates/versions that will be skipped when doing duplicate detection. skip = [] # Crate dependency trees that will be skipped when doing duplicate detection. skip-tree = [] [licenses] # The lint level for crates which do not have a detectable license allow = [ "Apache-2.0", "Apache-2.0 WITH LLVM-exception", # used by the ravif crate "BSD-2-Clause", # used by the ravif crate "BSD-3-Clause", "GPL-3.0", "MIT", "Unicode-DFS-2016", ] [sources] unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] allow-git = ["https://github.com/SoftbearStudios/bitcode.git"] 07070110381063000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000000F00000000swww-0.9.5/doc07070110381064000081A4000000000000000000000001663426830000000B000000080000000100000000000000000000001A00000000swww-0.9.5/doc/.gitignoregenerated/ 07070110381065000081ED00000000000000000000000166342683000002A2000000080000000100000000000000000000001600000000swww-0.9.5/doc/gen.sh#!/bin/sh # This script generates man pages in a `doc/generated` directory. In order to # install these, you need to move the man pages to the appropriate location in # your system. You should be able to figure out the correct directories by # running `manpath`. # # Package Maintainers: please consult your distribution's specific documentation # to adapt to whatever idiosyncrasies it may have. set -e DIR=$(dirname "$0") GEN_DIR="$DIR"/generated if [ ! -d "$GEN_DIR" ]; then mkdir -v "$GEN_DIR" fi for FILE in "$DIR"/*scd; do GEN="$GEN_DIR"/"$(basename --suffix .scd "$FILE")" printf "generating %s..." "$GEN" scdoc < "$FILE" > "$GEN" printf " ...done!\n" done 07070110381067000081A400000000000000000000000166342683000002E1000000080000000100000000000000000000002600000000swww-0.9.5/doc/swww-clear-cache.1.scdswww-clear-cache(1) # NAME swww-clear-cache # SYNOPSIS *swww clear-cache* # OPTIONS *-h*, *--help* Print help (see a summary with '-h') # DESCRIPTION Deletes the `swww` cache directory. The cache resides at _$XDG_CACHE_HOME/swww_ or _$HOME/.cache/swww_ if $XDG_CACHE_HOME does not exist. For each monitor, there will be a file in those locations corresponding to the current image/animation being displayed. Furthermore, the cache will keep preprocessed versions of `gif`s. So, if you load a large `gif`, you would have to pay the price for its processing the first time. Note that `swww` will automatically delete any preprocessed animation created with a previous version of `swww` from the cache. # SEE ALSO *swww-img*(1) 07070110381066000081A400000000000000000000000166342683000002DC000000080000000100000000000000000000002000000000swww-0.9.5/doc/swww-clear.1.scdswww-clear(1) # NAME swww-clear # SYNOPSIS *swww clear* [OPTIONS] <COLOR> # OPTIONS *-o*, *--outputs* Comma separated list of outputs to display the image at. Use *swww query* to know which outputs are currently being used. If it isn't set, the image is displayed on all outputs. *-h*, *--help* Print help (see a summary with '-h') # COLOR The color to fill the screen with. It must be given in *RRGGBB*, hex format. Note there is no prepended '#'. Defaults to *000000*. # DESCRIPTION Fills the specified outputs with the given color. Currently, we *do not* cache this, so if you want a color to be set at initialization, you must set it every time: ``` swww init && swww clear 1a804a ``` # SEE ALSO *swww-query*(1) 07070110381068000081A40000000000000000000000016634268300000448000000080000000100000000000000000000002100000000swww-0.9.5/doc/swww-daemon.1.scdswww-daemon(1) # NAME swww-daemon # SYNOPSIS swww-daemon [-q|--quiet] [-f|--format <xrgb|xbgr|rgb|bgr>] [--no-cache] # OPTIONS *-f*,*--format* <xrgb|xbgr|rgb|bgr> Force the daemon to use a specific wl_shm format. IMPORTANT: make sure this is a value your compositor actually supports! 'swww-daemon' will automatically select the best format for itself during initialization; this is only here for fallback, debug, and workaround purposes. *--no-cache* Don't search the cache for the last wallpaper for each output. Useful if you always want to select which image 'swww' loads manually using 'swww img' *-q*,*--quiet* Makes the daemon only log errors. *-h*, *--help* Print help (see a summary with '-h') *-V*, *--version* Print version # DESCRIPTION The *swww-daemon* will run continuously, waiting for commands in _${XDG_RUNTIME_DIR}/swww-${WAYLAND_DISPLAY}.socket_ (or _/tmp/swww/swww-${WAYLAND_DISPLAY}.socket_, if $XDG_RUNTIME_DIR is not set). The daemon will take care of both creating and deleting that file when it is initialized or killed. # SEE ALSO *swww-init*(1) 07070110381069000081A40000000000000000000000016634268300001BCA000000080000000100000000000000000000001E00000000swww-0.9.5/doc/swww-img.1.scdswww-img(1) # NAME swww-img # SYNOPSIS *swww img* [OPTIONS] <path/to/img> # OPTIONS *-f*, *--filter* <FILTER> Filter to use when scaling images Available options are: _Nearest_ | _Bilinear_ | _CatmullRom_ | _Mitchell_ | _Lanczos3_ These are offered by the fast_image_resize crate (https://docs.rs/fast_image_resize/2.5.0/fast_image_resize/). _Nearest_ is what I recommend for pixel art stuff, and ONLY for pixel art stuff. It is also the fastest filter. For non pixel art stuff, I would usually recommend one of the last three, though some experimentation will be necessary to see which one you like best. Note you can also pass the flag *--no-resize*, explained below. In which case the *--filter* flag will have no effect. Default is Lanczos3. *--no-resize* Do not resize the image. Equivalent to *--resize* _no_. If this is set, the image won't be resized, and will be centralized in the middle of the screen instead. If it is smaller than the screen's size, it will be padded with the value of *--fill_color*, below. *--resize* <RESIZE> Whether to resize the image and the method by which to resize it. Possible values: - _no_: Do not resize the image - _crop_: Resize the image to fill the whole screen, cropping out parts that don't fit - _fit_: Resize the image to fit inside the screen, preserving the original aspect ratio Default is _crop_. *--fill-color* <RRGGBB> Which color to fill the padding with when not resizing. Default is _000000_. *-o*, *--outputs* Comma separated list of outputs to display the image at. Use *swww query* to know which outputs are currently being used. If it isn't set, the image is displayed on all outputs. *-t*, *--transition-type* <TRANSITION_TYPE> \[Environment Variable $SWWW_TRANSITION] Sets the type of transition. Default is _simple_, that fades into the new image. Possible transitions are: [- _none_ :- _simple_ :- _fade_ :- _left_ :- _right_ :- _top_ :- _bottom_ :- _wipe_ :- _wave_ :- _grow_ :- _center_ :- _any_ :- _outer_ :- _random_ _none_ will complete the transition instantly. _fade_ is like _simple_ but uses bezier curves while fading the image, its a more polished looking version of _simple_ with less artifacts The _left_, _right_, _top_ and _bottom_ options make the transition happen from that position to its opposite in the screen. _wipe_ is similar to _left_ but allows you to specify the angle for transition with the `--transition-angle` flag. _wave_ is similar to _wipe_ but the sweeping line is wavy. You can control the "waviness" with `--transition-wave`. _grow_ causes a growing circle to transition across the screen and allows changing the circle's center position with the `--transition-pos` flag. _center_ is an alias to _grow_ with position set to center of screen. _any_ is an alias to _grow_ with position set to a random point on screen. _outer_ is the same as grow but the circle shrinks instead of growing. Finally, _random_ will select a transition effect at random *--transition-step* <0-255> \[Environment Variable $SWWW_TRANSITION_STEP] How fast the transition approaches the new image. The transition logic works by adding or subtracting from the current rgb values until the old image transforms in the new one. This controls by how much we add or subtract. For example, if pixel A is 000010, and we need it to transition to pixel B, which is 000020, if *transition-step* is 2, then in one frame pixel A will turn to 000012, in the next frame to 000014, and so on. Larger values will make the transition faster, but more abrupt. A value of 255 will always switch to the new image immediately. Default is 90. If *transition-type* is _simple_, default is 2. *--transition-duration* <seconds (can have decimals)> \[Environment Variable $SWWW_TRANSITION_DURATION] How long the transition takes to complete, in seconds. Note this doesn't work with the _simple_ transition. Default is 3. *--transition-fps* <frames per second (max 255)> \[Environment Variable: $SWWW_TRANSITION_FPS] Frame rate for the transition effect. Note there is no point in setting this to a value smaller than what your monitor supports. Also note this is **different** from the transition-step. That one controls by how much we approach the new image every frame. Default is 30. *--transition-angle* <angle, in degrees (parsed as a float)> \[Environment Variable: SWWW_TRANSITION_ANGLE] This is used for the _wipe_ and _wave_ transitions. It controls the angle of the wipe. Note that the angle is in degrees, where '0' is right to left and '90' is top to bottom, and '270' bottom to top Default is 45. *--transition-pos* <x,y> \[Environment Variable: SWWW_TRANSITION_POS] This is only used for the _grow_ and _outer_ transitions. It controls the center of circle (default is _center_). Position values can be given in both percentage values and pixel values: float values are interpreted as percentages and integer values as pixel values. Eg.: 0.5,0.5 means 50% of the screen width and 50% of the screen height, while 200,400 means 200 pixels from the left and 400 pixels from the bottom. The value can also be an alias which will set the position accordingly: [- _center_ :- _top_ :- _left_ :- _right_ :- _bottom_ :- _top-left_ :- _top-right_ :- _bottom-left_ :- _bottom-right_ Default is _center_. *--invert-y* <bool> \[Environment Variable: SWWW_INVERT_Y] inverts the y position sent in `transiiton_pos` flag *--transition-bezier* <f1,f2,f3,f4 (all floats)> \[Environment Variable: SWWW_TRANSITION_BEZIER] Bezier curve to use for the transition animation. https://cubic-bezier.com is a good website to get these values from. eg: 0.0,0.0,1.0,1.0 for linear animation Default is .54,0,.34,.99 *--transition-wave* <width,height (both floats)> \[Environment Variable: SWWW_TRANSITION_WAVE] Currently only used for _wave_ transition to control the width and height of each wave. Default is : 20,20 *-h*, *--help* Print help (see a summary with '-h') # DESCRIPTION Sends an image (or animated gif) for the daemon to display. You can also use `-` to read from stdin instead. # ABOUT THE CACHE The images sent will be cached at _$XDG_CACHE_HOME/swww_ or _$HOME/.cache/swww_ if $XDG_CACHE_HOME does not exist. For each monitor, there will be a file in those locations corresponding to the current image/animation being displayed. Importantly, **cache will only be loaded during initialization if you use swww init**. That is, calling `swww-daemon` directly will **NOT** load the cache, but calling `swww-init` will. The `swww-daemon` will actually wait until the first image has been set before trying to load the cache. Finally, the cache will keep preprocessed versions of `gif`s. So, if you load a large `gif`, you would have to pay the price for its processing the first time. If you constantly load large `gif`s, this could cause the cache to get very big. You can simply run `swww clean-cache` if this happens. # SEE ALSO *swww-clear-cache*(1) *swww-daemon*(1) *swww-query*(1) 0707011038106A000081A40000000000000000000000016634268300000500000000080000000100000000000000000000001F00000000swww-0.9.5/doc/swww-init.1.scdswww-init(1) # NAME swww-init (DEPRECATED) # SYNOPSIS *swww init* [--no-daemon] [--no-cache] [--format <xrgb|xbgr|rgb|bgr>] # OPTIONS *--no-daemon* Don't fork the daemon. This will keep it running in the current terminal. The only reason to do this would be to see the daemon's logs. Note that for release builds we only log info, warnings and errors, so you won't be seeing much (ideally). This is mostly useful for debugging and developing. *--no-cache* Don't load the cache *during initialization* (it still loads on monitor (re)connection). If want to always pass an image for 'swww' to load, this option can help make the results some reliable: 'swww init --no-cache && swww img <some img>' *--format* <xrgb|xbgr|rgb|bgr> Force the daemon to use a specific wl_shm format. IMPORTANT: make sure this is a value your compositor actually supports! 'swww-daemon' will automatically select the best format for itself during initialization; this is only here for fallback, debug, and workaround purposes. *-h*, *--help* Print help (see a summary with '-h') # DESCRIPTION Initializes the daemon. This is used to be the recommended way of doing it, but that is no longer the case. You should call 'swww-daemon' directly instead. # SEE ALSO *swww-daemon*(1) 0707011038106B000081A4000000000000000000000001663426830000022A000000080000000100000000000000000000001F00000000swww-0.9.5/doc/swww-kill.1.scdswww-kill(1) # NAME swww-kill # SYNOPSIS *swww kill* # OPTIONS *-h*, *--help* Print help (see a summary with '-h') # DESCRIPTION Kills the daemon. This is the recommended way of doing it, since we wait to make sure the socket file was deleted, thus confirming the daemon exited. Note that sending SIGTERM to the daemon would work correctly, but sending SIGKILL would make daemon leave behind the socket file. This is not a big problem; it would only cause a warning to be printed next time the daemon is initialized. # SEE ALSO *swww-daemon*(1) 0707011038106C000081A400000000000000000000000166342683000002E8000000080000000100000000000000000000002000000000swww-0.9.5/doc/swww-query.1.scdswww-query(1) # NAME swww-query # SYNOPSIS *swww query* # OPTIONS *-h*, *--help* Print help (see a summary with '-h') # DESCRIPTION Asks the daemon to print output information (names and dimensions). You may use this to find out valid values for the <swww-img --outputs> option. If you want more detailed information about your outputs, I would recommend trying something like *wlr-randr*. # OUTPUT FORMAT Currently, *swww query* prints information in the following format: ``` OUTPUT: SIZE, scale: SCALE, currently displaying: IMAGE_OR_COLOR ``` where *SIZE* is in the format *WxH* (eg.: *1920x1080*), *SCALE* in "scale: NUMBER", and *IMAGE_OR_COLOR* in - "image: IMAGENAME", if it's an image; or - "color: RGB", if it's a color 07070110381079000081A4000000000000000000000001663426830000029B000000080000000100000000000000000000002200000000swww-0.9.5/doc/swww-restore.1.scdswww-restore(1) # NAME swww-restore # SYNOPSIS *swww restore* # OPTIONS *-o*, *--outputs* Comma separated list of outputs to restore. Use *swww query* to know which outputs are currently being used. If it isn't set, all outputs will be restored. *-h*, *--help* Print help (see a summary with '-h') # DESCRIPTION Restores the last displayed image on the specified outputs. This can be used to split initialization (with `swww init --no-daemon`) and cache loading into different steps, in order to avoid race condition traps. You can also use this command to restore the last displayed image when reconnecting a monitor. # SEE ALSO *swww-clear-cache*(1) 07070110381B0B000081A40000000000000000000000016634268300000700000000080000000100000000000000000000001A00000000swww-0.9.5/doc/swww.1.scdswww(1) # NAME swww - A Solution to your Wayland Wallpaper Woes # SYNOPSIS *swww* <COMMAND> # COMMANDS *clear* Fills the specified outputs with the given color *restore* Restores the last displayed image on the specified outputs *clear-cache* Fills the specified outputs with the given color *img* Sends an image (or animated gif) for the daemon to display *init* Initializes the daemon *kill* Kills the daemon *query* Asks the daemon to print output information (names and dimensions) *help [COMMAND]* Print help or the help of the given command # OPTIONS *-h*, *--help* Print help (see a summary with '-h') *-V*, *--version* Print version # DESCRIPTION *swww* is a wallpaper manager that lets you change what your monitors display as a background by controlling the *swww-daemon* at runtime. It supports animated gifs and putting different stuff in different monitors. I also did my best to make it as resource efficient as possible. To start, begin by running *swww init*. That will set up the *swww-daemon*. Then, you can send images to be displayed with *swww img*. To kill the daemon, use *swww kill*. *Note that swww only works in a compositor that implements the layer-shell protocol*. Typically, _wlr-roots_ based compositors. # FILES *swww* will create the following files in your system: - A socket in _${XDG_RUNTIME_DIR}/swww-${WAYLAND_DISPLAY}.socket_, or _/tmp/swww/swww-${WAYLAND_DISPLAY}.socket_, if $XDG_RUNTIME_DIR is not set. - Cache files in _$XDG_CACHE_HOME/swww_ or _$HOME/.cache/swww_ if $XDG_CACHE_HOME does not exist. These are used to set the wallpaper to the previous image when a monitor is (re)connected or turned on. # SEE ALSO *swww-daemon*(1) *swww-clear*(1) *swww-img*(1) *swww-init*(1) *swww-kill*(1) *swww-query*(1) 070701201CC4DC000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000001B00000000swww-0.9.5/example_scripts070701201CC4E1000081A400000000000000000000000166342683000001BF000000080000000100000000000000000000002500000000swww-0.9.5/example_scripts/README.mdIn here you will find example scripts to help you get started writing your own scripts to work with `swww` for various effects. Currently, there are scripts for: * Randomly going through the images in a directory (swww_randomize.sh) * Changing with which image `swww` is initialized according to the time of day (swww_init_according_to_time_of_day.sh) * Scheduling changes to the wallpaper at different times of day (swww_scheduler.sh) 070701201CC4ED000081ED00000000000000000000000166342683000003CB000000080000000100000000000000000000004100000000swww-0.9.5/example_scripts/swww_init_according_to_time_of_day.sh#!/bin/sh # This allows you to control which image to init the daemon with according # to the time of day. You may change the match cases as you see fit. # This currently only takes hours into account, but it should be easy to # modify to also use minutes, or days of the week, if you want. # # Use it simply by calling this script instead of swww init case $(date +%H) in 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07) # First 8 hours of the day # Uncomment below to setup the image you wish to display as your # wallpaper if you run this script during the first 8 hours of the # day # swww init && swww img path/to/img ;; 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15) # Middle 8 hours of the day # Same as above, but for the middle 8 hours of the day # swww init && swww img path/to/img ;; 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23) # Final 8 hours of the day # Same as above, but for the final 8 hours of the day # swww init && swww img path/to/img ;; esac 070701201CC841000081ED00000000000000000000000166342683000002E3000000080000000100000000000000000000002D00000000swww-0.9.5/example_scripts/swww_randomize.sh#!/bin/bash # This script will randomly go through the files of a directory, setting it # up as the wallpaper at regular intervals # # NOTE: this script is in bash (not posix shell), because the RANDOM variable # we use is not defined in posix if [[ $# -lt 1 ]] || [[ ! -d $1 ]]; then echo "Usage: $0 <dir containing images>" exit 1 fi # Edit below to control the images transition export SWWW_TRANSITION_FPS=60 export SWWW_TRANSITION_STEP=2 # This controls (in seconds) when to switch to the next image INTERVAL=300 while true; do find "$1" -type f \ | while read -r img; do echo "$((RANDOM % 1000)):$img" done \ | sort -n | cut -d':' -f2- \ | while read -r img; do swww img "$img" sleep $INTERVAL done done 070701201CC840000081ED00000000000000000000000166342683000003BF000000080000000100000000000000000000002D00000000swww-0.9.5/example_scripts/swww_scheduler.sh#!/bin/sh # This is a script to help you schedule image switching at different times of # the day. You may use it as-is or as inspiration for something else if [ $# -lt 2 ]; then echo "Usage: $0 <path/to/img [optional arguments to pass to swww]> <time in HH:MM format> This will use the 'at' command to schedule the image switch. You can control the transition fps or step by passing the respective options: $0 'path/to/img --transition-fps 60 --transition-step 5' '18:00' " exit 1 fi if ! type "at" > /dev/null 2>&1; then echo "ERROR: 'at' command doesn't exist!" exit 1 fi echo "swww img $1" | at "$2" # NOTE: the above line is really the only one that matters, so if you are # making a script and want to schedule a bunch of things at once, I recommend # creating a function, like: # # swww_schedule() { # echo "swww img $1" | at "$2" # } # # Then, you can simply call: # swww_schedule <path/to/img> <HH:MM> # as many time as you need 07070130195CE4000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000000F00000000swww-0.9.5/src07070130195CE5000081A400000000000000000000000166342683000047C3000000080000000100000000000000000000001600000000swww-0.9.5/src/cli.rs/// Note: this file only has basic declarations and some definitions in order to be possible to /// import it in the build script, to automate shell completion use clap::{Parser, ValueEnum}; use std::path::PathBuf; fn from_hex(hex: &str) -> Result<[u8; 3], String> { let chars = hex .chars() .filter(|&c| c.is_ascii_alphanumeric()) .map(|c| c.to_ascii_uppercase() as u8); if chars.clone().count() != 6 { return Err(format!( "expected 6 characters, found {}", chars.clone().count() )); } let mut color = [0, 0, 0]; for (i, c) in chars.enumerate() { match c { b'A'..=b'F' => color[i / 2] += c - b'A' + 10, b'0'..=b'9' => color[i / 2] += c - b'0', _ => { return Err(format!( "expected [0-9], [a-f], or [A-F], found '{}'", char::from(c) )) } } if i % 2 == 0 { color[i / 2] *= 16; } } Ok(color) } #[derive(Clone, ValueEnum)] pub enum PixelFormat { /// No swap, can copy directly onto WlBuffer Bgr, /// Swap R and B channels at client, can copy directly onto WlBuffer Rgb, /// No swap, must extend pixel with an extra byte when copying Xbgr, /// Swap R and B channels at client, must extend pixel with an extra byte when copying Xrgb, } #[derive(Clone)] pub enum Filter { Nearest, Bilinear, CatmullRom, Mitchell, Lanczos3, } impl std::str::FromStr for Filter { type Err = &'static str; fn from_str(s: &str) -> Result<Self, Self::Err> { match s { "Nearest" => Ok(Self::Nearest), "Bilinear" => Ok(Self::Bilinear), "CatmullRom" => Ok(Self::CatmullRom), "Mitchell" => Ok(Self::Mitchell), "Lanczos3" => Ok(Self::Lanczos3), _ => Err("unrecognized filter. Valid filters are:\ Nearest | Bilinear | CatmullRom | Mitchell | Lanczos3\ see swww img --help for more details"), } } } #[derive(Clone)] pub enum TransitionType { None, Simple, Fade, Left, Right, Top, Bottom, Center, Outer, Any, Random, Wipe, Wave, Grow, } impl std::str::FromStr for TransitionType { type Err = &'static str; fn from_str(s: &str) -> Result<Self, Self::Err> { match s { "none" => Ok(Self::None), "simple" => Ok(Self::Simple), "left" => Ok(Self::Left), "right" => Ok(Self::Right), "top" => Ok(Self::Top), "bottom" => Ok(Self::Bottom), "wipe" => Ok(Self::Wipe), "grow" => Ok(Self::Grow), "center" => Ok(Self::Center), "outer" => Ok(Self::Outer), "any" => Ok(Self::Any), "wave" => Ok(Self::Wave), "random" => Ok(Self::Random), "fade" => Ok(Self::Fade), _ => Err("unrecognized transition type.\nValid transitions are:\n\ \tsimple | fade | left | right | top | bottom | wipe | grow | center | outer | random | wave\n\ see swww img --help for more details"), } } } #[derive(Clone)] pub enum CliCoord { Percent(f32), Pixel(f32), } #[derive(Clone)] pub struct CliPosition { pub x: CliCoord, pub y: CliCoord, //Unknown(f32, f32), } impl CliPosition { pub fn new(x: CliCoord, y: CliCoord) -> Self { Self { x, y } } } #[derive(Parser)] #[command(version, name = "swww")] ///A Solution to your Wayland Wallpaper Woes /// ///Change what your monitors display as a background by controlling the swww daemon at runtime. ///Supports animated gifs and putting different stuff in different monitors. I also did my best to ///make it as resource efficient as possible. /// ///Note `swww` will only work in a compositor that implements the layer-shell protocol. Typically, ///wlr-roots based compositors. pub enum Swww { ///Fills the specified outputs with the given color. /// ///Defaults to filling all outputs with black. Clear(Clear), ///Restores the last displayed image on the specified outputs. Restore(Restore), ///Clears the swww cache. /// ///We currently store the address of the last file set as wallpaper for each monitor, as well ///as the animation frames of every gif ever set for a given version of `swww`. ClearCache, /// Sends an image (or animated gif) for the daemon to display. /// /// Use `-` to read from stdin Img(Img), /// [DEPRECATED] Initializes the daemon. /// /// Exits if there is already a daemon running. We check that by seeing if /// $XDG_RUNTIME_DIR/swww.socket exists. Init { ///Don't fork the daemon. This will keep it running in the current terminal. /// ///The only advantage of this would be seeing the logging real time. Note that for release ///builds we only log info, warnings and errors, so you won't be seeing much (ideally). #[clap(long)] no_daemon: bool, ///Don't load the cache *during initialization* (it still loads on monitor (re)connection) /// ///If want to always pass an image for `swww` to load, this option can help make the ///results some reliable: `swww init --no-cache && swww img <some img>` #[clap(long)] no_cache: bool, /// Force the daemon to use a specific wl_shm format /// /// IMPORTANT: make sure this is a value your compositor actually supports! `swww` will /// automatically select the best format for itself during initialization; this is only /// here for fallback, debug, and workaround purposes #[clap(long)] format: Option<PixelFormat>, }, ///Kills the daemon Kill, ///Asks the daemon to print output information (names and dimensions). /// ///You may use this to find out valid values for the <swww-img --outputs> option. If you want ///more detailed information about your outputs, I would recommend trying wlr-randr. Query, } #[derive(Parser)] pub struct Clear { /// Color to fill the screen with. /// /// Must be given in rrggbb format (note there is no prepended '#'). #[arg(value_parser = from_hex, default_value = "000000")] pub color: [u8; 3], /// Comma separated list of outputs to display the image at. /// /// If it isn't set, the image is displayed on all outputs. #[clap(short, long, default_value = "")] pub outputs: String, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)] pub enum ResizeStrategy { /// Do not resize the image /// /// If this is set, the image won't be resized, and will be centralized in the middle of the /// screen instead. If it is smaller than the screen's size, it will be padded with the value /// of `fill_color`, below. No, #[default] /// Resize the image to fill the whole screen, cropping out parts that don't fit Crop, /// Resize the image to fit inside the screen, preserving the original aspect ratio Fit, } #[derive(Parser)] pub struct Restore { /// Comma separated list of outputs to restore. /// /// If it isn't set, all outputs will be restored. #[arg(short, long, default_value = "")] pub outputs: String, } #[derive(Parser)] pub struct Img { /// Path to the image to display pub path: PathBuf, /// Comma separated list of outputs to display the image at. /// /// If it isn't set, the image is displayed on all outputs. #[arg(short, long, default_value = "")] pub outputs: String, /// Do not resize the image. Equivalent to `--resize=no` /// /// If this is set, the image won't be resized, and will be centralized in the middle of the /// screen instead. If it is smaller than the screen's size, it will be padded with the value /// of `fill_color`, below. #[deprecated(since = "0.7.3", note = "use `resize` instead")] #[arg(long)] pub no_resize: bool, /// Whether to resize the image and the method by which to resize it #[arg( long, default_value = "crop", default_value_if("no_resize", "true", "no") )] pub resize: ResizeStrategy, /// Which color to fill the padding with when output image does not fill screen #[arg(value_parser = from_hex, long, default_value = "000000")] pub fill_color: [u8; 3], ///Filter to use when scaling images (run swww img --help to see options). /// ///Available options are: /// ///Nearest | Bilinear | CatmullRom | Mitchell | Lanczos3 /// ///These are offered by the fast_image_resize crate ///(https://docs.rs/fast_image_resize/2.5.0/fast_image_resize/). 'Nearest' is ///what I recommend for pixel art stuff, and ONLY for pixel art stuff. It is also the ///fastest filter. /// ///For non pixel art stuff, I would usually recommend one of the last three, though some ///experimentation will be necessary to see which one you like best. Also note they are ///all slower than Nearest. #[arg(short, long, default_value = "Lanczos3")] pub filter: Filter, ///Sets the type of transition. Default is 'simple', that fades into the new image /// ///Possible transitions are: /// ///none | simple | fade | left | right | top | bottom | wipe | wave | grow | center | any | outer | random /// ///The 'left', 'right', 'top' and 'bottom' options make the transition happen from that ///position to its opposite in the screen. /// ///'none' is an alias to 'simple' that also sets the 'transition-step' to 255. This has the ///effect of the transition finishing instantly /// ///'fade' is similar to 'simple' but the fade is controlled through the --transition-bezier flag /// ///'wipe' is similar to 'left' but allows you to specify the angle for transition with the `--transition-angle` flag. /// ///'wave' is similar to 'wipe' sweeping line is wavy /// ///'grow' causes a growing circle to transition across the screen and allows changing the circle's center /// position with the `--transition-pos` flag. /// ///'center' is an alias to 'grow' with position set to center of screen. /// ///'any' is an alias to 'grow' with position set to a random point on screen. /// ///'outer' is the same as grow but the circle shrinks instead of growing. /// ///Finally, 'random' will select a transition effect at random #[arg(short, long, env = "SWWW_TRANSITION", default_value = "simple")] pub transition_type: TransitionType, ///How fast the transition approaches the new image. /// ///The transition logic works by adding or subtracting from the current rgb values until the ///old image transforms in the new one. This controls by how much we add or subtract. /// ///Larger values will make the transition faster, but more abrupt. A value of 255 will always ///switch to the new image immediately. /// /// This defaults to 2 when transition-type is 'simple', and 90 otherwise #[arg( long, env = "SWWW_TRANSITION_STEP", default_value = "90", default_value_if("transition_type", "simple", "2") )] pub transition_step: std::num::NonZeroU8, ///How long the transition takes to complete in seconds. /// ///Note that this doesn't work with the 'simple' transition #[arg(long, env = "SWWW_TRANSITION_DURATION", default_value = "3")] pub transition_duration: f32, ///Frame rate for the transition effect. /// ///Note there is no point in setting this to a value smaller than what your monitor supports. /// ///Also note this is **different** from the transition-step. That one controls by how much we ///approach the new image every frame. #[arg(long, env = "SWWW_TRANSITION_FPS", default_value = "30")] pub transition_fps: u16, ///This is used for the 'wipe' and 'wave' transitions. It controls the angle of the wipe /// ///Note that the angle is in degrees, where '0' is right to left and '90' is top to bottom, and '270' bottom to top #[arg(long, env = "SWWW_TRANSITION_ANGLE", default_value = "45")] pub transition_angle: f64, ///This is only used for the 'grow','outer' transitions. It controls the center of circle (default is 'center'). /// ///Position values can be given in both percentage values and pixel values: /// float values are interpreted as percentages and integer values as pixel values /// eg: 0.5,0.5 means 50% of the screen width and 50% of the screen height /// 200,400 means 200 pixels from the left and 400 pixels from the bottom /// ///the value can also be an alias which will set the position accordingly): /// 'center' | 'top' | 'left' | 'right' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' #[arg(long, env = "SWWW_TRANSITION_POS", default_value = "center", value_parser=parse_coords)] pub transition_pos: CliPosition, /// inverts the y position sent in 'transiiton_pos' flag #[arg(long, env = "INVERT_Y", default_value = "false")] pub invert_y: bool, ///bezier curve to use for the transition ///https://cubic-bezier.com is a good website to get these values from /// ///eg: 0.0,0.0,1.0,1.0 for linear animation #[arg(long, env = "SWWW_TRANSITION_BEZIER", default_value = ".54,0,.34,.99", value_parser = parse_bezier)] pub transition_bezier: (f32, f32, f32, f32), ///currently only used for 'wave' transition to control the width and height of each wave #[arg(long, env = "SWWW_TRANSITION_WAVE", default_value = "20,20", value_parser = parse_wave)] pub transition_wave: (f32, f32), } fn parse_wave(raw: &str) -> Result<(f32, f32), String> { let mut iter = raw.split(','); let mut parse = || { iter.next() .ok_or_else(|| "Not enough values".to_string()) .and_then(|s| s.parse::<f32>().map_err(|e| e.to_string())) }; let parsed = (parse()?, parse()?); Ok(parsed) } fn parse_bezier(raw: &str) -> Result<(f32, f32, f32, f32), String> { let mut iter = raw.split(','); let mut parse = || { iter.next() .ok_or_else(|| "Not enough values".to_string()) .and_then(|s| s.parse::<f32>().map_err(|e| e.to_string())) }; let parsed = (parse()?, parse()?, parse()?, parse()?); if parsed == (0.0, 0.0, 0.0, 0.0) { return Err("Invalid bezier curve: 0,0,0,0 (try using 0,0,1,1 instead)".to_string()); } Ok(parsed) } // parses Percents and numbers in format of "<coord1>,<coord2>" fn parse_coords(raw: &str) -> Result<CliPosition, String> { let coords = raw.split(',').map(|s| s.trim()).collect::<Vec<&str>>(); if coords.len() != 2 { match coords[0] { "center" => { return Ok(CliPosition::new( CliCoord::Percent(0.5), CliCoord::Percent(0.5), )); } "top" => { return Ok(CliPosition::new( CliCoord::Percent(0.5), CliCoord::Percent(1.0), )); } "bottom" => { return Ok(CliPosition::new( CliCoord::Percent(0.5), CliCoord::Percent(0.0), )); } "left" => { return Ok(CliPosition::new( CliCoord::Percent(0.0), CliCoord::Percent(0.5), )); } "right" => { return Ok(CliPosition::new( CliCoord::Percent(1.0), CliCoord::Percent(0.5), )); } "top-left" => { return Ok(CliPosition::new( CliCoord::Percent(0.0), CliCoord::Percent(1.0), )); } "top-right" => { return Ok(CliPosition::new( CliCoord::Percent(1.0), CliCoord::Percent(1.0), )); } "bottom-left" => { return Ok(CliPosition::new( CliCoord::Percent(0.0), CliCoord::Percent(0.0), )); } "bottom-right" => { return Ok(CliPosition::new( CliCoord::Percent(1.0), CliCoord::Percent(0.0), )); } _ => return Err(format!("Invalid position keyword: {raw}")), } } let x = coords[0]; let y = coords[1]; let parsed_x = match x.parse::<u32>() { Ok(x) => CliCoord::Pixel(x as f32), Err(_) => match x.parse::<f32>() { Ok(x) => CliCoord::Percent(x), Err(_) => return Err(format!("Invalid x coord: {x}")), }, }; let parsed_y = match y.parse::<u32>() { Ok(y) => CliCoord::Pixel(y as f32), Err(_) => match y.parse::<f32>() { Ok(y) => CliCoord::Percent(y), Err(_) => return Err(format!("Invalid y coord: {y}")), }, }; Ok(CliPosition::new(parsed_x, parsed_y)) } #[cfg(test)] mod tests { use super::*; #[test] fn should_reject_wrong_colors() { assert!( from_hex("0012231").is_err(), "function is accepting strings with more than 6 chars" ); assert!( from_hex("00122").is_err(), "function is accepting strings with less than 6 chars" ); assert!( from_hex("00r223").is_err(), "function is accepting strings with chars that aren't hex" ); } #[test] fn should_convert_colors_from_hex() { let color = from_hex("101010").unwrap(); assert_eq!(color, [16, 16, 16]); let color = from_hex("ffffff").unwrap(); assert_eq!(color, [255, 255, 255]); let color = from_hex("000000").unwrap(); assert_eq!(color, [0, 0, 0]); } } 07070130196BC5000081A40000000000000000000000016634268300004734000000080000000100000000000000000000001A00000000swww-0.9.5/src/imgproc.rsuse fast_image_resize::{FilterType, PixelType, Resizer}; use image::{ codecs::{gif::GifDecoder, png::PngDecoder, webp::WebPDecoder}, AnimationDecoder, DynamicImage, Frames, GenericImageView, ImageFormat, }; use std::{ io::{stdin, Cursor, Read}, num::NonZeroU32, path::Path, time::Duration, }; use utils::{ compression::{BitPack, Compressor}, ipc::{self, Coord, PixelFormat, Position}, }; use crate::cli::ResizeStrategy; use super::cli; pub struct ImgBuf { bytes: Box<[u8]>, format: ImageFormat, is_animated: bool, } impl ImgBuf { /// Create a new ImgBuf from a given path. Use - for Stdin pub fn new(path: &Path) -> Result<Self, String> { let bytes = if let Some("-") = path.to_str() { let mut bytes = Vec::new(); stdin() .read_to_end(&mut bytes) .map_err(|e| format!("failed to read standard input: {e}"))?; bytes } else { std::fs::read(path).map_err(|e| format!("failed to read file: {e}"))? }; let reader = image::io::Reader::new(Cursor::new(&bytes)) .with_guessed_format() .map_err(|e| format!("failed to detect the image's format: {e}"))?; let format = reader.format(); let is_animated = match format { Some(ImageFormat::Gif) => true, Some(ImageFormat::WebP) => WebPDecoder::new(Cursor::new(&bytes)) .map_err(|e| format!("failed to decode Webp Image: {e}"))? .has_animation(), Some(ImageFormat::Png) => PngDecoder::new(Cursor::new(&bytes)) .map_err(|e| format!("failed to decode Png Image: {e}"))? .is_apng() .map_err(|e| format!("failed to detect if Png is animated: {e}"))?, None => return Err("Unknown image format".to_string()), _ => false, }; Ok(Self { format: format.unwrap(), // this is ok because we return err earlier if it is None bytes: bytes.into_boxed_slice(), is_animated, }) } #[inline] pub fn is_animated(&self) -> bool { self.is_animated } /// Decode the ImgBuf into am RgbImage pub fn decode(&self, format: PixelFormat) -> Result<Image, String> { let mut reader = image::io::Reader::new(Cursor::new(&self.bytes)); reader.set_format(self.format); let dynimage = reader .decode() .map_err(|e| format!("failed to decode image: {e}"))?; let width = dynimage.width(); let height = dynimage.height(); let bytes = { let mut img = if format.channels() == 3 { dynimage.into_rgb8().into_raw().into_boxed_slice() } else { dynimage.into_rgba8().into_raw().into_boxed_slice() }; if format.must_swap_r_and_b_channels() { for pixel in img.chunks_exact_mut(format.channels() as usize) { pixel.swap(0, 2); } } img }; Ok(Image { width, height, bytes, format, }) } /// Convert this ImgBuf into Frames pub fn as_frames(&self) -> Result<Frames, String> { match self.format { ImageFormat::Gif => Ok(GifDecoder::new(Cursor::new(&self.bytes)) .map_err(|e| format!("failed to decode gif during animation: {e}"))? .into_frames()), ImageFormat::WebP => Ok(WebPDecoder::new(Cursor::new(&self.bytes)) .map_err(|e| format!("failed to decode webp during animation: {e}"))? .into_frames()), ImageFormat::Png => Ok(PngDecoder::new(Cursor::new(&self.bytes)) .map_err(|e| format!("failed to decode png during animation: {e}"))? .apng() .unwrap() // we detected this earlier .into_frames()), _ => Err(format!( "requested format has no decoder: {:#?}", self.format )), } } } /// Created by decoding an ImgBuf pub struct Image { width: u32, height: u32, format: PixelFormat, bytes: Box<[u8]>, } impl Image { #[must_use] fn crop(&self, x: u32, y: u32, width: u32, height: u32) -> Self { // make sure we don't crop a region larger than the image let x = x.min(self.width) as usize; let y = y.min(self.height) as usize; let width = (width as usize).min(self.width as usize - x); let height = (height as usize).min(self.height as usize - y); let mut bytes = Vec::with_capacity(width * height * self.format.channels() as usize); let begin = ((y * self.width as usize) + x) * self.format.channels() as usize; let stride = self.width as usize * self.format.channels() as usize; let row_size = width * self.format.channels() as usize; for row_index in 0..height { let row = begin + row_index * stride; bytes.extend_from_slice(&self.bytes[row..row + row_size]); } Self { width: width as u32, height: height as u32, bytes: bytes.into_boxed_slice(), format: self.format, } } fn from_frame(frame: image::Frame, format: PixelFormat) -> Self { let dynimage = DynamicImage::ImageRgba8(frame.into_buffer()); let (width, height) = dynimage.dimensions(); // NOTE: when animating frames, we ALWAYS use 3 channels let format = match format { PixelFormat::Bgr | PixelFormat::Xbgr => PixelFormat::Bgr, PixelFormat::Rgb | PixelFormat::Xrgb => PixelFormat::Rgb, }; let mut bytes = dynimage.into_rgb8().into_raw().into_boxed_slice(); if format.must_swap_r_and_b_channels() { for pixel in bytes.chunks_exact_mut(3) { pixel.swap(0, 2); } } Self { width, height, format, bytes, } } } pub fn compress_frames( mut frames: Frames, dim: (u32, u32), format: PixelFormat, filter: FilterType, resize: ResizeStrategy, color: &[u8; 3], ) -> Result<Vec<(BitPack, Duration)>, String> { let mut compressor = Compressor::new(); let mut compressed_frames = Vec::new(); // The first frame should always exist let first = frames.next().unwrap().unwrap(); let first_duration = first.delay().numer_denom_ms(); let mut first_duration = Duration::from_millis((first_duration.0 / first_duration.1).into()); let first_img = Image::from_frame(first, format); let first_img = match resize { ResizeStrategy::No => img_pad(&first_img, dim, color)?, ResizeStrategy::Crop => img_resize_crop(&first_img, dim, filter)?, ResizeStrategy::Fit => img_resize_fit(&first_img, dim, filter, color)?, }; let mut canvas: Option<Box<[u8]>> = None; while let Some(Ok(frame)) = frames.next() { let (dur_num, dur_div) = frame.delay().numer_denom_ms(); let duration = Duration::from_millis((dur_num / dur_div).into()); let img = Image::from_frame(frame, format); let img = match resize { ResizeStrategy::No => img_pad(&img, dim, color)?, ResizeStrategy::Crop => img_resize_crop(&img, dim, filter)?, ResizeStrategy::Fit => img_resize_fit(&img, dim, filter, color)?, }; if let Some(canvas) = canvas.as_ref() { match compressor.compress(canvas, &img, format) { Some(bytes) => compressed_frames.push((bytes, duration)), None => match compressed_frames.last_mut() { Some(last) => last.1 += duration, None => first_duration += duration, }, } } else { match compressor.compress(&first_img, &img, format) { Some(bytes) => compressed_frames.push((bytes, duration)), None => first_duration += duration, } } canvas = Some(img); } //Add the first frame we got earlier: if let Some(canvas) = canvas.as_ref() { match compressor.compress(canvas, &first_img, format) { Some(bytes) => compressed_frames.push((bytes, first_duration)), None => match compressed_frames.last_mut() { Some(last) => last.1 += first_duration, None => first_duration += first_duration, }, } } Ok(compressed_frames) } pub fn make_filter(filter: &cli::Filter) -> fast_image_resize::FilterType { match filter { cli::Filter::Nearest => fast_image_resize::FilterType::Box, cli::Filter::Bilinear => fast_image_resize::FilterType::Bilinear, cli::Filter::CatmullRom => fast_image_resize::FilterType::CatmullRom, cli::Filter::Mitchell => fast_image_resize::FilterType::Mitchell, cli::Filter::Lanczos3 => fast_image_resize::FilterType::Lanczos3, } } pub fn img_pad(img: &Image, dimensions: (u32, u32), color: &[u8; 3]) -> Result<Box<[u8]>, String> { let channels = img.format.channels() as usize; let mut color3 = color.to_owned(); let mut color4 = [color[0], color[1], color[2], 255]; let color: &mut [u8] = if channels == 3 { &mut color3 } else { &mut color4 }; if img.format.must_swap_r_and_b_channels() { color.swap(0, 2); } let (padded_w, padded_h) = dimensions; let (padded_w, padded_h) = (padded_w as usize, padded_h as usize); let mut padded = Vec::with_capacity(padded_h * padded_w * channels); let img = if img.width > dimensions.0 || img.height > dimensions.1 { let left = (img.width - dimensions.0) / 2; let top = (img.height - dimensions.1) / 2; img.crop(left, top, dimensions.0, dimensions.1) } else { img.crop(0, 0, dimensions.0, dimensions.1) }; let (img_w, img_h) = ( (img.width as usize).min(padded_w), (img.height as usize).min(padded_h), ); for _ in 0..(((padded_h - img_h) / 2) * padded_w) { padded.extend_from_slice(color); } // Calculate left and right border widths. `u32::div` rounds toward 0, so, if `img_w` is odd, // add an extra pixel to the right border to ensure the row is the correct width. let left_border_w = (padded_w - img_w) / 2; let right_border_w = left_border_w + (img_w % 2); for row in 0..img_h { for _ in 0..left_border_w { padded.extend_from_slice(color); } padded.extend_from_slice( &img.bytes[(row * img_w * channels)..((row + 1) * img_w * channels)], ); for _ in 0..right_border_w { padded.extend_from_slice(color); } } while padded.len() < (padded_h * padded_w * channels) { padded.extend_from_slice(color); } Ok(padded.into_boxed_slice()) } /// Resize an image to fit within the given dimensions, covering as much space as possible without /// cropping. pub fn img_resize_fit( img: &Image, dimensions: (u32, u32), filter: FilterType, padding_color: &[u8; 3], ) -> Result<Box<[u8]>, String> { let (width, height) = dimensions; if (img.width, img.height) != (width, height) { // if our image is already scaled to fit, skip resizing it and just pad it directly if img.width == width || img.height == height { return img_pad(img, dimensions, padding_color); } let ratio = width as f32 / height as f32; let img_r = img.width as f32 / img.height as f32; let (trg_w, trg_h) = if ratio > img_r { let scale = height as f32 / img.height as f32; ((img.width as f32 * scale) as u32, height) } else { let scale = width as f32 / img.width as f32; (width, (img.height as f32 * scale) as u32) }; let pixel_type = if img.format.channels() == 3 { PixelType::U8x3 } else { PixelType::U8x4 }; let src = match fast_image_resize::Image::from_vec_u8( // We unwrap below because we know the images's dimensions should never be 0 NonZeroU32::new(img.width).unwrap(), NonZeroU32::new(img.height).unwrap(), img.bytes.to_vec(), pixel_type, ) { Ok(i) => i, Err(e) => return Err(e.to_string()), }; // We unwrap below because we know the outputs's dimensions should never be 0 let new_w = NonZeroU32::new(trg_w).unwrap(); let new_h = NonZeroU32::new(trg_h).unwrap(); let mut dst = fast_image_resize::Image::new(new_w, new_h, pixel_type); let mut dst_view = dst.view_mut(); let mut resizer = Resizer::new(fast_image_resize::ResizeAlg::Convolution(filter)); if let Err(e) = resizer.resize(&src.view(), &mut dst_view) { return Err(e.to_string()); } let img = Image { width: trg_w, height: trg_h, format: img.format, bytes: dst.into_vec().into_boxed_slice(), }; img_pad(&img, dimensions, padding_color) } else { Ok(img.bytes.clone()) } } pub fn img_resize_crop( img: &Image, dimensions: (u32, u32), filter: FilterType, ) -> Result<Box<[u8]>, String> { let (width, height) = dimensions; let resized_img = if (img.width, img.height) != (width, height) { let pixel_type = if img.format.channels() == 3 { PixelType::U8x3 } else { PixelType::U8x4 }; let src = match fast_image_resize::Image::from_vec_u8( // We unwrap below because we know the images's dimensions should never be 0 NonZeroU32::new(img.width).unwrap(), NonZeroU32::new(img.height).unwrap(), img.bytes.to_vec(), pixel_type, ) { Ok(i) => i, Err(e) => return Err(e.to_string()), }; // We unwrap below because we know the outputs's dimensions should never be 0 let new_w = NonZeroU32::new(width).unwrap(); let new_h = NonZeroU32::new(height).unwrap(); let mut src_view = src.view(); src_view.set_crop_box_to_fit_dst_size(new_w, new_h, Some((0.5, 0.5))); let mut dst = fast_image_resize::Image::new(new_w, new_h, pixel_type); let mut dst_view = dst.view_mut(); let mut resizer = Resizer::new(fast_image_resize::ResizeAlg::Convolution(filter)); if let Err(e) = resizer.resize(&src_view, &mut dst_view) { return Err(e.to_string()); } dst.into_vec().into_boxed_slice() } else { img.bytes.clone() }; Ok(resized_img) } pub fn make_transition(img: &cli::Img) -> ipc::Transition { let mut angle = img.transition_angle; let step = img.transition_step; let x = match img.transition_pos.x { cli::CliCoord::Percent(x) => { if !(0.0..=1.0).contains(&x) { println!( "Warning: x value not in range [0,1] position might be set outside screen: {x}" ); } Coord::Percent(x) } cli::CliCoord::Pixel(x) => Coord::Pixel(x), }; let y = match img.transition_pos.y { cli::CliCoord::Percent(y) => { if !(0.0..=1.0).contains(&y) { println!( "Warning: y value not in range [0,1] position might be set outside screen: {y}" ); } Coord::Percent(y) } cli::CliCoord::Pixel(y) => Coord::Pixel(y), }; let mut pos = Position::new(x, y); let transition_type = match img.transition_type { cli::TransitionType::None => ipc::TransitionType::None, cli::TransitionType::Simple => ipc::TransitionType::Simple, cli::TransitionType::Fade => ipc::TransitionType::Fade, cli::TransitionType::Wipe => ipc::TransitionType::Wipe, cli::TransitionType::Outer => ipc::TransitionType::Outer, cli::TransitionType::Grow => ipc::TransitionType::Grow, cli::TransitionType::Wave => ipc::TransitionType::Wave, cli::TransitionType::Right => { angle = 0.0; ipc::TransitionType::Wipe } cli::TransitionType::Top => { angle = 90.0; ipc::TransitionType::Wipe } cli::TransitionType::Left => { angle = 180.0; ipc::TransitionType::Wipe } cli::TransitionType::Bottom => { angle = 270.0; ipc::TransitionType::Wipe } cli::TransitionType::Center => { pos = Position::new(Coord::Percent(0.5), Coord::Percent(0.5)); ipc::TransitionType::Grow } cli::TransitionType::Any => { pos = Position::new( Coord::Percent(rand::random::<f32>()), Coord::Percent(rand::random::<f32>()), ); if rand::random::<u8>() % 2 == 0 { ipc::TransitionType::Grow } else { ipc::TransitionType::Outer } } cli::TransitionType::Random => { pos = Position::new( Coord::Percent(rand::random::<f32>()), Coord::Percent(rand::random::<f32>()), ); angle = rand::random(); match rand::random::<u8>() % 4 { 0 => ipc::TransitionType::Simple, 1 => ipc::TransitionType::Wipe, 2 => ipc::TransitionType::Outer, 3 => ipc::TransitionType::Grow, _ => unreachable!(), } } }; ipc::Transition { duration: img.transition_duration, step, fps: img.transition_fps, bezier: img.transition_bezier, angle, pos, transition_type, wave: img.transition_wave, invert_y: img.invert_y, } } 07070130196BC7000081A40000000000000000000000016634268300003537000000080000000100000000000000000000001700000000swww-0.9.5/src/main.rsuse clap::Parser; use std::{path::PathBuf, process::Stdio, time::Duration}; use utils::{ cache, ipc::{ self, connect_to_socket, get_socket_path, read_socket, AnimationRequest, Answer, Request, }, }; mod imgproc; use imgproc::*; mod cli; use cli::{ResizeStrategy, Swww}; fn main() -> Result<(), String> { let swww = Swww::parse(); if let Swww::Init { no_daemon, format, .. } = &swww { eprintln!( "DEPRECATION WARNING: `swww init` IS DEPRECATED. Call `swww-daemon` directly instead" ); match is_daemon_running() { Ok(false) => { let socket_path = get_socket_path(); if socket_path.exists() { eprintln!( "WARNING: socket file {} was not deleted when the previous daemon exited", socket_path.to_string_lossy() ); if let Err(e) = std::fs::remove_file(socket_path) { return Err(format!("failed to delete previous socket: {e}")); } } } Ok(true) => { return Err("There seems to already be another instance running...".to_string()) } Err(e) => { eprintln!("WARNING: failed to read '/proc' directory to determine whether the daemon is running: {e} Falling back to trying to checking if the socket file exists..."); let socket_path = get_socket_path(); if socket_path.exists() { return Err(format!( "Found socket at {}. There seems to be an instance already running...", socket_path.to_string_lossy() )); } } } spawn_daemon(*no_daemon, format)?; if *no_daemon { return Ok(()); } } if let Swww::ClearCache = &swww { return cache::clean(); } loop { let socket = connect_to_socket(&get_socket_path(), 5, 100)?; Request::Ping.send(&socket)?; let bytes = read_socket(&socket)?; let answer = Answer::receive(&bytes); if let Answer::Ping(configured) = answer { if configured { break; } } else { return Err("Daemon did not return Answer::Ping, as expected".to_string()); } std::thread::sleep(Duration::from_millis(1)); } process_swww_args(&swww) } fn process_swww_args(args: &Swww) -> Result<(), String> { let request = match make_request(args)? { Some(request) => request, None => return Ok(()), }; let socket = connect_to_socket(&get_socket_path(), 5, 100)?; request.send(&socket)?; let bytes = read_socket(&socket)?; drop(socket); match Answer::receive(&bytes) { Answer::Err(msg) => return Err(msg.to_string()), Answer::Info(info) => info.iter().for_each(|i| println!("{}", i)), Answer::Ok => { if let Swww::Kill = args { #[cfg(debug_assertions)] let tries = 20; #[cfg(not(debug_assertions))] let tries = 10; let socket_path = get_socket_path(); for _ in 0..tries { if !socket_path.exists() { return Ok(()); } std::thread::sleep(Duration::from_millis(100)); } return Err(format!( "Could not confirm socket deletion at: {socket_path:?}" )); } } Answer::Ping(_) => { return Ok(()); } } Ok(()) } fn make_request(args: &Swww) -> Result<Option<Request>, String> { match args { Swww::Clear(c) => { let (format, _, _) = get_format_dims_and_outputs(&[])?; let mut color = c.color; if format.must_swap_r_and_b_channels() { color.swap(0, 2); } Ok(Some(Request::Clear(ipc::Clear { color, outputs: split_cmdline_outputs(&c.outputs), }))) } Swww::Restore(restore) => { let requested_outputs = split_cmdline_outputs(&restore.outputs); restore_from_cache(&requested_outputs)?; Ok(None) } Swww::ClearCache => unreachable!("there is no request for clear-cache"), Swww::Img(img) => { let requested_outputs = split_cmdline_outputs(&img.outputs); let (format, dims, outputs) = get_format_dims_and_outputs(&requested_outputs)?; let imgbuf = ImgBuf::new(&img.path)?; if imgbuf.is_animated() { let animations = { let first_frame = imgbuf.decode(format)?; let img_request = make_img_request(img, first_frame, &dims, &outputs)?; let animations = make_animation_request(img, &imgbuf, &dims, format, &outputs); let socket = connect_to_socket(&get_socket_path(), 5, 100)?; Request::Img(img_request).send(&socket)?; let bytes = read_socket(&socket)?; drop(socket); if let Answer::Err(e) = Answer::receive(&bytes) { return Err(format!("daemon error when sending image: {e}")); } animations } .map_err(|e| format!("failed to create animated request: {e}"))?; Ok(Some(Request::Animation(animations))) } else { let img_raw = imgbuf.decode(format)?; Ok(Some(Request::Img(make_img_request( img, img_raw, &dims, &outputs, )?))) } } Swww::Init { no_cache, .. } => { if !*no_cache { restore_from_cache(&[])?; } Ok(None) } Swww::Kill => Ok(Some(Request::Kill)), Swww::Query => Ok(Some(Request::Query)), } } fn make_img_request( img: &cli::Img, img_raw: Image, dims: &[(u32, u32)], outputs: &[Vec<String>], ) -> Result<ipc::ImageRequest, String> { let transition = make_transition(img); let mut img_req_builder = ipc::ImageRequestBuilder::new(transition); for (dim, outputs) in dims.iter().zip(outputs) { let path = match img.path.canonicalize() { Ok(p) => p.to_string_lossy().to_string(), Err(e) => { if let Some("-") = img.path.to_str() { "STDIN".to_string() } else { return Err(format!("failed no canonicalize image path: {e}")); } } }; let img = match img.resize { ResizeStrategy::No => img_pad(&img_raw, *dim, &img.fill_color)?, ResizeStrategy::Crop => img_resize_crop(&img_raw, *dim, make_filter(&img.filter))?, ResizeStrategy::Fit => { img_resize_fit(&img_raw, *dim, make_filter(&img.filter), &img.fill_color)? } }; img_req_builder.push( ipc::Img { img, path }, outputs.to_owned().into_boxed_slice(), ); } Ok(img_req_builder.build()) } #[allow(clippy::type_complexity)] fn get_format_dims_and_outputs( requested_outputs: &[String], ) -> Result<(ipc::PixelFormat, Vec<(u32, u32)>, Vec<Vec<String>>), String> { let mut outputs: Vec<Vec<String>> = Vec::new(); let mut dims: Vec<(u32, u32)> = Vec::new(); let mut imgs: Vec<ipc::BgImg> = Vec::new(); let socket = connect_to_socket(&get_socket_path(), 5, 100)?; Request::Query.send(&socket)?; let bytes = read_socket(&socket)?; drop(socket); let answer = Answer::receive(&bytes); match answer { Answer::Info(infos) => { let mut format = ipc::PixelFormat::Xrgb; for info in infos.iter() { format = info.pixel_format; let info_img = &info.img; let name = info.name.to_string(); if !requested_outputs.is_empty() && !requested_outputs.contains(&name) { continue; } let real_dim = info.real_dim(); if let Some((_, output)) = dims .iter_mut() .zip(&imgs) .zip(&mut outputs) .find(|((dim, img), _)| real_dim == **dim && info_img == *img) { output.push(name); } else { outputs.push(vec![name]); dims.push(real_dim); imgs.push(info_img.clone()); } } if outputs.is_empty() { Err("none of the requested outputs are valid".to_owned()) } else { Ok((format, dims, outputs)) } } Answer::Err(e) => Err(format!("daemon error when sending query: {e}")), _ => unreachable!(), } } fn make_animation_request( img: &cli::Img, imgbuf: &ImgBuf, dims: &[(u32, u32)], pixel_format: ipc::PixelFormat, outputs: &[Vec<String>], ) -> Result<AnimationRequest, String> { let mut anim_req_builder = ipc::AnimationRequestBuilder::new(); let filter = make_filter(&img.filter); let mut animations = Vec::with_capacity(dims.len()); for (dim, outputs) in dims.iter().zip(outputs) { // do not load cache if we are reading from stdin if let Some("-") = img.path.to_str() { //TODO: make cache work for all resize strategies if img.resize == ResizeStrategy::Crop { match cache::load_animation_frames(&img.path, *dim, pixel_format) { Ok(Some(animation)) => { animations.push((animation, outputs.to_owned().into_boxed_slice())); continue; } Ok(None) => (), Err(e) => eprintln!("Error loading cache for {:?}: {e}", img.path), } } } let animation = ipc::Animation { path: img.path.to_string_lossy().to_string(), dimensions: *dim, animation: compress_frames( imgbuf.as_frames()?, *dim, pixel_format, filter, img.resize, &img.fill_color, )? .into_boxed_slice(), pixel_format, }; anim_req_builder.push(animation, outputs.to_owned().into_boxed_slice()); } Ok(anim_req_builder.build()) } fn split_cmdline_outputs(outputs: &str) -> Box<[String]> { outputs .split(',') .map(|s| s.to_owned()) .filter(|s| !s.is_empty()) .collect() } fn spawn_daemon(no_daemon: bool, format: &Option<cli::PixelFormat>) -> Result<(), String> { let mut cmd = std::process::Command::new("swww-daemon"); if let Some(format) = format { cmd.arg("--format"); cmd.arg(match format { cli::PixelFormat::Xrgb => "xrgb", cli::PixelFormat::Xbgr => "xbgr", cli::PixelFormat::Rgb => "rgb", cli::PixelFormat::Bgr => "bgr", }); } if no_daemon { match cmd.status() { Ok(_) => Ok(()), Err(e) => Err(format!("error spawning swww-daemon: {e}")), } } else { match cmd.stdout(Stdio::null()).stderr(Stdio::null()).spawn() { Ok(_) => Ok(()), Err(e) => Err(format!("error spawning swww-daemon: {e}")), } } } fn is_daemon_running() -> Result<bool, String> { let socket = match connect_to_socket(&get_socket_path(), 5, 100) { Ok(s) => s, // likely a connection refused; either way, this is a reliable signal there's no surviving // daemon. Err(_) => return Ok(false), }; Request::Ping.send(&socket)?; let answer = Answer::receive(&read_socket(&socket)?); match answer { Answer::Ping(_) => Ok(true), _ => Err("Daemon did not return Answer::Ping, as expected".to_string()), } } fn restore_from_cache(requested_outputs: &[String]) -> Result<(), String> { let (_, _, outputs) = get_format_dims_and_outputs(requested_outputs)?; for output in outputs.iter().flatten() { let img_path = utils::cache::get_previous_image_path(output)?; #[allow(deprecated)] if let Err(e) = process_swww_args(&Swww::Img(cli::Img { path: PathBuf::from(img_path), outputs: output.to_string(), no_resize: false, resize: ResizeStrategy::Crop, fill_color: [0, 0, 0], filter: cli::Filter::Lanczos3, transition_type: cli::TransitionType::None, transition_step: std::num::NonZeroU8::MAX, transition_duration: 0.0, transition_fps: 30, transition_angle: 0.0, transition_pos: cli::CliPosition { x: cli::CliCoord::Pixel(0.0), y: cli::CliCoord::Pixel(0.0), }, invert_y: false, transition_bezier: (0.0, 0.0, 0.0, 0.0), transition_wave: (0.0, 0.0), })) { eprintln!("WARNING: failed to load cache for output {output}: {e}"); } } Ok(()) } 07070141FCBDAB000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000001100000000swww-0.9.5/tests07070141FCBDAC000081A4000000000000000000000001663426830000139A000000080000000100000000000000000000002000000000swww-0.9.5/tests/integration.rs//! These are relatively simple tests just to make sure no basic functionally //! was broken by anything. They are no substitute to actually trying to run //! the program yourself and seeing if anything broke (e.g. maybe images stopped //! rendering correctly, somehow, without the program breaking down) use assert_cmd::Command; use std::path::PathBuf; const TEST_IMG_DIR: &str = "test_images"; const TEST_IMGS: [&str; 3] = [ "test_images/test1.jpg", "test_images/test2.png", "test_images/test3.bmp", ]; fn make_img_dir() { let p = PathBuf::from(TEST_IMG_DIR); if !p.is_dir() { std::fs::create_dir(p) .expect("Failed to create directory to put the images used for testing: "); } } fn make_test_imgs() { make_img_dir(); for (i, test_img) in TEST_IMGS.iter().enumerate() { let p = PathBuf::from(test_img); if !p.is_file() { //We use i to create images of different dimensions, just to be more through let mut imgbuf = image::ImageBuffer::new(400 * (i as u32 + 1), 400 * (i as u32 + 1)); //This is taken straight from the image crate fractal example for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { let r = (0.3 * x as f32) as u8; let b = (0.3 * y as f32) as u8; *pixel = image::Rgb([r, 0, b]); } imgbuf .save(test_img) .expect("Failed to create image for testing: "); } } } fn cmd() -> Command { Command::cargo_bin("swww").unwrap() } fn start_daemon() -> Command { let mut cmd = Command::cargo_bin("swww-daemon").unwrap(); cmd.arg("--no-cache"); cmd } #[test] #[ignore] fn general_commands() { make_test_imgs(); init_daemon(); init_daemon_twice(); sending_imgs(); sending_img_that_does_not_exist(); sending_imgs_with_filter(); sending_img_with_filter_that_does_not_exist(); sending_img_from_stdin(); let output = query_outputs(); sending_img_to_individual_monitors(&output); sending_img_to_monitor_that_does_not_exist(); sending_img_with_custom_transition(); clear_outputs(); killing_daemon(); cmd().arg("query").assert().failure(); //daemon is dead, so this should fail } fn sending_imgs() { for img in TEST_IMGS { cmd().arg("img").arg(img).assert().success(); } } fn init_daemon() { std::thread::spawn(|| { start_daemon().assert().success(); }); // sleep for a bit to allow the daemon to init correctly // note that even though this is a race-condition, in the actual program we // have implemented some proper syncronization. And, in here, it is *very* // unlikely that this will ever be a problem, (and, if it is, it is not a // very big deal, it will merely cause init_daemon_twice to false-fail) std::thread::sleep(std::time::Duration::from_millis(1)); } /// Should fail since we already have an instance running fn init_daemon_twice() { start_daemon().assert().failure(); } fn sending_img_that_does_not_exist() { cmd().arg("img").arg("I don't exist").assert().failure(); } fn query_outputs() -> String { let output = cmd().arg("query").output().expect("Query failed!"); let stdout = String::from_utf8(output.stdout).unwrap(); stdout.split_once(':').unwrap().0.to_string() } fn sending_img_to_individual_monitors(output: &str) { cmd() .arg("img") .arg("-t") .arg("none") .arg(TEST_IMGS[0]) .arg("-o") .arg(output) .assert() .success(); } fn sending_img_to_monitor_that_does_not_exist() { cmd() .arg("img") .arg("-t") .arg("none") .arg(TEST_IMGS[0]) .arg("-o") .arg("AHOY") .assert() .failure(); } fn sending_imgs_with_filter() { for filter in ["Nearest", "Bilinear", "CatmullRom", "Mitchell", "Lanczos3"] { cmd() .arg("img") .arg("-t") .arg("none") .arg(TEST_IMGS[0]) .arg("-f") .arg(filter) .assert() .success(); } } fn sending_img_from_stdin() { cmd() .arg("img") .arg("-t") .arg("none") .arg("-") .pipe_stdin("test_images/test1.jpg") .expect("failed to pipe stdin") .assert() .success(); } fn sending_img_with_filter_that_does_not_exist() { cmd() .arg("img") .arg("-t") .arg("none") .arg(TEST_IMGS[0]) .arg("-f") .arg("AHOY") .assert() .failure(); } fn sending_img_with_custom_transition() { cmd() .arg("img") .arg("-t") .arg("none") .arg(TEST_IMGS[0]) .arg("--transition-step") .arg("200") .assert() .success(); } fn clear_outputs() { cmd().arg("clear").assert().success(); } fn killing_daemon() { cmd().arg("kill").assert().success(); } 07070141FCBDAD000081A4000000000000000000000001663426830000060D000000080000000100000000000000000000002000000000swww-0.9.5/tests/spell_check.rsuse std::process::Command; /// We ignore because people might not have codespell installed, and I don't want to force anyone to /// install codespell to e.g. run tests before installing swww. This may change in the future #[test] #[ignore] fn spell_check_code_and_man_pages() { // Make sure no docs were generated let _ = std::fs::remove_dir_all("doc/generated"); match Command::new("codespell") .args([ "--enable-colors", "--ignore-words-list", "crate,statics", "--skip", "doc/generated", // skip the generated documentation "src", // client "daemon/src", // daemon "utils/src", // common code "doc", // man pages "example_scripts", // scripts "CHANGELOG.md", "README.md", ]) .output() { Ok(output) => { if !output.status.success() { panic!( "\nstdout:{}\nstderr:{}\n", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); } } Err(e) => match e.kind() { std::io::ErrorKind::NotFound => { eprintln!( "'codespell' not found. Please install in order to do spell checking: `pip install codespell`" ); } _ => eprintln!("{e}"), }, } } 07070100189724000041ED0000000000000000000000046634268300000000000000080000000100000000000000000000001100000000swww-0.9.5/utils0707010018A8C6000081A40000000000000000000000016634268300000224000000080000000100000000000000000000001C00000000swww-0.9.5/utils/Cargo.toml[package] name = "utils" version = "0.9.5" authors = ["Leonardo Gibrowski Faé <leonardo.fae44@gmail.com>"] edition = "2021" license-file = "../LICENSE" [dependencies] # use specific git version for Duration implementation. We will do this until the next bitcode release bitcode = { git = "https://github.com/SoftbearStudios/bitcode.git", rev = "5f25a59", default-features = false, features = [ "derive" ]} [build-dependencies] pkg-config = "0.3" [dev-dependencies] rand = "0.8" criterion = "0.5" [[bench]] name = "compression" harness = false 07070110381EE4000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000001900000000swww-0.9.5/utils/benches07070110381EE7000081A400000000000000000000000166342683000007CF000000080000000100000000000000000000002800000000swww-0.9.5/utils/benches/compression.rsuse criterion::{black_box, criterion_group, criterion_main, Criterion}; use utils::compression::{Compressor, Decompressor}; fn generate_data() -> (Box<[u8]>, Box<[u8]>) { let v1 = vec![120; 1920 * 1080 * 3]; let mut v2 = v1.clone(); const REGIONS: usize = 2000; let diff_bytes: usize = v2.len() / (REGIONS + 1); // Make different regions for i in 0..REGIONS { // With 100 different bytes total for j in 0..10 { v2[i * diff_bytes + j] = 100; } for j in 10..30 { v2[i * diff_bytes + j] = 200; } for j in 30..60 { v2[i * diff_bytes + j] = 20; } for j in 60..100 { v2[i * diff_bytes + j] = 30; } } (v1.into_boxed_slice(), v2.into_boxed_slice()) } fn buf_from(slice: &[u8]) -> Vec<u8> { let mut v = Vec::new(); for pix in slice.chunks_exact(3) { v.extend_from_slice(pix); v.push(255); } v } pub fn compression_and_decompression(c: &mut Criterion) { let (prev, cur) = generate_data(); let mut compressor = Compressor::new(); let mut comp = c.benchmark_group("compression"); comp.bench_function("Full", |b| { b.iter(|| { black_box( compressor .compress(&prev, &cur, utils::ipc::PixelFormat::Xrgb) .is_some(), ) }) }); comp.finish(); let mut decomp = c.benchmark_group("decompression 4 channels"); let bitpack = compressor .compress(&prev, &cur, utils::ipc::PixelFormat::Xrgb) .unwrap(); let mut canvas = buf_from(&prev); let mut decompressor = Decompressor::new(); decomp.bench_function("Full", |b| { b.iter(|| { black_box(decompressor.decompress(&bitpack, &mut canvas, utils::ipc::PixelFormat::Xrgb)) }) }); decomp.finish(); } criterion_group!(compression, compression_and_decompression); criterion_main!(compression); 0707010018A8CB000081A40000000000000000000000016634268300000078000000080000000100000000000000000000001A00000000swww-0.9.5/utils/build.rsfn main() { pkg_config::Config::new() .atleast_version("1.8") .probe("liblz4") .unwrap(); } 070701201CC843000041ED0000000000000000000000036634268300000000000000080000000100000000000000000000001500000000swww-0.9.5/utils/src070701201CC844000081A400000000000000000000000166342683000018D2000000080000000100000000000000000000001E00000000swww-0.9.5/utils/src/cache.rs//! Implements basic cache functionality. //! //! The idea is: //! 1. the client registers the last image sent for each output in a file //! 2. the daemon spawns a client that reloads that image when an output is created use std::{ fs::File, io::{BufReader, BufWriter, Read, Write}, path::{Path, PathBuf}, }; use crate::ipc::{Animation, PixelFormat}; pub fn store(output_name: &str, img_path: &str) -> Result<(), String> { let mut filepath = cache_dir()?; filepath.push(output_name); let file = File::create(filepath).map_err(|e| e.to_string())?; let mut writer = BufWriter::new(file); writer .write_all(img_path.as_bytes()) .map_err(|e| format!("failed to write cache: {e}")) } pub fn store_animation_frames(animation: &Animation) -> Result<(), String> { let filename = animation_filename( &PathBuf::from(&animation.path), animation.dimensions, animation.pixel_format, ); let mut filepath = cache_dir()?; filepath.push(&filename); let bytes = bitcode::encode(animation); if !filepath.is_file() { let file = File::create(filepath).map_err(|e| e.to_string())?; let mut writer = BufWriter::new(file); writer .write_all(&bytes) .map_err(|e| format!("failed to write cache: {e}")) } else { Ok(()) } } pub fn load_animation_frames( path: &Path, dimensions: (u32, u32), pixel_format: PixelFormat, ) -> Result<Option<Animation>, String> { let filename = animation_filename(path, dimensions, pixel_format); let cache_dir = cache_dir()?; let mut filepath = cache_dir.clone(); filepath.push(filename); let read_dir = cache_dir .read_dir() .map_err(|e| format!("failed to read cache directory ({cache_dir:?}): {e}"))?; for entry in read_dir.into_iter().flatten() { if entry.path() == filepath { let file = File::open(&filepath).map_err(|e| e.to_string())?; let mut buf_reader = BufReader::new(file); let mut buf = Vec::new(); buf_reader .read_to_end(&mut buf) .map_err(|e| format!("failed to read file `{filepath:?}`: {e}"))?; let frames: Animation = bitcode::decode(&buf).expect("failed to decode cached animations"); return Ok(Some(frames)); } } Ok(None) } pub fn get_previous_image_path(output_name: &str) -> Result<String, String> { let mut filepath = cache_dir()?; clean_previous_verions(&filepath); filepath.push(output_name); if !filepath.is_file() { return Ok("".to_string()); } let file = std::fs::File::open(filepath).map_err(|e| format!("failed to open file: {e}"))?; let mut reader = BufReader::new(file); let mut buf = Vec::with_capacity(64); reader .read_to_end(&mut buf) .map_err(|e| format!("failed to read file: {e}"))?; String::from_utf8(buf).map_err(|e| format!("failed to decode bytes: {e}")) } pub fn load(output_name: &str) -> Result<(), String> { let img_path = get_previous_image_path(output_name)?; if img_path.is_empty() { return Ok(()); } if let Ok(mut child) = std::process::Command::new("pidof").arg("swww").spawn() { if let Ok(status) = child.wait() { if status.success() { return Err("there is already another swww process running".to_string()); } } } match std::process::Command::new("swww") .arg("img") .args([ &format!("--outputs={output_name}"), "--transition-type=none", &img_path, ]) .spawn() { Ok(mut child) => match child.wait() { Ok(_) => Ok(()), Err(e) => Err(format!("child process failed: {e}")), }, Err(e) => Err(format!("failed to spawn child process: {e}")), } } pub fn clean() -> Result<(), String> { std::fs::remove_dir_all(cache_dir()?) .map_err(|e| format!("failed to remove cache directory: {e}")) } fn clean_previous_verions(cache_dir: &Path) { let mut read_dir = match std::fs::read_dir(cache_dir) { Ok(read_dir) => read_dir, Err(_) => { eprintln!("WARNING: failed to read cache dir {:?} entries", cache_dir); return; } }; let current_version = env!("CARGO_PKG_VERSION"); while let Some(Ok(entry)) = read_dir.next() { let filename = entry.file_name(); let filename = match filename.to_str() { Some(filename) => filename, None => { eprintln!("WARNING: failed to read filename of {:?}", filename); continue; } }; // only the images we've cached will have a _v token, indicating their version if let Some(i) = filename.rfind("_v") { if &filename[i + 2..] != current_version { if let Err(e) = std::fs::remove_file(entry.path()) { eprintln!( "WARNING: failed to remove cache file {} of old swww version {:?}", filename, e ); } } } } } fn create_dir(p: &Path) -> Result<(), String> { if !p.is_dir() { if let Err(e) = std::fs::create_dir(p) { return Err(format!("failed to create directory({p:#?}): {e}")); } } Ok(()) } fn cache_dir() -> Result<PathBuf, String> { if let Ok(path) = std::env::var("XDG_CACHE_HOME") { let mut path: PathBuf = path.into(); path.push("swww"); create_dir(&path)?; Ok(path) } else if let Ok(path) = std::env::var("HOME") { let mut path: PathBuf = path.into(); path.push(".cache"); path.push("swww"); create_dir(&path)?; Ok(path) } else { Err("failed to read both $XDG_CACHE_HOME and $HOME environment variables".to_string()) } } #[must_use] fn animation_filename(path: &Path, dimensions: (u32, u32), pixel_format: PixelFormat) -> PathBuf { format!( "{}__{}x{}_{:?}_v{}", path.to_string_lossy().replace('/', "_"), dimensions.0, dimensions.1, pixel_format, env!("CARGO_PKG_VERSION"), ) .into() } 07070130196BC9000041ED0000000000000000000000046634268300000000000000080000000100000000000000000000002100000000swww-0.9.5/utils/src/compression07070141FCBDAE000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000002600000000swww-0.9.5/utils/src/compression/comp07070141FCBDAF000081A400000000000000000000000166342683000012E7000000080000000100000000000000000000002D00000000swww-0.9.5/utils/src/compression/comp/mod.rs//! # Compression Strategy //! //! We only compress RBG images, 8 bytes per channel; I don't think anyone will use //! transparency for a background (nor if it even makes sense) //! //! For what's left, we store only the difference from the last frame to this one. //! We do that as follows: //! * First, we count how many pixels didn't change. We store that value as a u8. //! Every time the u8 hits the max (i.e. 255, or 0xFF), we push in onto the vector //! and restart the counting. //! * Once we find a pixel that has changed, we count, starting from that one, how many //! changed, the same way we counted above (i.e. store as u8, every time it hits the //! max push and restart the counting) //! * Then, we store all the new bytes. //! * Start from the top until we are done with the image //! //! The default implementation lies in this file. Architecture-specific implementations //! that make use of specialized instructions lie in other submodules. #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub(super) mod sse2; /// # Safety /// /// s1.len() must be equal to s2.len() #[inline(always)] unsafe fn count_equals(s1: &[u8], s2: &[u8], mut i: usize) -> usize { let mut equals = 0; while i + 7 < s1.len() { // SAFETY: we exit the while loop when there are less than 8 bytes left we read let a: u64 = unsafe { s1.as_ptr().add(i).cast::<u64>().read_unaligned() }; let b: u64 = unsafe { s2.as_ptr().add(i).cast::<u64>().read_unaligned() }; let cmp = a ^ b; if cmp != 0 { equals += cmp.trailing_zeros() as usize / 24; return equals; } equals += 2; i += 6; } while i + 2 < s1.len() { // SAFETY: we exit the while loop when there are less than 3 bytes left we read let a = unsafe { s1.get_unchecked(i..i + 3) }; let b = unsafe { s2.get_unchecked(i..i + 3) }; if a != b { break; } equals += 1; i += 3; } equals } /// # Safety /// /// s1.len() must be equal to s2.len() #[inline(always)] unsafe fn count_different(s1: &[u8], s2: &[u8], mut i: usize) -> usize { let mut different = 0; while i + 2 < s1.len() { // SAFETY: we exit the while loop when there are less than 3 bytes left we read let a = unsafe { s1.get_unchecked(i..i + 3) }; let b = unsafe { s2.get_unchecked(i..i + 3) }; if a == b { break; } different += 1; i += 3; } different } /// This calculates the difference between the current(cur) frame and the next(goal) /// /// # Safety /// /// cur.len() must be equal to goal.len() #[inline(always)] pub(super) unsafe fn pack_bytes(cur: &[u8], goal: &[u8], v: &mut Vec<u8>) { // use the most efficient implementation available: #[cfg(not(test))] // when testing, we want to use the specific implementation { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] if super::cpu::features::sse2() { return unsafe { sse2::pack_bytes(cur, goal, v) }; } } let mut i = 0; while i < cur.len() { // SAFETY: count_equals demands the same invariants as the current function let equals = unsafe { count_equals(cur, goal, i) }; i += equals * 3; if i >= cur.len() { break; } let start = i; // SAFETY: count_different demands the same invariants as the current function let diffs = unsafe { count_different(cur, goal, i) }; i += diffs * 3; let j = v.len() + equals / 255; v.resize(1 + j + diffs / 255, 255); v[j] = (equals % 255) as u8; v.push((diffs % 255) as u8); v.extend_from_slice(unsafe { goal.get_unchecked(start..i) }); i += 3; } if !v.is_empty() { // add two extra bytes to prevent access out of bounds later during decompression v.push(0); v.push(0); } } #[cfg(test)] mod tests { // note the full compression -> decompression roundtrip is tested in super use super::*; #[test] fn count_equal_test() { let a = [0u8; 102]; assert_eq!(unsafe { count_equals(&a, &a, 0) }, 102 / 3); for i in [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] { let mut b = a; b[i] = 1; assert_eq!(unsafe { count_equals(&a, &b, 0) }, i / 3, "i: {i}"); } } #[test] fn count_diffs_test() { let a = [0u8; 102]; assert_eq!(unsafe { count_different(&a, &a, 0) }, 0,); for i in [10, 20, 30, 40, 50, 60, 70, 80, 90, 102] { let mut b = a; for x in &mut b[..i] { *x = 1; } assert_eq!(unsafe { count_different(&a, &b, 0) }, (i + 2) / 3, "i: {i}"); } } } 07070141FCBDB0000081A400000000000000000000000166342683000021F1000000080000000100000000000000000000002E00000000swww-0.9.5/utils/src/compression/comp/sse2.rs/// # Safety /// /// s1.len() must be equal to s2.len() #[inline] #[target_feature(enable = "sse2")] unsafe fn count_equals(s1: &[u8], s2: &[u8], mut i: usize) -> usize { #[cfg(target_arch = "x86")] use std::arch::x86 as intr; #[cfg(target_arch = "x86_64")] use std::arch::x86_64 as intr; let mut equals = 0; while i + 15 < s1.len() { // SAFETY: we exit the while loop when there are less than 16 bytes left we read let a = intr::_mm_loadu_si128(s1.as_ptr().add(i).cast()); let b = intr::_mm_loadu_si128(s2.as_ptr().add(i).cast()); let cmp = intr::_mm_cmpeq_epi8(a, b); let mask = intr::_mm_movemask_epi8(cmp); if mask != 0xFFFF { equals += mask.trailing_ones() as usize / 3; return equals; } equals += 5; i += 15; } while i + 2 < s1.len() { // SAFETY: we exit the while loop when there are less than 3 bytes left we read let a = unsafe { s1.get_unchecked(i..i + 3) }; let b = unsafe { s2.get_unchecked(i..i + 3) }; if a != b { break; } equals += 1; i += 3; } equals } /// # Safety /// /// s1.len() must be equal to s2.len() #[inline] #[target_feature(enable = "sse2")] unsafe fn count_different(s1: &[u8], s2: &[u8], mut i: usize) -> usize { #[cfg(target_arch = "x86")] use std::arch::x86 as intr; #[cfg(target_arch = "x86_64")] use std::arch::x86_64 as intr; let mut diff = 0; while i + 15 < s1.len() { // SAFETY: we exit the while loop when there are less than 16 bytes left we read let a = intr::_mm_loadu_si128(s1.as_ptr().add(i).cast()); let b = intr::_mm_loadu_si128(s2.as_ptr().add(i).cast()); let cmp = intr::_mm_cmpeq_epi8(a, b); let mask = intr::_mm_movemask_epi8(cmp); // we only care about the case where all three bytes are equal let mask = (mask & (mask >> 1) & (mask >> 2)) & 0b001001001001001; if mask != 0 { let tz = mask.trailing_zeros() as usize; diff += (tz + 2) / 3; return diff; } diff += 5; i += 15; } while i + 2 < s1.len() { // SAFETY: we exit the while loop when there are less than 3 bytes left we read let a = unsafe { s1.get_unchecked(i..i + 3) }; let b = unsafe { s2.get_unchecked(i..i + 3) }; if a == b { break; } diff += 1; i += 3; } diff } /// # Safety /// /// s1.len() must be equal to s2.len() #[inline] #[target_feature(enable = "sse2")] pub(super) unsafe fn pack_bytes(cur: &[u8], goal: &[u8], v: &mut Vec<u8>) { let mut i = 0; while i < cur.len() { // SAFETY: count_equals demands the same invariants as the current function let equals = unsafe { count_equals(cur, goal, i) }; i += equals * 3; if i >= cur.len() { break; } let start = i; // SAFETY: count_equals demands the same invariants as the current function let diffs = unsafe { count_different(cur, goal, i) }; i += diffs * 3; let j = v.len() + equals / 255; v.resize(1 + j + diffs / 255, 255); v[j] = (equals % 255) as u8; v.push((diffs % 255) as u8); v.extend_from_slice(unsafe { goal.get_unchecked(start..i) }); i += 3; } if !v.is_empty() { // add two extra bytes to prevent access out of bounds later during decompression v.push(0); v.push(0); } } #[cfg(test)] mod tests { use super::*; use crate::compression::unpack_bytes_4channels; use rand::prelude::random; #[test] fn count_equal_test() { let a = [0u8; 102]; assert_eq!(unsafe { count_equals(&a, &a, 0) }, 102 / 3); for i in [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] { let mut b = a; b[i] = 1; assert_eq!(unsafe { count_equals(&a, &b, 0) }, i / 3, "i: {i}"); } } #[test] fn count_diffs_test() { let a = [0u8; 102]; assert_eq!(unsafe { count_different(&a, &a, 0) }, 0,); for i in [10, 20, 30, 40, 50, 60, 70, 80, 90, 102] { let mut b = a; for x in &mut b[..i] { *x = 1; } assert_eq!(unsafe { count_different(&a, &b, 0) }, (i + 2) / 3, "i: {i}"); } } fn buf_from(slice: &[u8]) -> Vec<u8> { let mut v = Vec::new(); for pix in slice.chunks_exact(3) { v.extend_from_slice(pix); v.push(255); } v } #[test] fn small() { if !is_x86_feature_detected!("sse2") { return; } let frame1 = [1, 2, 3, 4, 5, 6]; let frame2 = [1, 2, 3, 6, 5, 4]; let mut compressed = Vec::new(); unsafe { pack_bytes(&frame1, &frame2, &mut compressed) }; let mut buf = buf_from(&frame1); unpack_bytes_4channels(&mut buf, &compressed); for i in 0..2 { for j in 0..3 { assert_eq!( frame2[i * 3 + j], buf[i * 4 + j], "\nframe2: {frame2:?}, buf: {buf:?}\n" ); } } } #[test] fn total_random() { if !is_x86_feature_detected!("sse2") { return; } for _ in 0..10 { let mut original = Vec::with_capacity(20); for _ in 0..20 { let mut v = Vec::with_capacity(3000); for _ in 0..3000 { v.push(random::<u8>()); } original.push(v); } let mut compressed = Vec::with_capacity(20); let mut buf = Vec::new(); unsafe { pack_bytes(original.last().unwrap(), &original[0], &mut buf) } compressed.push(buf.clone().into_boxed_slice()); for i in 1..20 { buf.clear(); unsafe { pack_bytes(&original[i - 1], &original[i], &mut buf) } compressed.push(buf.clone().into_boxed_slice()); } let mut buf = buf_from(original.last().unwrap()); for i in 0..20 { unpack_bytes_4channels(&mut buf, &compressed[i]); let mut j = 0; let mut l = 0; while j < 3000 { for k in 0..3 { assert_eq!( buf[j + l + k], original[i][j + k], "Failed at index: {}", j + k ); } j += 3; l += 1; } } } } #[test] fn full() { if !is_x86_feature_detected!("sse2") { return; } for _ in 0..10 { let mut original = Vec::with_capacity(20); for j in 0..20 { let mut v = Vec::with_capacity(3006); v.extend([j, 0, 0, 0, 0, j]); for _ in 0..750 { v.push(random::<u8>()); } for i in 0..750 { v.push((i % 255) as u8); } for _ in 0..750 { v.push(random::<u8>()); } for i in 0..750 { v.push((i % 255) as u8); } original.push(v); } let mut compressed = Vec::with_capacity(20); let mut buf = Vec::new(); unsafe { pack_bytes(original.last().unwrap(), &original[0], &mut buf) } compressed.push(buf.clone().into_boxed_slice()); for i in 1..20 { buf.clear(); unsafe { pack_bytes(&original[i - 1], &original[i], &mut buf) } compressed.push(buf.clone().into_boxed_slice()); } let mut buf = buf_from(original.last().unwrap()); for i in 0..20 { unpack_bytes_4channels(&mut buf, &compressed[i]); let mut j = 0; let mut l = 0; while j < 3000 { for k in 0..3 { assert_eq!( buf[j + l + k], original[i][j + k], "Failed at index: {}", j + k ); } j += 3; l += 1; } } } } } 07070130196BCA000081A400000000000000000000000166342683000008CE000000080000000100000000000000000000002800000000swww-0.9.5/utils/src/compression/cpu.rs//! This module detects cpu features once and caches the results in mutable statics //! //! The mutable statics are not public, and therefore they are innacessible from other modules. //! The only thing this module exposes are functions that let us read them, and one function to //! initialize them. //! //! At worst, if we forget to initialize them, they will be holding false by default, and we won't //! be using the more optimized versions of the relevant functions. use std::sync::Once; /// This macro declares a a cpu feature as a static mut, automatically generating a getter /// function for it. Using it is safe because no one can modify it outside this module macro_rules! decl_feature { ($feature:ident, $function:ident) => { static mut $feature: bool = false; #[inline(always)] pub fn $function() -> bool { // SAFETY: we ensure this is false by default, and only changes ONCE, if someone calls // this module's init() function unsafe { $feature } } }; } static ONCE_INIT: Once = Once::new(); pub(super) fn init() { // SAFETY: features::init will modify some static mut variables. It is safe because we are // wrapping them in a Once call ONCE_INIT.call_once(|| unsafe { features::init() }); } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub mod features { decl_feature!(SSE2, sse2); decl_feature!(SSSE3, ssse3); /// # Safety /// /// This function modifies the static muts inside this module, so it mustn't be called while /// someone else is trying to read them. /// /// That said, at worst, what will happen is that they will default to false and we won't use /// the most optimized versions of some functions. The case where we will accidentally call a /// function with unsupported instructions will never happen. pub(super) unsafe fn init() { SSE2 = is_x86_feature_detected!("sse2"); SSSE3 = is_x86_feature_detected!("ssse3"); } } #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] pub mod features { /// UNIMPLEMENTED!!! This function must exist so that the init function in super compiles on /// any target pub(super) unsafe fn init() {} } 07070100189B40000041ED0000000000000000000000026634268300000000000000080000000100000000000000000000002800000000swww-0.9.5/utils/src/compression/decomp07070100189B61000081A40000000000000000000000016634268300001391000000080000000100000000000000000000002F00000000swww-0.9.5/utils/src/compression/decomp/mod.rs//! This modules contains all the decompression functions, including the specialized ones using //! architecture-dependent instructions #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub(super) mod ssse3; /// diff must be a slice produced by a BitPack /// buf must have the EXACT expected size by the BitPack #[inline(always)] pub(super) fn unpack_bytes_4channels(buf: &mut [u8], diff: &[u8]) { assert!( diff[diff.len() - 1] | diff[diff.len() - 2] == 0, "Poorly formed BitPack" ); // use the most efficient implementation available: #[cfg(not(test))] // when testing, we want to use the specific implementation { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] if super::cpu::features::ssse3() { return unsafe { ssse3::unpack_bytes_4channels(buf, diff) }; } } // The final bytes are just padding to prevent us from going out of bounds let len = diff.len() - 3; let buf_ptr = buf.as_mut_ptr(); let diff_ptr = diff.as_ptr(); let mut diff_idx = 0; let mut pix_idx = 0; while diff_idx < len { while unsafe { diff_ptr.add(diff_idx).read() } == u8::MAX { pix_idx += u8::MAX as usize; diff_idx += 1; } pix_idx += unsafe { diff_ptr.add(diff_idx).read() } as usize; diff_idx += 1; let mut to_cpy = 0; while unsafe { diff_ptr.add(diff_idx).read() } == u8::MAX { to_cpy += u8::MAX as usize; diff_idx += 1; } to_cpy += unsafe { diff_ptr.add(diff_idx).read() } as usize; diff_idx += 1; assert!( diff_idx + to_cpy * 3 + 1 < diff.len(), "copying: {}, diff.len(): {}", diff_idx + to_cpy * 3 + 1, diff.len() ); for _ in 0..to_cpy { unsafe { std::ptr::copy_nonoverlapping(diff_ptr.add(diff_idx), buf_ptr.add(pix_idx * 4), 4) } diff_idx += 3; pix_idx += 1; } pix_idx += 1; } } #[inline(always)] pub(super) fn unpack_bytes_3channels(buf: &mut [u8], diff: &[u8]) { assert!( diff[diff.len() - 1] | diff[diff.len() - 2] == 0, "Poorly formed BitPack" ); // The final bytes are just padding to prevent us from going out of bounds let len = diff.len() - 3; let buf_ptr = buf.as_mut_ptr(); let diff_ptr = diff.as_ptr(); let mut diff_idx = 0; let mut pix_idx = 0; while diff_idx < len { while unsafe { diff_ptr.add(diff_idx).read() } == u8::MAX { pix_idx += u8::MAX as usize; diff_idx += 1; } pix_idx += unsafe { diff_ptr.add(diff_idx).read() } as usize; diff_idx += 1; let mut to_cpy = 0; while unsafe { diff_ptr.add(diff_idx).read() } == u8::MAX { to_cpy += u8::MAX as usize; diff_idx += 1; } to_cpy += unsafe { diff_ptr.add(diff_idx).read() } as usize; diff_idx += 1; assert!( diff_idx + to_cpy * 3 <= diff.len(), "diff_idx: {diff_idx}, to_copy: {to_cpy}, diff.len(): {}", diff.len() ); unsafe { std::ptr::copy_nonoverlapping( diff_ptr.add(diff_idx), buf_ptr.add(pix_idx * 3), to_cpy * 3, ); } diff_idx += to_cpy * 3; pix_idx += to_cpy + 1; } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn ub_unpack_bytes4_poorly_formed() { let mut bytes = vec![u8::MAX; 9]; let diff = vec![u8::MAX; 18]; unpack_bytes_4channels(&mut bytes, &diff); } #[test] #[should_panic] fn ub_unpack_bytes3_poorly_formed() { let mut bytes = vec![u8::MAX; 9]; let diff = vec![u8::MAX; 18]; unpack_bytes_3channels(&mut bytes, &diff); } #[test] #[should_panic] fn ub_unpack_bytes4_poorly_formed2() { let mut bytes = vec![u8::MAX; 9]; let mut diff = vec![u8::MAX; 18]; diff[8] = 0; diff[7] = 0; unpack_bytes_4channels(&mut bytes, &diff); } #[test] #[should_panic] fn ub_unpack_bytes3_poorly_formed2() { let mut bytes = vec![u8::MAX; 9]; let mut diff = vec![u8::MAX; 18]; diff[8] = 0; diff[7] = 0; unpack_bytes_3channels(&mut bytes, &diff); } #[test] #[should_panic] fn ub_unpack_bytes4_poorly_formed3() { let mut bytes = vec![u8::MAX; 9]; let mut diff = vec![u8::MAX; 18]; diff[8] = 0; diff[7] = 0; diff[2] = 0; unpack_bytes_4channels(&mut bytes, &diff); } #[test] #[should_panic] fn ub_unpack_bytes3_poorly_formed3() { let mut bytes = vec![u8::MAX; 9]; let mut diff = vec![u8::MAX; 18]; diff[8] = 0; diff[7] = 0; diff[2] = 0; unpack_bytes_3channels(&mut bytes, &diff); } } 07070100189B67000081A40000000000000000000000016634268300001812000000080000000100000000000000000000003100000000swww-0.9.5/utils/src/compression/decomp/ssse3.rs#[inline] #[target_feature(enable = "ssse3")] pub(super) unsafe fn unpack_bytes_4channels(buf: &mut [u8], diff: &[u8]) { #[cfg(target_arch = "x86")] use std::arch::x86 as intr; #[cfg(target_arch = "x86_64")] use std::arch::x86_64 as intr; // The final bytes are just padding to prevent us from going out of bounds let len = diff.len() - 3; let buf_ptr = buf.as_mut_ptr(); let diff_ptr = diff.as_ptr(); let mask = intr::_mm_set_epi8(-1, 11, 10, 9, -1, 8, 7, 6, -1, 5, 4, 3, -1, 2, 1, 0); let mut diff_idx = 0; let mut pix_idx = 0; while diff_idx < len { while diff_ptr.add(diff_idx).read() == u8::MAX { pix_idx += u8::MAX as usize; diff_idx += 1; } pix_idx += diff_ptr.add(diff_idx).read() as usize; diff_idx += 1; let mut to_cpy = 0; while diff_ptr.add(diff_idx).read() == u8::MAX { to_cpy += u8::MAX as usize; diff_idx += 1; } to_cpy += diff_ptr.add(diff_idx).read() as usize; diff_idx += 1; assert!( diff_idx + to_cpy * 3 + 1 < diff.len(), "copying: {}, diff.len(): {}", diff_idx + to_cpy * 3 + 1, diff.len() ); while to_cpy > 4 { let d = intr::_mm_loadu_si128(diff_ptr.add(diff_idx).cast()); let to_store = intr::_mm_shuffle_epi8(d, mask); intr::_mm_storeu_si128(buf_ptr.add(pix_idx * 4).cast(), to_store); diff_idx += 12; pix_idx += 4; to_cpy -= 4; } for _ in 0..to_cpy { std::ptr::copy_nonoverlapping(diff_ptr.add(diff_idx), buf_ptr.add(pix_idx * 4), 4); diff_idx += 3; pix_idx += 1; } pix_idx += 1; } } #[cfg(test)] mod tests { use super::*; use crate::compression::pack_bytes; use rand::prelude::random; fn buf_from(slice: &[u8]) -> Vec<u8> { let mut v = Vec::new(); for pix in slice.chunks_exact(3) { v.extend_from_slice(pix); v.push(255); } v } #[test] fn small() { if !is_x86_feature_detected!("ssse3") { return; } let frame1 = [1, 2, 3, 4, 5, 6]; let frame2 = [1, 2, 3, 6, 5, 4]; let mut compressed = Vec::new(); unsafe { pack_bytes(&frame1, &frame2, &mut compressed) } let mut buf = buf_from(&frame1); unsafe { unpack_bytes_4channels(&mut buf, &compressed) } for i in 0..2 { for j in 0..3 { assert_eq!( frame2[i * 3 + j], buf[i * 4 + j], "\nframe2: {frame2:?}, buf: {buf:?}\n" ); } } } #[test] fn total_random() { if !is_x86_feature_detected!("ssse3") { return; } for _ in 0..10 { let mut original = Vec::with_capacity(20); for _ in 0..20 { let mut v = Vec::with_capacity(3000); for _ in 0..3000 { v.push(random::<u8>()); } original.push(v); } let mut compressed = Vec::with_capacity(20); let mut buf = Vec::new(); unsafe { pack_bytes(original.last().unwrap(), &original[0], &mut buf) } compressed.push(buf.clone().into_boxed_slice()); for i in 1..20 { buf.clear(); unsafe { pack_bytes(&original[i - 1], &original[i], &mut buf) } compressed.push(buf.clone().into_boxed_slice()); } let mut buf = buf_from(original.last().unwrap()); for i in 0..20 { unsafe { unpack_bytes_4channels(&mut buf, &compressed[i]) } let mut j = 0; let mut l = 0; while j < 3000 { for k in 0..3 { assert_eq!( buf[j + l + k], original[i][j + k], "Failed at index: {}", j + k ); } j += 3; l += 1; } } } } #[test] fn full() { if !is_x86_feature_detected!("ssse3") { return; } for _ in 0..10 { let mut original = Vec::with_capacity(20); for _ in 0..20 { let mut v = Vec::with_capacity(3000); for _ in 0..750 { v.push(random::<u8>()); } for i in 0..750 { v.push((i % 255) as u8); } for _ in 0..750 { v.push(random::<u8>()); } for i in 0..750 { v.push((i % 255) as u8); } original.push(v); } let mut compressed = Vec::with_capacity(20); let mut buf = Vec::new(); unsafe { pack_bytes(original.last().unwrap(), &original[0], &mut buf) } compressed.push(buf.clone().into_boxed_slice()); for i in 1..20 { buf.clear(); unsafe { pack_bytes(&original[i - 1], &original[i], &mut buf) } compressed.push(buf.clone().into_boxed_slice()); } let mut buf = buf_from(original.last().unwrap()); for i in 0..20 { unsafe { unpack_bytes_4channels(&mut buf, &compressed[i]) } let mut j = 0; let mut l = 0; while j < 3000 { for k in 0..3 { assert_eq!( buf[j + l + k], original[i][j + k], "Failed at index: {}", j + k ); } j += 3; l += 1; } } } } } 07070130196BCF000081A40000000000000000000000016634268300003195000000080000000100000000000000000000002800000000swww-0.9.5/utils/src/compression/mod.rs//! # Compression utilities //! //! Our compression strategy is documented in `comp/mod.rs` use comp::pack_bytes; use decomp::{unpack_bytes_3channels, unpack_bytes_4channels}; use std::ffi::{c_char, c_int}; use bitcode::{Decode, Encode}; use crate::ipc::PixelFormat; mod comp; mod cpu; mod decomp; /// extracted from lz4.h const LZ4_MAX_INPUT_SIZE: usize = 0x7E000000; extern "C" { /// # Safety /// /// This is guaranteed to succeed if `dst_cap >= LZ4_compressBound`. fn LZ4_compress_HC( src: *const c_char, dst: *mut c_char, src_len: c_int, dst_cap: c_int, comp_level: c_int, ) -> c_int; /// # Safety /// /// Fails when src is malformed, or dst_cap is insufficient. fn LZ4_decompress_safe( src: *const c_char, dst: *mut c_char, compressed_size: c_int, dst_cap: c_int, ) -> c_int; /// # Safety /// /// Only works for input_size <= LZ4_MAX_INPUT_SIZE. fn LZ4_compressBound(input_size: c_int) -> c_int; } /// This struct represents the cached difference between the previous frame and the next #[derive(Encode, Decode)] pub struct BitPack { inner: Box<[u8]>, /// This field will ensure we won't ever try to unpack the images on a buffer of the wrong size, /// which ultimately is what allows us to use unsafe in the unpack_bytes function expected_buf_size: usize, compressed_size: i32, } /// Struct responsible for compressing our data. We use it to cache vector extensions that might /// speed up compression #[derive(Default)] pub struct Compressor { buf: Vec<u8>, } impl Compressor { #[inline] pub fn new() -> Self { cpu::init(); Self { buf: Vec::new() } } /// Compresses a frame of animation by getting the difference between the previous and the /// current frame, and then running lz4 /// /// # Returns: /// * None if the two frames are identical /// * Some(bytes) if compression yielded something /// /// # Panics: /// * `prev.len() != cur.len()` /// * the len of the diff buffer is larger than 0x7E000000. In practice, this can only /// happen for 64k monitors and beyond #[inline] pub fn compress( &mut self, prev: &[u8], cur: &[u8], pixel_format: PixelFormat, ) -> Option<BitPack> { assert_eq!( prev.len(), cur.len(), "swww cannot currently deal with animations whose frames have different sizes!" ); self.buf.clear(); // SAFETY: the above assertion ensures prev.len() and cur.len() are equal, as needed unsafe { pack_bytes(prev, cur, &mut self.buf) } if self.buf.is_empty() { return None; } // This should only be a problem with 64k monitors and beyond, (hopefully) far into the future assert!( self.buf.len() <= LZ4_MAX_INPUT_SIZE, "frame is too large! cannot compress with LZ4!" ); // SAFETY: the above assertion ensures this will never fail let size = unsafe { LZ4_compressBound(self.buf.len() as c_int) } as usize; let mut v = vec![0; size]; // SAFETY: we've ensured above that size >= LZ4_compressBound, so this should always work let n = unsafe { LZ4_compress_HC( self.buf.as_ptr().cast(), v.as_mut_ptr() as _, self.buf.len() as c_int, size as c_int, 9, ) as usize }; v.truncate(n); let expected_buf_size = if pixel_format.channels() == 3 { cur.len() } else { (cur.len() / 3) * 4 }; Some(BitPack { inner: v.into_boxed_slice(), expected_buf_size, compressed_size: self.buf.len() as i32, }) } } pub struct Decompressor { /// this pointer stores an inner buffer we need to speed up decompression /// note we explicitly do not care about its length ptr: std::ptr::NonNull<u8>, cap: usize, } impl Drop for Decompressor { #[inline] fn drop(&mut self) { if self.cap > 0 { let layout = std::alloc::Layout::array::<u8>(self.cap).unwrap(); unsafe { std::alloc::dealloc(self.ptr.as_ptr(), layout) } } } } impl Decompressor { #[allow(clippy::new_without_default)] #[inline] pub fn new() -> Self { cpu::init(); Self { ptr: std::ptr::NonNull::dangling(), cap: 0, } } fn ensure_capacity(&mut self, goal: usize) { if self.cap >= goal { return; } let ptr = if self.cap == 0 { let layout = std::alloc::Layout::array::<u8>(goal).unwrap(); let p = unsafe { std::alloc::alloc(layout) }; match std::ptr::NonNull::new(p) { Some(p) => p, None => std::alloc::handle_alloc_error(layout), } } else { let old_layout = std::alloc::Layout::array::<u8>(self.cap).unwrap(); let new_layout = std::alloc::Layout::array::<u8>(goal).unwrap(); let p = unsafe { std::alloc::realloc(self.ptr.as_ptr(), old_layout, new_layout.size()) }; match std::ptr::NonNull::new(p) { Some(p) => p, None => std::alloc::handle_alloc_error(new_layout), } }; self.ptr = ptr; self.cap = goal; } ///returns whether unpacking was successful. Note it can only fail if `buf.len() != ///expected_buf_size` #[inline] pub fn decompress( &mut self, bitpack: &BitPack, buf: &mut [u8], pixel_format: PixelFormat, ) -> Result<(), String> { if buf.len() != bitpack.expected_buf_size { return Err(format!( "buf has len {}, but expected len is {}", buf.len(), bitpack.expected_buf_size )); } self.ensure_capacity(bitpack.compressed_size as usize); // SAFETY: errors will never happen because BitPacked is *always* only produced // with correct lz4 compression, and ptr has the necessary capacity let size = unsafe { LZ4_decompress_safe( bitpack.inner.as_ptr() as _, self.ptr.as_ptr() as _, bitpack.inner.len() as c_int, bitpack.compressed_size as c_int, ) }; if size != bitpack.compressed_size { return Err("BitPack is malformed!".to_string()); } // SAFETY: the call to self.ensure_capacity guarantees the pointer has the necessary size // to hold all the data let v = unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), bitpack.compressed_size as usize) }; if pixel_format.can_copy_directly_onto_wl_buffer() { unpack_bytes_3channels(buf, v); } else { unpack_bytes_4channels(buf, v); } Ok(()) } } #[cfg(test)] mod tests { use super::*; use rand::prelude::random; const FORMATS: [PixelFormat; 2] = [PixelFormat::Xrgb, PixelFormat::Rgb]; fn buf_from(slice: &[u8], original_channels: usize) -> Vec<u8> { if original_channels == 3 { return slice.to_vec(); } let mut v = Vec::new(); for pix in slice.chunks_exact(3) { v.extend_from_slice(pix); v.push(255); } v } #[test] //Use this when annoying problems show up fn small() { for format in FORMATS { let frame1 = [1, 2, 3, 4, 5, 6]; let frame2 = [1, 2, 3, 6, 5, 4]; let compressed = Compressor::new() .compress(&frame1, &frame2, format) .unwrap(); let mut buf = buf_from(&frame1, format.channels().into()); Decompressor::new() .decompress(&compressed, &mut buf, format) .unwrap(); for i in 0..2 { for j in 0..3 { assert_eq!( frame2[i * 3 + j], buf[i * format.channels() as usize + j], "\nframe2: {frame2:?}, buf: {buf:?}\n" ); } } } } #[test] fn total_random() { for format in FORMATS.into_iter() { for _ in 0..10 { let mut original = Vec::with_capacity(20); for _ in 0..20 { let mut v = Vec::with_capacity(3000); for _ in 0..3000 { v.push(random::<u8>()); } original.push(v); } let mut compressed = Vec::with_capacity(20); let mut compressor = Compressor::new(); let mut decompressor = Decompressor::new(); compressed.push( compressor .compress(original.last().unwrap(), &original[0], format) .unwrap(), ); for i in 1..20 { compressed.push( compressor .compress(&original[i - 1], &original[i], format) .unwrap(), ); } let mut buf = buf_from(original.last().unwrap(), format.channels().into()); for i in 0..20 { decompressor .decompress(&compressed[i], &mut buf, format) .unwrap(); let mut j = 0; let mut l = 0; while j < 3000 { for k in 0..3 { assert_eq!( buf[j + l + k], original[i][j + k], "Failed at index: {}", j + k ); } j += 3; l += !format.can_copy_directly_onto_wl_buffer() as usize; } } } } } #[test] fn full() { for format in FORMATS { for _ in 0..10 { let mut original = Vec::with_capacity(20); for _ in 0..20 { let mut v = Vec::with_capacity(3000); for _ in 0..750 { v.push(random::<u8>()); } for i in 0..750 { v.push((i % 255) as u8); } for _ in 0..750 { v.push(random::<u8>()); } for i in 0..750 { v.push((i % 255) as u8); } original.push(v); } let mut compressor = Compressor::new(); let mut decompressor = Decompressor::new(); let mut compressed = Vec::with_capacity(20); compressed.push( compressor .compress(original.last().unwrap(), &original[0], format) .unwrap(), ); for i in 1..20 { compressed.push( compressor .compress(&original[i - 1], &original[i], format) .unwrap(), ); } let mut buf = buf_from(original.last().unwrap(), format.channels().into()); for i in 0..20 { decompressor .decompress(&compressed[i], &mut buf, format) .unwrap(); let mut j = 0; let mut l = 0; while j < 3000 { for k in 0..3 { assert_eq!( buf[j + l + k], original[i][j + k], "Failed at index: {}", j + k ); } j += 3; l += !format.can_copy_directly_onto_wl_buffer() as usize; } } } } } } 070701201CC845000081A40000000000000000000000016634268300003851000000080000000100000000000000000000001C00000000swww-0.9.5/utils/src/ipc.rsuse bitcode::{Decode, Encode}; use std::{ fmt, io::{BufReader, BufWriter, Read, Write}, num::{NonZeroI32, NonZeroU8}, os::unix::net::UnixStream, path::PathBuf, time::Duration, }; use crate::{cache, compression::BitPack}; #[derive(Clone, PartialEq, Decode, Encode)] pub enum Coord { Pixel(f32), Percent(f32), } #[derive(Clone, PartialEq, Decode, Encode)] pub struct Position { pub x: Coord, pub y: Coord, } impl Position { #[must_use] pub fn new(x: Coord, y: Coord) -> Self { Self { x, y } } #[must_use] pub fn to_pixel(&self, dim: (u32, u32), invert_y: bool) -> (f32, f32) { let x = match self.x { Coord::Pixel(x) => x, Coord::Percent(x) => x * dim.0 as f32, }; let y = match self.y { Coord::Pixel(y) => { if invert_y { dim.1 as f32 - y } else { y } } Coord::Percent(y) => { if invert_y { (1.0 - y) * dim.1 as f32 } else { y * dim.1 as f32 } } }; (x, y) } #[must_use] pub fn to_percent(&self, dim: (u32, u32)) -> (f32, f32) { let x = match self.x { Coord::Pixel(x) => x / dim.0 as f32, Coord::Percent(x) => x, }; let y = match self.y { Coord::Pixel(y) => y / dim.1 as f32, Coord::Percent(y) => y, }; (x, y) } } #[derive(Debug, PartialEq, Clone, Encode, Decode)] pub enum BgImg { Color([u8; 3]), Img(String), } impl fmt::Display for BgImg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { BgImg::Color(color) => { write!(f, "color: {:02X}{:02X}{:02X}", color[0], color[1], color[2]) } BgImg::Img(p) => write!(f, "image: {p}",), } } } #[derive(Clone, Copy, Debug, Encode, Decode, PartialEq)] pub enum PixelFormat { /// No swap, can copy directly onto WlBuffer Bgr, /// Swap R and B channels at client, can copy directly onto WlBuffer Rgb, /// No swap, must extend pixel with an extra byte when copying Xbgr, /// Swap R and B channels at client, must extend pixel with an extra byte when copying Xrgb, } impl PixelFormat { #[inline] #[must_use] pub const fn channels(&self) -> u8 { match self { Self::Rgb => 3, Self::Bgr => 3, Self::Xbgr => 4, Self::Xrgb => 4, } } #[inline] #[must_use] pub const fn must_swap_r_and_b_channels(&self) -> bool { match self { Self::Bgr => false, Self::Rgb => true, Self::Xbgr => false, Self::Xrgb => true, } } #[inline] #[must_use] pub const fn can_copy_directly_onto_wl_buffer(&self) -> bool { match self { Self::Bgr => true, Self::Rgb => true, Self::Xbgr => false, Self::Xrgb => false, } } } #[derive(Clone, Copy, Debug, Decode, Encode, PartialEq)] pub enum Scale { Whole(NonZeroI32), Fractional(NonZeroI32), } impl Scale { #[inline] #[must_use] pub fn mul_dim(&self, width: i32, height: i32) -> (i32, i32) { match self { Scale::Whole(i) => (width * i.get(), height * i.get()), Scale::Fractional(f) => { let scale = f.get() as f64 / 120.0; let width = (width as f64 * scale).round() as i32; let height = (height as f64 * scale).round() as i32; (width, height) } } } #[inline] #[must_use] pub fn div_dim(&self, width: i32, height: i32) -> (i32, i32) { match self { Scale::Whole(i) => (width / i.get(), height / i.get()), Scale::Fractional(f) => { let scale = 120.0 / f.get() as f64; let width = (width as f64 * scale).round() as i32; let height = (height as f64 * scale).round() as i32; (width, height) } } } } impl fmt::Display for Scale { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match self { Scale::Whole(i) => i.get() as f32, Scale::Fractional(f) => f.get() as f32 / 120.0, } ) } } #[derive(Clone, Decode, Encode)] pub struct BgInfo { pub name: String, pub dim: (u32, u32), pub scale_factor: Scale, pub img: BgImg, pub pixel_format: PixelFormat, } impl BgInfo { #[inline] #[must_use] pub fn real_dim(&self) -> (u32, u32) { let dim = self .scale_factor .mul_dim(self.dim.0 as i32, self.dim.1 as i32); (dim.0 as u32, dim.1 as u32) } } impl fmt::Display for BgInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}: {}x{}, scale: {}, currently displaying: {}", self.name, self.dim.0, self.dim.1, self.scale_factor, self.img ) } } #[derive(Clone, Copy, Decode, Encode)] pub enum TransitionType { None, Simple, Fade, Outer, Wipe, Grow, Wave, } #[derive(Decode, Encode)] pub struct Transition { pub transition_type: TransitionType, pub duration: f32, pub step: NonZeroU8, pub fps: u16, pub angle: f64, pub pos: Position, pub bezier: (f32, f32, f32, f32), pub wave: (f32, f32), pub invert_y: bool, } #[derive(Decode, Encode)] pub struct Clear { pub color: [u8; 3], pub outputs: Box<[String]>, } #[derive(Decode, Encode)] pub struct Img { pub path: String, pub img: Box<[u8]>, } #[derive(Decode, Encode)] pub struct Animation { pub animation: Box<[(BitPack, Duration)]>, pub path: String, pub dimensions: (u32, u32), pub pixel_format: PixelFormat, } #[derive(Decode, Encode)] pub struct AnimationRequest { pub animations: Box<[Animation]>, pub outputs: Box<[Box<[String]>]>, } pub struct AnimationRequestBuilder { animations: Vec<Animation>, outputs: Vec<Box<[String]>>, } impl AnimationRequestBuilder { #[inline] #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { animations: Vec::new(), outputs: Vec::new(), } } #[inline] pub fn push(&mut self, animation: Animation, outputs: Box<[String]>) { self.animations.push(animation); self.outputs.push(outputs); } #[inline] pub fn build(self) -> AnimationRequest { AnimationRequest { animations: self.animations.into_boxed_slice(), outputs: self.outputs.into_boxed_slice(), } } } #[derive(Decode, Encode)] pub struct ImageRequest { pub transition: Transition, pub imgs: Box<[Img]>, pub outputs: Box<[Box<[String]>]>, } pub struct ImageRequestBuilder { pub transition: Transition, pub imgs: Vec<Img>, pub outputs: Vec<Box<[String]>>, } impl ImageRequestBuilder { #[inline] pub fn new(transition: Transition) -> Self { Self { transition, imgs: Vec::new(), outputs: Vec::new(), } } #[inline] pub fn push(&mut self, img: Img, outputs: Box<[String]>) { self.imgs.push(img); self.outputs.push(outputs); } #[inline] pub fn build(self) -> ImageRequest { ImageRequest { transition: self.transition, imgs: self.imgs.into_boxed_slice(), outputs: self.outputs.into_boxed_slice(), } } } #[derive(Decode, Encode)] pub enum Request { Animation(AnimationRequest), Clear(Clear), Ping, Kill, Query, Img(ImageRequest), } impl Request { pub fn send(&self, stream: &UnixStream) -> Result<(), String> { let bytes = bitcode::encode(self); std::thread::scope(|s| { if let Self::Animation(AnimationRequest { animations, .. }) = self { s.spawn(|| { for animation in animations.iter() { // only store the cache if we aren't reading from stdin if animation.path != "-" { if let Err(e) = cache::store_animation_frames(animation) { eprintln!("Error storing cache for {}: {e}", animation.path); } } } }); } let mut writer = BufWriter::new(stream); if let Err(e) = writer.write_all(&bytes.len().to_ne_bytes()) { return Err(format!("failed to write serialized request's length: {e}")); } if let Err(e) = writer.write_all(&bytes) { Err(format!("failed to write serialized request: {e}")) } else { if let Self::Img(ImageRequest { imgs, outputs, .. }) = self { for (Img { path, .. }, outputs) in imgs.iter().zip(outputs.iter()) { for output in outputs.iter() { if let Err(e) = super::cache::store(output, path) { eprintln!("ERROR: failed to store cache: {e}"); } } } } Ok(()) } }) } #[must_use] #[inline] pub fn receive(bytes: &[u8]) -> Self { bitcode::decode(bytes).expect("failed to decode request") } } #[derive(Decode, Encode)] pub enum Answer { Ok, Err(String), Info(Box<[BgInfo]>), Ping(bool), } impl Answer { pub fn send(&self, stream: &UnixStream) -> Result<(), String> { let bytes = bitcode::encode(self); let mut writer = BufWriter::new(stream); if let Err(e) = writer.write_all(&bytes.len().to_ne_bytes()) { return Err(format!("failed to write serialized answer's length: {e}")); } if let Err(e) = writer.write_all(&bytes) { Err(format!("Failed to write serialized answer: {e}")) } else { Ok(()) } } #[must_use] #[inline] pub fn receive(bytes: &[u8]) -> Self { bitcode::decode(bytes).expect("failed to decode answer") } } pub fn read_socket(stream: &UnixStream) -> Result<Vec<u8>, String> { let mut reader = BufReader::new(stream); let mut buf = vec![0; 8]; let mut tries = 0; loop { match reader.read_exact(&mut buf[0..std::mem::size_of::<usize>()]) { Ok(()) => break, Err(e) => { if e.kind() == std::io::ErrorKind::WouldBlock && tries < 5 { std::thread::sleep(Duration::from_millis(1)); } else { return Err(format!("failed to read serialized length: {e}")); } } } tries += 1; } let len = usize::from_ne_bytes(buf[0..std::mem::size_of::<usize>()].try_into().unwrap()); buf.clear(); buf.resize(len, 0); if let Err(e) = reader.read_exact(&mut buf) { return Err(format!("Failed to read request: {e}")); } Ok(buf) } #[must_use] pub fn get_socket_path() -> PathBuf { let runtime_dir = if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") { dir } else { "/tmp/swww".to_string() }; let mut socket_path = PathBuf::new(); socket_path.push(runtime_dir); let mut socket_name = String::new(); socket_name.push_str("swww-"); if let Ok(socket) = std::env::var("WAYLAND_DISPLAY") { socket_name.push_str(socket.as_str()); } else { socket_name.push_str("wayland-0") } socket_name.push_str(".socket"); socket_path.push(socket_name); socket_path } pub fn get_cache_path() -> Result<PathBuf, String> { let cache_path = match std::env::var("XDG_CACHE_HOME") { Ok(dir) => { let mut cache = PathBuf::from(dir); cache.push("swww"); cache } Err(_) => match std::env::var("HOME") { Ok(dir) => { let mut cache = PathBuf::from(dir); cache.push(".cache/swww"); cache } Err(_) => return Err("failed to read both XDG_CACHE_HOME and HOME env vars".to_owned()), }, }; if !cache_path.is_dir() { if let Err(e) = std::fs::create_dir(&cache_path) { return Err(format!( "failed to create cache_path \"{}\": {e}", cache_path.display() )); } } Ok(cache_path) } /// We make sure the Stream is always set to blocking mode /// /// * `tries` - how many times to attempt the connection /// * `interval` - how long to wait between attempts, in milliseconds pub fn connect_to_socket(addr: &PathBuf, tries: u8, interval: u64) -> Result<UnixStream, String> { //Make sure we try at least once let tries = if tries == 0 { 1 } else { tries }; let mut error = None; for _ in 0..tries { match UnixStream::connect(addr) { Ok(socket) => { if let Err(e) = socket.set_nonblocking(false) { return Err(format!("Failed to set blocking connection: {e}")); } #[cfg(debug_assertions)] let timeout = Duration::from_secs(30); //Some operations take a while to respond in debug mode #[cfg(not(debug_assertions))] let timeout = Duration::from_secs(5); if let Err(e) = socket.set_read_timeout(Some(timeout)) { return Err(format!("failed to set read timeout for socket: {e}")); } return Ok(socket); } Err(e) => error = Some(e), } std::thread::sleep(Duration::from_millis(interval)); } let error = error.unwrap(); if error.kind() == std::io::ErrorKind::NotFound { return Err("Socket file not found. Are you sure swww-daemon is running?".to_string()); } Err(format!("Failed to connect to socket: {error}")) } 070701201CC846000081A40000000000000000000000016634268300000031000000080000000100000000000000000000001C00000000swww-0.9.5/utils/src/lib.rspub mod cache; pub mod compression; pub mod ipc; 0707010018BB92000081ED0000000000000000000000016634268300000426000000080000000100000000000000000000001600000000swww-0.9.5/version.sh#!/bin/sh # This is a helper script to use just before releasing a new version # (it helps us not forget anything, as has happenned before) if [ $# -lt 1 ]; then echo "Usage: $0 <new version name>" exit 1 fi pkill swww-daemon set -e # don't forget updating dependencies and testing everything cargo update cargo build cargo test --workspace -- --include-ignored ./doc/gen.sh # make sure the docs "compile" # Cargo.toml: sed \ -e "s/^version = .*/version = \"$1\"/" \ -e "s/^utils.*/utils = { version = \"$1\", path = \"utils\" }/" \ Cargo.toml > TMP mv -v TMP Cargo.toml sed "s/^version = .*/version = \"$1\"/" utils/Cargo.toml > TMP mv -v TMP utils/Cargo.toml sed \ -e "s/^version = .*/version = \"$1\"/" \ -e "s/^utils.*/utils = { version = \"$1\", path = \"..\/utils\" }/" \ daemon/Cargo.toml > TMP mv -v TMP daemon/Cargo.toml # CHANGELOG: sed -e "s/^### Unreleased/### $1/" \ -e '1s/^/### Unreleased\n\n\n/' CHANGELOG.md > TMP mv -v TMP CHANGELOG.md # Make sure it still builds (just to be 100% safe), and to update Cargo.lock cargo build 07070100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor