File swww-0.8.2.obscpio of Package failed_swww
07070100000000000081A400000000000000000000000165A5297300000025000000000000000000000000000000000000001600000000swww-0.8.2/.gitignoretarget/*
completions/*
test_images/*
07070100000001000081A400000000000000000000000165A5297300002F52000000000000000000000000000000000000001800000000swww-0.8.2/CHANGELOG.md### Unreleased
### 0.8.2-master
### 0.8.2
#### **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.
07070100000002000081A400000000000000000000000165A529730000B7A7000000000000000000000000000000000000001600000000swww-0.8.2/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 = "ahash"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[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.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]]
name = "assert_cmd"
version = "2.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00ad3f3a942eee60335ab4342358c161ee296829e0d16ff42fc1d6cb07815467"
dependencies = [
"anstyle",
"bstr",
"doc-comment",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "bstr"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
dependencies = [
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "bytecheck"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627"
dependencies = [
"bytecheck_derive",
"ptr_meta",
"simdutf8",
]
[[package]]
name = "bytecheck_derive"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[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.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"jobserver",
"libc",
]
[[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.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
[[package]]
name = "ciborium-ll"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
dependencies = [
"ciborium-io",
"half 1.8.2",
]
[[package]]
name = "clap"
version = "4.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
]
[[package]]
name = "clap_complete"
version = "4.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97aeaa95557bd02f23fbb662f981670c3d20c5a26e69f7354b28f57092437fcd"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
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 = "cursor-icon"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
[[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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[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.71.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8"
dependencies = [
"bit_field",
"flume",
"half 2.2.1",
"lebe",
"miniz_oxide",
"rayon-core",
"smallvec",
"zune-inflate",
]
[[package]]
name = "fast_image_resize"
version = "2.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc789a40040e11bbe4ba31ca319406805a12fe3f8d71314bbc4bd076602ad55a"
dependencies = [
"num-traits",
"thiserror",
]
[[package]]
name = "fdeflate"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
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 = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "getrandom"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gif"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "half"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
dependencies = [
"crunchy",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "image"
version = "0.24.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"exr",
"gif",
"jpeg-decoder",
"num-traits",
"png",
"qoi",
"tiff",
]
[[package]]
name = "is-terminal"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys 0.52.0",
]
[[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.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobserver"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
dependencies = [
"libc",
]
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
dependencies = [
"rayon",
]
[[package]]
name = "js-sys"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
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 = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[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.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "libloading"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "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.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "lzzzz"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8014d1362004776e6a91e4c15a3aa7830d1b6650a075b51a9969ebb6d6af13bc"
dependencies = [
"cc",
]
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "memmap2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
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 = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "nix"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.4.1",
"cfg-if",
"libc",
]
[[package]]
name = "num-traits"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
"libm",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
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 = "pkg-config"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
[[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.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a"
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.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0"
dependencies = [
"anstyle",
"difflib",
"itertools 0.11.0",
"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.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "ptr_meta"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
dependencies = [
"ptr_meta_derive",
]
[[package]]
name = "ptr_meta_derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
"bytemuck",
]
[[package]]
name = "quick-xml"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[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 = "rayon"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rend"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd"
dependencies = [
"bytecheck",
]
[[package]]
name = "rkyv"
version = "0.7.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5"
dependencies = [
"bitvec",
"bytecheck",
"bytes",
"hashbrown",
"ptr_meta",
"rend",
"rkyv_derive",
"seahash",
"tinyvec",
"uuid",
]
[[package]]
name = "rkyv_derive"
version = "0.7.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "rustix"
version = "0.38.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
dependencies = [
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "ryu"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[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 = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "serde_json"
version = "1.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "simdutf8"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
[[package]]
name = "simplelog"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369"
dependencies = [
"log",
"termcolor",
"time",
]
[[package]]
name = "smallvec"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e"
[[package]]
name = "smithay-client-toolkit"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f"
dependencies = [
"bitflags 2.4.1",
"cursor-icon",
"libc",
"log",
"memmap2",
"rustix",
"thiserror",
"wayland-backend",
"wayland-client",
"wayland-csd-frame",
"wayland-cursor",
"wayland-protocols",
"wayland-protocols-wlr",
"wayland-scanner",
"xkeysym",
]
[[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.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "swww"
version = "0.8.2"
dependencies = [
"assert_cmd",
"clap",
"clap_complete",
"fast_image_resize",
"image",
"rand",
"utils",
]
[[package]]
name = "swww-daemon"
version = "0.8.2"
dependencies = [
"keyframe",
"log",
"nix 0.27.1",
"rand",
"rayon",
"rkyv",
"sd-notify",
"simplelog",
"smithay-client-toolkit",
"spin_sleep",
"utils",
"wayland-client",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
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.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[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.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
dependencies = [
"deranged",
"itoa",
"libc",
"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.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
dependencies = [
"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 = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[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.8.2"
dependencies = [
"criterion",
"lazy_static",
"lzzzz",
"rand",
"rkyv",
]
[[package]]
name = "uuid"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[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.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
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.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.48",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
[[package]]
name = "wayland-backend"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4"
dependencies = [
"cc",
"downcast-rs",
"nix 0.26.4",
"scoped-tls",
"smallvec",
"wayland-sys",
]
[[package]]
name = "wayland-client"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3"
dependencies = [
"bitflags 2.4.1",
"log",
"nix 0.26.4",
"wayland-backend",
"wayland-scanner",
]
[[package]]
name = "wayland-csd-frame"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
dependencies = [
"bitflags 2.4.1",
"cursor-icon",
"wayland-backend",
]
[[package]]
name = "wayland-cursor"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44aa20ae986659d6c77d64d808a046996a932aa763913864dc40c359ef7ad5b"
dependencies = [
"nix 0.26.4",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c"
dependencies = [
"bitflags 2.4.1",
"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.4.1",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-scanner"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c"
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.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "weezl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "xcursor"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
[[package]]
name = "xkeysym"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621"
[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]
07070100000003000081A400000000000000000000000165A52973000002D0000000000000000000000000000000000000001600000000swww-0.8.2/Cargo.toml[workspace]
members = ["daemon"]
default-members = [".", "daemon"]
[package]
name = "swww"
version = "0.8.2"
authors = ["Leonardo Gibrowski FaƩ <leonardo.fae44@gmail.com>"]
edition = "2021"
# we use `std::cell::OnceLock` which was stabilized in 1.70
rust-version = "1.70"
[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.24"
fast_image_resize = "2.7"
clap = { version = "4.4", features = ["derive", "wrap_help", "env"] }
rand = "0.8"
utils = { path = "utils" }
[dev-dependencies]
assert_cmd = "2.0"
[build-dependencies]
clap = { version = "4.4", features = ["derive", "env"] }
clap_complete = "4.4"
07070100000004000081A400000000000000000000000165A529730000894D000000000000000000000000000000000000001300000000swww-0.8.2/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>.
07070100000005000081A400000000000000000000000165A5297300001EC9000000000000000000000000000000000000001500000000swww-0.8.2/README.md# A Solution to your Wayland Wallpaper Woes
### Efficient animated wallpaper daemon for wayland, controlled at runtime


## 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)
## 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.70.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 init
```
Then, 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

The `left`, `right`, `top` and `bottom` transitions all work similarly.
#### Example outer transition

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
### 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
07070100000006000081A400000000000000000000000165A52973000000CB000000000000000000000000000000000000001000000000swww-0.8.2/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?
07070100000007000081A400000000000000000000000165A52973000002EB000000000000000000000000000000000000001400000000swww-0.8.2/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)
}
07070100000008000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000001200000000swww-0.8.2/daemon07070100000009000081A400000000000000000000000165A52973000002F2000000000000000000000000000000000000001D00000000swww-0.8.2/daemon/Cargo.toml[package]
name = "swww-daemon"
version = "0.8.2"
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" ]}
smithay-client-toolkit = { version = "0.18", default-features = false }
nix = { version = "0.27", default-features = false, features = [ "signal", "poll" ] }
keyframe = "1.1"
rkyv = "0.7"
rayon = "1.7"
spin_sleep = "1.1"
sd-notify = { version = "0.4.1" }
utils = { path = "../utils" }
[dev-dependencies]
rand = "0.8"
0707010000000A000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000001600000000swww-0.8.2/daemon/src0707010000000B000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000002100000000swww-0.8.2/daemon/src/animations0707010000000C000081A400000000000000000000000165A5297300000A69000000000000000000000000000000000000003100000000swww-0.8.2/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();
}
}
}
0707010000000D000081A400000000000000000000000165A5297300001AAE000000000000000000000000000000000000002800000000swww-0.8.2/daemon/src/animations/mod.rsuse log::error;
use rkyv::{boxed::ArchivedBox, string::ArchivedString, Deserialize};
use std::{
sync::Arc,
thread::{self, Scope},
time::Duration,
};
use utils::ipc::{
Answer, ArchivedAnimation, ArchivedImg, ArchivedRequest, ArchivedTransition, BgImg, Request,
};
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 struct Animator {
anim_barrier: ArcAnimBarrier,
}
impl Animator {
pub fn new() -> Self {
Self {
anim_barrier: ArcAnimBarrier::new(),
}
}
fn spawn_transition_thread<'a, 'b>(
scope: &'a Scope<'b, '_>,
transition: &'b ArchivedTransition,
img: &'b ArchivedBox<[u8]>,
path: &'b ArchivedString,
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();
if img.len() == dimensions.0 as usize * dimensions.1 as usize * 3 {
Transition::new(wallpapers, dimensions, transition.clone()).execute(img);
} else {
error!(
"image is of wrong size! Image len: {}, expected size: {}",
img.len(),
dimensions.0 as usize * dimensions.1 as usize * 3
);
}
})
{
error!("failed to spawn 'transition' thread: {}", e);
}
}
pub fn transition(&mut self, bytes: Vec<u8>, wallpapers: Vec<Vec<Arc<Wallpaper>>>) -> Answer {
match thread::Builder::new()
.stack_size(1 << 15)
.name("transition spawner".to_string())
.spawn(move || {
if let ArchivedRequest::Img((transition, imgs)) = Request::receive(&bytes) {
thread::scope(|s| {
for ((ArchivedImg { 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 ArchivedAnimation,
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.transition_finished() {
break;
}
let duration: Duration = animation.animation[0]
.1
.deserialize(&mut rkyv::Infallible)
.unwrap();
std::thread::sleep(duration / 2);
}
}
let mut now = std::time::Instant::now();
for (frame, duration) in animation.animation.iter().cycle() {
let duration: Duration = duration.deserialize(&mut rkyv::Infallible).unwrap();
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;
}
if !wallpapers[i].canvas_change(|canvas| frame.unpack(canvas)) {
error!("failed to unpack frame, canvas has the wrong size");
wallpapers.swap_remove(i);
tokens.swap_remove(i);
continue;
}
wallpapers[i].draw();
i += 1;
}
if wallpapers.is_empty() {
return;
}
let timeout = duration.saturating_sub(now.elapsed());
spin_sleep::sleep(timeout);
crate::wake_poll();
now = std::time::Instant::now();
}
})
{
error!("failed to spawn 'animation' thread: {}", e);
}
}
pub fn animate(&mut self, bytes: Vec<u8>, 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| {
if let ArchivedRequest::Animation(animations) = Request::receive(&bytes) {
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()),
}
}
}
0707010000000E000081A400000000000000000000000165A5297300003AAC000000000000000000000000000000000000003000000000swww-0.8.2/daemon/src/animations/transitions.rsuse std::{
sync::Arc,
time::{Duration, Instant},
};
use rayon::prelude::*;
use log::debug;
use utils::ipc::{ArchivedPosition, ArchivedTransitionType};
use crate::wallpaper::{AnimationToken, Wallpaper};
use keyframe::{
functions::BezierCurve, keyframes, mint::Vector2, num_traits::Pow, AnimationSequence,
};
macro_rules! change_cols {
($step:ident, $old:ident, $new:ident, $done:ident) => {
for (old_col, new_col) in $old.iter_mut().zip($new) {
if old_col.abs_diff(*new_col) < $step {
*old_col = *new_col;
} else if *old_col > *new_col {
*old_col -= $step;
$done = false;
} else {
*old_col += $step;
$done = false;
}
}
};
($step:ident, $old:ident, $new:ident) => {
for (old_col, new_col) in $old.iter_mut().zip($new) {
if old_col.abs_diff(*new_col) < $step {
*old_col = *new_col;
} else if *old_col > *new_col {
*old_col -= $step;
} else {
*old_col += $step;
}
}
};
}
pub struct Transition {
animation_tokens: Vec<AnimationToken>,
wallpapers: Vec<Arc<Wallpaper>>,
dimensions: (u32, u32),
transition_type: ArchivedTransitionType,
duration: f32,
step: u8,
fps: Duration,
angle: f64,
pos: ArchivedPosition,
bezier: BezierCurve,
wave: (f32, f32),
invert_y: bool,
}
/// All transitions return whether or not they completed
impl Transition {
pub fn new(
wallpapers: Vec<Arc<Wallpaper>>,
dimensions: (u32, u32),
transition: utils::ipc::ArchivedTransition,
) -> 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,
fps: Duration::from_nanos(1_000_000_000 / transition.fps as u64),
angle: transition.angle,
pos: transition.pos,
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 fn execute(mut self, new_img: &[u8]) {
debug!("Starting transitions");
match self.transition_type {
ArchivedTransitionType::Simple => self.simple(new_img),
ArchivedTransitionType::Wipe => self.wipe(new_img),
ArchivedTransitionType::Grow => self.grow(new_img),
ArchivedTransitionType::Outer => self.outer(new_img),
ArchivedTransitionType::Wave => self.wave(new_img),
ArchivedTransitionType::Fade => self.fade(new_img),
};
debug!("Transitions finished");
}
fn send_frame(&mut self, now: &mut Instant) {
let fps = self.fps;
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;
}
let timeout = fps.saturating_sub(now.elapsed());
spin_sleep::sleep(timeout);
crate::wake_poll();
*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 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_mut() {
wallpaper.canvas_change(|canvas| {
for (old, new) in canvas.chunks_exact_mut(4).zip(new_img.chunks_exact(3)) {
change_cols!(step, old, new, done);
}
});
wallpaper.draw();
}
self.send_frame(&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 mut now = Instant::now();
while start.elapsed().as_secs_f64() < seq.duration() {
for wallpaper in self.wallpapers.iter_mut() {
wallpaper.canvas_change(|canvas| {
canvas
.par_chunks_exact_mut(4)
.zip(new_img.par_chunks_exact(3))
.for_each(|(old_pix, new_pix)| {
for (old_col, new_col) in old_pix.iter_mut().zip(new_pix) {
*old_col =
(*old_col as f64 * (1.0 - step) + *new_col as f64 * step) as u8;
}
});
});
wallpaper.draw();
}
self.send_frame(&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;
while start.elapsed().as_secs_f64() < seq.duration() {
for wallpaper in self.wallpapers.iter_mut() {
wallpaper.canvas_change(|canvas| {
canvas
.par_chunks_exact_mut(4)
.zip(new_img.par_chunks_exact(3))
.enumerate()
.for_each(|(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_cols!(step, old, new);
}
});
});
wallpaper.draw();
}
self.send_frame(&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;
while start.elapsed().as_secs_f64() < seq.duration() {
for wallpaper in self.wallpapers.iter_mut() {
wallpaper.canvas_change(|canvas| {
canvas
.par_chunks_exact_mut(4)
.zip(new_img.par_chunks_exact(3))
.enumerate()
.for_each(|(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_cols!(step, old, new);
}
});
});
wallpaper.draw();
}
self.send_frame(&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 (mut seq, start) = self.bezier_seq(0.0, dist_end);
let mut now = Instant::now();
while start.elapsed().as_secs_f64() < seq.duration() {
for wallpaper in self.wallpapers.iter_mut() {
wallpaper.canvas_change(|canvas| {
canvas
.par_chunks_exact_mut(4)
.zip(new_img.par_chunks_exact(3))
.enumerate()
.for_each(|(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 = self
.step
.saturating_add((dist_center - pix_center_dist).log2() as u8);
change_cols!(step, old, new);
}
});
});
wallpaper.draw();
}
self.send_frame(&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 (mut seq, start) = self.bezier_seq(dist_center, 0.0);
let mut now = Instant::now();
while start.elapsed().as_secs_f64() < seq.duration() {
for wallpaper in self.wallpapers.iter_mut() {
wallpaper.canvas_change(|canvas| {
canvas
.par_chunks_exact_mut(4)
.zip(new_img.par_chunks_exact(3))
.enumerate()
.for_each(|(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 = self
.step
.saturating_add((pix_center_dist - dist_center).log2() as u8);
change_cols!(step, old, new);
}
});
});
wallpaper.draw();
}
self.send_frame(&mut now);
dist_center = seq.now();
seq.advance_to(start.elapsed().as_secs_f64());
}
self.step = 4 + self.step / 4;
self.simple(new_img)
}
}
0707010000000F000081A400000000000000000000000165A5297300004F16000000000000000000000000000000000000001E00000000swww-0.8.2/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;
mod wallpaper;
use log::{debug, error, info, warn, LevelFilter};
use nix::{
poll::{poll, PollFd, PollFlags},
sys::signal::{self, SigHandler, Signal},
};
use rkyv::{boxed::ArchivedBox, string::ArchivedString};
use simplelog::{ColorChoice, TermLogger, TerminalMode, ThreadLogMode};
use wallpaper::Wallpaper;
use std::{
fs,
num::NonZeroI32,
os::{
fd::{BorrowedFd, RawFd},
unix::net::{UnixListener, UnixStream},
},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex, OnceLock,
},
};
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState, Region},
delegate_compositor, delegate_layer, delegate_output, delegate_registry, delegate_shm,
output::{OutputHandler, OutputState},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
shell::{
wlr_layer::{Layer, LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure},
WaylandSurface,
},
shm::{slot::SlotPool, Shm, ShmHandler},
};
use wayland_client::{
globals::{registry_queue_init, GlobalList},
protocol::{wl_output, wl_surface},
Connection, QueueHandle,
};
use utils::ipc::{get_socket_path, Answer, ArchivedRequest, BgInfo, Request};
use animations::Animator;
// 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 POLL_WAKER: OnceLock<RawFd> = OnceLock::new();
pub fn wake_poll() {
if let Err(e) = nix::unistd::write(*unsafe { POLL_WAKER.get().unwrap_unchecked() }, &[0]) {
error!("failed to write to pipe file descriptor: {e}");
}
}
extern "C" fn signal_handler(_s: i32) {
exit_daemon();
}
fn main() -> Result<(), String> {
rayon::ThreadPoolBuilder::default()
.thread_name(|i| format!("rayon thread {i}"))
.build_global()
.expect("failed to configure rayon global thread pool");
make_logger();
let listener = SocketWrapper::new()?;
let wake = setup_signals_and_pipe();
let conn = Connection::connect_to_env().expect("failed to connect to the wayland server");
// 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);
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...");
let mut buf = [0; 16];
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 waker = unsafe { BorrowedFd::borrow_raw(wake) };
let mut fds = [
PollFd::new(&listener.0, PollFlags::POLLIN),
PollFd::new(&connection_fd, PollFlags::POLLIN | PollFlags::POLLRDBAND),
PollFd::new(&waker, PollFlags::POLLIN),
];
match poll(&mut fds, -1) {
Ok(_) => (),
Err(e) => match e {
nix::errno::Errno::EINTR => (),
_ => panic!("failed to poll file descriptors: {e}"),
},
};
[fds[0].revents(), fds[1].revents(), fds[2].revents()]
};
if let Some(flags) = events[1] {
if !flags.is_empty() {
read_guard.read().expect("failed to read the event queue");
event_queue
.dispatch_pending(&mut daemon)
.expect("failed to dispatch events");
}
}
if let Some(flags) = events[0] {
if !flags.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}")),
},
}
}
}
if let Some(flags) = events[2] {
if !flags.is_empty() {
if let Err(e) = nix::unistd::read(wake, &mut buf) {
error!("error reading pipe file descriptor: {e}");
}
}
}
}
if let Err(e) = nix::unistd::close(*POLL_WAKER.get().unwrap()) {
error!("error closing write pipe file descriptor: {e}");
}
if let Err(e) = nix::unistd::close(wake) {
error!("error closing read pipe file descriptor: {e}");
}
info!("Goodbye!");
Ok(())
}
/// Returns the file descriptor we should install in the poll handler
fn setup_signals_and_pipe() -> RawFd {
let handler = SigHandler::Handler(signal_handler);
for signal in [Signal::SIGINT, Signal::SIGQUIT, Signal::SIGTERM] {
unsafe { signal::signal(signal, handler).expect("failed to install signal handler") };
}
let (r, w) = nix::unistd::pipe().expect("failed to create pipe");
let _ = POLL_WAKER.get_or_init(|| w);
r
}
/// This is a wrapper that makes sure to delete the socket when it is dropped
/// It also makes sure to set the listener to nonblocking mode
struct SocketWrapper(UnixListener);
impl SocketWrapper {
fn new() -> Result<Self, String> {
let socket_addr = get_socket_path();
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
);
if let Err(e) = listener.set_nonblocking(true) {
let _ = fs::remove_file(&socket_addr);
return Err(format!("failed to set socket to nonblocking mode: {e}"));
}
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_state: CompositorState,
registry_state: RegistryState,
output_state: OutputState,
shm: Shm,
pool: Arc<Mutex<SlotPool>>,
// swww stuff
wallpapers: Vec<Arc<Wallpaper>>,
animator: Animator,
initializing: bool,
}
impl Daemon {
fn new(globals: &GlobalList, qh: &QueueHandle<Self>) -> Self {
// The compositor (not to be confused with the server which is commonly called the compositor) allows
// configuring surfaces to be presented.
let compositor_state =
CompositorState::bind(globals, qh).expect("wl_compositor is not available");
let layer_shell = LayerShell::bind(globals, qh).expect("layer shell is not available");
let shm = Shm::bind(globals, qh).expect("wl_shm is not available");
let pool = SlotPool::new(256 * 256 * 4, &shm).expect("failed to create SlotPool");
Self {
// Outputs may be hotplugged at runtime, therefore we need to setup a registry state to
// listen for Outputs.
registry_state: RegistryState::new(globals),
output_state: OutputState::new(globals, qh),
compositor_state,
shm,
pool: Arc::new(Mutex::new(pool)),
layer_shell,
wallpapers: Vec::new(),
animator: Animator::new(),
initializing: true,
}
}
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 {
ArchivedRequest::Animation(animations) => {
let mut wallpapers = Vec::new();
for (_, names) in animations.iter() {
wallpapers.push(self.find_wallpapers_by_names(names));
}
self.animator.animate(bytes, wallpapers)
}
ArchivedRequest::Clear(clear) => {
self.initializing = false;
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.inc_animation_id();
}
for wallpaper in wallpapers {
wallpaper.set_img_info(utils::ipc::BgImg::Color(color));
wallpaper.clear(color);
wallpaper.draw();
}
wake_poll();
}) {
Ok(_) => Answer::Ok,
Err(e) => Answer::Err(format!("failed to spawn `clear` thread: {e}")),
}
}
ArchivedRequest::Init => Answer::Init(
self.wallpapers
.iter()
.all(|w| w.configured.load(std::sync::atomic::Ordering::Acquire)),
),
ArchivedRequest::Kill => {
exit_daemon();
Answer::Ok
}
ArchivedRequest::Query => Answer::Info(self.wallpapers_info()),
ArchivedRequest::Img((_, imgs)) => {
self.initializing = false;
let mut used_wallpapers = Vec::new();
for img in imgs.iter() {
let mut wallpapers = self.find_wallpapers_by_names(&img.1);
for wallpaper in wallpapers.iter_mut() {
wallpaper.inc_animation_id();
}
used_wallpapers.push(wallpapers);
}
self.animator.transition(bytes, used_wallpapers);
Answer::Ok
}
};
if let Err(e) = answer.send(&stream) {
error!("error sending answer to client: {e}");
}
}
fn wallpapers_info(&self) -> Box<[BgInfo]> {
self.output_state
.outputs()
.filter_map(|output| {
if let Some(info) = self.output_state.info(&output) {
if let Some(wallpaper) = self.wallpapers.iter().find(|w| w.has_id(info.id)) {
return Some(BgInfo {
name: info.name.unwrap_or("?".to_string()),
dim: info
.logical_size
.map(|(width, height)| (width as u32, height as u32))
.unwrap_or((0, 0)),
scale_factor: info.scale_factor,
img: wallpaper.get_img_info(),
});
}
}
None
})
.collect()
}
fn find_wallpapers_by_names(
&self,
names: &ArchivedBox<[ArchivedString]>,
) -> Vec<Arc<Wallpaper>> {
self.output_state
.outputs()
.filter_map(|output| {
if let Some(info) = self.output_state.info(&output) {
if let Some(name) = info.name {
if names.is_empty() || names.iter().any(|n| n.as_str() == name) {
if let Some(wallpaper) =
self.wallpapers.iter().find(|w| w.has_id(info.id))
{
return Some(Arc::clone(wallpaper));
}
}
}
}
None
})
.collect()
}
}
impl CompositorHandler for Daemon {
fn scale_factor_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
surface: &wl_surface::WlSurface,
new_factor: i32,
) {
for wallpaper in self.wallpapers.iter_mut() {
if wallpaper.has_surface(surface) {
wallpaper.resize(None, None, Some(NonZeroI32::new(new_factor).unwrap()));
return;
}
}
}
fn frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
surface: &wl_surface::WlSurface,
_time: u32,
) {
for wallpaper in self.wallpapers.iter_mut() {
if wallpaper.has_surface(surface) {
wallpaper.draw();
return;
}
}
}
fn transform_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_new_transform: wl_output::Transform,
) {
// do not do anything for now
}
}
impl OutputHandler for Daemon {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
output: wl_output::WlOutput,
) {
if let Some(output_info) = self.output_state.info(&output) {
let surface = self.compositor_state.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.
if let Ok(region) = Region::new(&self.compositor_state) {
surface.set_input_region(Some(region.wl_region()));
}
let layer_surface = self.layer_shell.create_layer_surface(
qh,
surface,
Layer::Background,
Some("swww"),
Some(&output),
);
if !self.initializing {
if let Some(name) = &output_info.name {
let name = name.to_owned();
if let Err(e) = std::thread::Builder::new()
.name("cache loader".to_string())
.stack_size(1 << 14)
.spawn(move || {
// Wait for a bit for the output to be properly configured and stuff
// this is obviously not ideal, but it solves the vast majority of problems
std::thread::sleep(std::time::Duration::from_millis(100));
if let Err(e) = utils::cache::load(&name) {
warn!("failed to load cache: {e}");
}
})
{
warn!("failed to spawn `cache loader` thread: {e}");
}
}
}
debug!("New output: {output_info:?}");
self.wallpapers.push(Arc::new(Wallpaper::new(
output_info,
layer_surface,
Arc::clone(&self.pool),
)));
debug!("Output count: {}", self.wallpapers.len());
}
}
fn update_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
output: wl_output::WlOutput,
) {
if let Some(output_info) = self.output_state.info(&output) {
if let Some(output_size) = output_info.logical_size {
if output_size.0 == 0 || output_size.1 == 0 {
error!(
"output dimensions cannot be '0'. Received: {:#?}",
output_size
);
return;
}
for wallpaper in self.wallpapers.iter_mut() {
if wallpaper.has_id(output_info.id) {
let (width, height) = (
Some(NonZeroI32::new(output_size.0).unwrap()),
Some(NonZeroI32::new(output_size.1).unwrap()),
);
let scale_factor = Some(NonZeroI32::new(output_info.scale_factor).unwrap());
wallpaper.resize(width, height, scale_factor);
return;
}
}
}
}
}
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
output: wl_output::WlOutput,
) {
if let Some(output_info) = self.output_state.info(&output) {
self.wallpapers.retain(|w| !w.has_id(output_info.id));
debug!("Destroyed output: {output_info:?}");
}
}
}
impl ShmHandler for Daemon {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
impl LayerShellHandler for Daemon {
fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, layer: &LayerSurface) {
self.wallpapers
.retain(|w| !w.has_surface(layer.wl_surface()));
}
fn configure(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
layer: &LayerSurface,
_configure: LayerSurfaceConfigure,
_serial: u32,
) {
// we only care about output configs, so we just set the wallpaper as having been
// configured
for w in &mut self.wallpapers {
if w.has_surface(layer.wl_surface()) {
w.configured
.store(true, std::sync::atomic::Ordering::Release);
break;
}
}
}
}
delegate_compositor!(Daemon);
delegate_output!(Daemon);
delegate_shm!(Daemon);
delegate_layer!(Daemon);
delegate_registry!(Daemon);
impl ProvidesRegistryState for Daemon {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState];
}
fn make_logger() {
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(
LevelFilter::Debug,
config,
TerminalMode::Stderr,
ColorChoice::AlwaysAnsi,
)
.expect("Failed to initialize logger. Cancelling...");
}
07070100000010000081A400000000000000000000000165A529730000208B000000000000000000000000000000000000002300000000swww-0.8.2/daemon/src/wallpaper.rsuse utils::ipc::BgImg;
use std::{
num::NonZeroI32,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
},
};
use smithay_client_toolkit::{
output::OutputInfo,
shell::{
wlr_layer::{Anchor, KeyboardInteractivity, LayerSurface},
WaylandSurface,
},
shm::slot::{Slot, SlotPool},
};
use wayland_client::protocol::{wl_shm, wl_surface::WlSurface};
#[derive(Debug)]
struct AnimationState {
id: AtomicUsize,
transition_finished: Arc<AtomicBool>,
}
#[derive(Debug)]
pub struct AnimationToken {
id: usize,
transition_finished: Arc<AtomicBool>,
}
impl AnimationToken {
pub fn transition_finished(&self) -> bool {
self.transition_finished.load(Ordering::Acquire)
}
}
impl Drop for AnimationToken {
fn drop(&mut self) {
self.transition_finished.store(true, Ordering::Release);
}
}
/// Owns all the necessary information for drawing.
struct WallpaperInner {
width: NonZeroI32,
height: NonZeroI32,
scale_factor: NonZeroI32,
slot: Slot,
img: BgImg,
}
pub struct Wallpaper {
output_id: u32,
inner: RwLock<WallpaperInner>,
layer_surface: LayerSurface,
animation_state: AnimationState,
pool: Arc<Mutex<SlotPool>>,
pub configured: AtomicBool,
}
impl Wallpaper {
pub fn new(
output_info: OutputInfo,
layer_surface: LayerSurface,
pool: Arc<Mutex<SlotPool>>,
) -> Self {
let (width, height): (NonZeroI32, NonZeroI32) = if let Some(size) = output_info.logical_size
{
if size.0 == 0 || size.1 == 0 {
(256.try_into().unwrap(), 256.try_into().unwrap())
} else {
(size.0.try_into().unwrap(), size.1.try_into().unwrap())
}
} else {
(256.try_into().unwrap(), 256.try_into().unwrap())
};
let scale_factor = NonZeroI32::new(output_info.scale_factor).unwrap();
let slot = pool
.lock()
.unwrap()
.new_slot(
width.get() as usize
* height.get() as usize
* scale_factor.get() as usize
* scale_factor.get() as usize
* 4,
)
.expect("failed to create slot in pool");
// 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(width.get() as u32, height.get() as u32);
layer_surface
.set_buffer_scale(scale_factor.get() as u32)
.unwrap();
// commit so that the compositor send the initial configuration
layer_surface.commit();
Self {
output_id: output_info.id,
layer_surface,
pool,
inner: RwLock::new(WallpaperInner {
width,
height,
scale_factor,
slot,
img: BgImg::Color([0, 0, 0]),
}),
animation_state: AnimationState {
id: AtomicUsize::new(0),
transition_finished: Arc::new(AtomicBool::new(false)),
},
configured: AtomicBool::new(false)
}
}
#[inline]
pub fn has_id(&self, id: u32) -> bool {
self.output_id == id
}
#[inline]
pub fn has_animation_id(&self, token: &AnimationToken) -> bool {
self.animation_state
.id
.load(std::sync::atomic::Ordering::Acquire)
== token.id
}
#[inline]
pub fn has_surface(&self, surface: &WlSurface) -> bool {
self.layer_surface.wl_surface() == surface
}
pub fn get_dimensions(&self) -> (u32, u32) {
let (inner, _) = self.lock();
let width = inner.width.get() as u32;
let height = inner.height.get() as u32;
let scale_factor = inner.scale_factor.get() as u32;
(width * scale_factor, height * scale_factor)
}
#[inline]
fn lock(&self) -> (RwLockReadGuard<WallpaperInner>, MutexGuard<SlotPool>) {
(self.inner.read().unwrap(), self.pool.lock().unwrap())
}
#[inline]
fn lock_inner_mut(&self) -> (RwLockWriteGuard<WallpaperInner>, MutexGuard<SlotPool>) {
(self.inner.write().unwrap(), self.pool.lock().unwrap())
}
pub fn canvas_change<F, T>(&self, f: F) -> T
where
F: FnOnce(&mut [u8]) -> T,
{
let mut nano_sleep = 2000000; // start at 2 ms, half it every loop
loop {
{
let (inner, mut pool) = self.lock();
if let Some(canvas) = inner.slot.canvas(&mut pool) {
log::debug!("got canvas! - output {}", self.output_id);
return f(canvas);
}
}
log::debug!("failed to get canvas - output {}", self.output_id);
// sleep to mitigate busy waiting
std::thread::sleep(std::time::Duration::from_nanos(nano_sleep));
nano_sleep /= 2;
}
}
#[inline]
pub fn get_img_info(&self) -> BgImg {
self.lock().0.img.clone()
}
#[inline]
pub fn create_animation_token(&self) -> AnimationToken {
let id = self.animation_state.id.load(Ordering::Acquire);
AnimationToken {
id,
transition_finished: Arc::clone(&self.animation_state.transition_finished),
}
}
/// This will stop all animations with the current id
#[inline]
pub fn inc_animation_id(&self) {
self.animation_state.id.fetch_add(1, Ordering::AcqRel);
self.animation_state
.transition_finished
.store(false, Ordering::Release);
}
pub fn clear(&self, color: [u8; 3]) {
self.canvas_change(|canvas| {
for pixel in canvas.chunks_exact_mut(4) {
pixel[2] = color[0];
pixel[1] = color[1];
pixel[0] = color[2];
}
});
}
pub fn set_img_info(&self, img_info: BgImg) {
log::debug!("output {} - drawing: {}", self.output_id, img_info);
self.lock_inner_mut().0.img = img_info;
}
pub fn draw(&self) {
let (inner, mut pool) = self.lock();
let width = inner.width.get() * inner.scale_factor.get();
let height = inner.height.get() * inner.scale_factor.get();
let stride = width * 4;
let buf = pool
.create_buffer_in(&inner.slot, width, height, stride, wl_shm::Format::Xrgb8888)
.unwrap();
drop(inner);
let surface = self.layer_surface.wl_surface();
buf.attach_to(surface).unwrap();
surface.damage_buffer(0, 0, width, height);
surface.commit();
}
pub fn resize(
&self,
width: Option<NonZeroI32>,
height: Option<NonZeroI32>,
scale_factor: Option<NonZeroI32>,
) {
if let Some(s) = scale_factor {
self.layer_surface.set_buffer_scale(s.get() as u32).unwrap();
}
let (mut inner, mut pool) = self.lock_inner_mut();
let width = width.unwrap_or(inner.width);
let height = height.unwrap_or(inner.height);
let scale_factor = scale_factor.unwrap_or(inner.scale_factor);
if (width, height, scale_factor) == (inner.width, inner.height, inner.scale_factor) {
return;
}
self.inc_animation_id();
inner.width = width;
inner.height = height;
inner.scale_factor = scale_factor;
inner.slot = pool
.new_slot(
inner.width.get() as usize
* inner.height.get() as usize
* inner.scale_factor.get() as usize
* inner.scale_factor.get() as usize
* 4,
)
.expect("failed to create slot");
self.layer_surface
.set_size(inner.width.get() as u32, inner.height.get() as u32);
inner.img = BgImg::Color([0, 0, 0]);
self.layer_surface.commit();
self.configured.store(false, Ordering::Release);
}
}
07070100000011000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000000F00000000swww-0.8.2/doc07070100000012000081A400000000000000000000000165A529730000000B000000000000000000000000000000000000001A00000000swww-0.8.2/doc/.gitignoregenerated/
07070100000013000081ED00000000000000000000000165A52973000002A2000000000000000000000000000000000000001600000000swww-0.8.2/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
07070100000014000081A400000000000000000000000165A52973000002E2000000000000000000000000000000000000002600000000swww-0.8.2/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)
07070100000015000081A400000000000000000000000165A52973000002DC000000000000000000000000000000000000002000000000swww-0.8.2/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)
07070100000016000081A400000000000000000000000165A52973000003CE000000000000000000000000000000000000002100000000swww-0.8.2/doc/swww-daemon.1.scdswww-daemon(1)
# NAME
swww-daemon
# DESCRIPTION
The *swww-daemon* will run continuously, waiting for commands in
_$XDG_RUNTIME_DIR/swww.socket_(or _/tmp/swww/swww.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.
*There is no reason for you to run the swww-daemon manually*. The daemon should
be started through *swww init* and killed through *swww kill*. Running the
daemon manually would only help for debugging, or developing *swww*. *swww init*
has the benefits that it checks to see if another instance is already running,
and it waits for the daemon to be ready, so something like
```
swww init && swww img bg.png
```
works, but
```
swww-daemon &
swww img bg.png
```
*can potentially fail*, because the daemon might still not be ready by the time
the client sends the image.
Bottom line is: just use *swww init* to initialize the daemon.
# SEE ALSO
*swww-init*(1)
07070100000017000081A400000000000000000000000165A5297300001C28000000000000000000000000000000000000001E00000000swww-0.8.2/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_ is an alias to _simple_, that also sets the _transition-step_ to
255. This has the effect of the transition completing 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)
07070100000018000081A400000000000000000000000165A52973000003B1000000000000000000000000000000000000001F00000000swww-0.8.2/doc/swww-init.1.scdswww-init(1)
# NAME
swww-init
# SYNOPSIS
*swww init* [--no-daemon]
# 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>`
*-h*, *--help*
Print help (see a summary with '-h')
# DESCRIPTION
Initializes the daemon. This is the recommended way of doing it, since we make
sure to check if another instance is already running, and wait until the daemon
is 100% ready to receive requests.
# SEE ALSO
*swww-daemon*(1)
07070100000019000081A400000000000000000000000165A529730000022B000000000000000000000000000000000000001F00000000swww-0.8.2/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)
0707010000001A000081A400000000000000000000000165A52973000002E9000000000000000000000000000000000000002000000000swww-0.8.2/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
0707010000001B000081A400000000000000000000000165A5297300000695000000000000000000000000000000000000001A00000000swww-0.8.2/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
*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.socket_ or _/tmp/swww/swww.socket_, if
$XDG_RUNTIME_DIR does not exist.
- 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)
0707010000001C000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000001B00000000swww-0.8.2/example_scripts0707010000001D000081A400000000000000000000000165A52973000001BF000000000000000000000000000000000000002500000000swww-0.8.2/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)
0707010000001E000081ED00000000000000000000000165A52973000003CB000000000000000000000000000000000000004100000000swww-0.8.2/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
0707010000001F000081ED00000000000000000000000165A52973000002DB000000000000000000000000000000000000002D00000000swww-0.8.2/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" \
| 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
07070100000020000081ED00000000000000000000000165A52973000003BF000000000000000000000000000000000000002D00000000swww-0.8.2/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
07070100000021000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000000F00000000swww-0.8.2/src07070100000022000081A400000000000000000000000165A5297300004372000000000000000000000000000000000000001600000000swww-0.8.2/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)]
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),
///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),
/// 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,
},
///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 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: u8,
///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: u8,
///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]);
}
}
07070100000023000081A400000000000000000000000165A529730000384E000000000000000000000000000000000000001A00000000swww-0.8.2/src/imgproc.rsuse fast_image_resize::{FilterType, PixelType, Resizer};
use image::{
codecs::{gif::GifDecoder, webp::WebPDecoder},
AnimationDecoder, DynamicImage, Frames, ImageFormat, RgbImage,
};
use std::{
fs::File,
io::Stdin,
io::{stdin, BufReader, Read},
num::NonZeroU32,
path::Path,
time::Duration,
};
use utils::{
comp_decomp::BitPack,
ipc::{self, Coord, Position},
};
use crate::cli::ResizeStrategy;
use super::cli;
pub enum ImgBuf {
Stdin(BufReader<Stdin>),
File(image::io::Reader<BufReader<File>>),
}
impl ImgBuf {
/// Create a new ImgBuf from a given path. Use - for Stdin
pub fn new(path: &Path) -> Result<Self, String> {
Ok(if let Some("-") = path.to_str() {
let reader = BufReader::new(stdin());
Self::Stdin(reader)
} else {
let reader = image::io::Reader::open(path)
.map_err(|e| format!("failed to open image: {e}"))?
.with_guessed_format()
.map_err(|e| format!("failed to detect the image's format: {e}"))?;
Self::File(reader)
})
}
/// Guess the format of the ImgBuf
fn format(&self) -> Option<ImageFormat> {
match self {
ImgBuf::Stdin(_) => None, // not seekable
ImgBuf::File(reader) => reader.format(),
}
}
/// Is this ImgBuf an animated image?
pub fn is_animated(&self) -> bool {
matches!(self.format(), Some(ImageFormat::Gif | ImageFormat::WebP))
}
/// Decode the ImgBuf into am RgbImage
pub fn decode(self) -> Result<RgbImage, String> {
Ok(match self {
ImgBuf::Stdin(mut reader) => {
let mut buffer = Vec::new();
reader
.read_to_end(&mut buffer)
.map_err(|e| format!("failed to read stdin: {e}"))?;
image::load_from_memory(&buffer)
}
ImgBuf::File(reader) => reader.decode(),
}
.map_err(|e| format!("failed to decode image: {e}"))?
.into_rgb8())
}
/// Convert this ImgBuf into Frames
pub fn into_frames<'a>(self) -> Result<Frames<'a>, String> {
fn create_decoder<'a>(
img_format: Option<ImageFormat>,
reader: impl Read + 'a,
) -> Result<Frames<'a>, String> {
match img_format {
Some(ImageFormat::Gif) => Ok(GifDecoder::new(reader)
.map_err(|e| format!("failed to decode gif during animation: {e}"))?
.into_frames()),
Some(ImageFormat::WebP) => Ok(WebPDecoder::new(reader)
.map_err(|e| format!("failed to decode webp during animation: {e}"))?
.into_frames()),
_ => Err(format!("requested format has no decoder: {img_format:#?}")),
}
}
let img_format = self.format();
match self {
ImgBuf::Stdin(reader) => create_decoder(img_format, reader),
ImgBuf::File(reader) => create_decoder(img_format, reader.into_inner()),
}
}
}
#[inline]
pub fn frame_to_rgb(frame: image::Frame) -> RgbImage {
DynamicImage::ImageRgba8(frame.into_buffer()).into_rgb8()
}
pub fn compress_frames(
mut frames: Frames,
dim: (u32, u32),
filter: FilterType,
resize: ResizeStrategy,
color: &[u8; 3],
) -> Result<Vec<(BitPack, Duration)>, String> {
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 first_duration = Duration::from_millis((first_duration.0 / first_duration.1).into());
let first_img = match resize {
ResizeStrategy::No => img_pad(frame_to_rgb(first), dim, color)?,
ResizeStrategy::Crop => img_resize_crop(frame_to_rgb(first), dim, filter)?,
ResizeStrategy::Fit => img_resize_fit(frame_to_rgb(first), dim, filter, color)?,
};
let mut canvas: Option<Vec<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 = match resize {
ResizeStrategy::No => img_pad(frame_to_rgb(frame), dim, color)?,
ResizeStrategy::Crop => img_resize_crop(frame_to_rgb(frame), dim, filter)?,
ResizeStrategy::Fit => img_resize_fit(frame_to_rgb(frame), dim, filter, color)?,
};
compressed_frames.push((
BitPack::pack(canvas.as_ref().unwrap_or(&first_img), &img)?,
duration,
));
canvas = Some(img);
}
//Add the first frame we got earlier:
compressed_frames.push((BitPack::pack(&canvas.unwrap(), &first_img)?, 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(
mut img: RgbImage,
dimensions: (u32, u32),
color: &[u8; 3],
) -> Result<Vec<u8>, String> {
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 * 3);
let img = image::imageops::crop(&mut img, 0, 0, dimensions.0, dimensions.1).to_image();
let (img_w, img_h) = img.dimensions();
let (img_w, img_h) = (img_w as usize, img_h as usize);
let raw_img = img.into_vec();
for _ in 0..(((padded_h - img_h) / 2) * padded_w) {
padded.push(color[2]);
padded.push(color[1]);
padded.push(color[0]);
}
// 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.push(color[2]);
padded.push(color[1]);
padded.push(color[0]);
}
for pixel in raw_img[(row * img_w * 3)..((row + 1) * img_w * 3)].chunks_exact(3) {
padded.push(pixel[2]);
padded.push(pixel[1]);
padded.push(pixel[0]);
}
for _ in 0..right_border_w {
padded.push(color[2]);
padded.push(color[1]);
padded.push(color[0]);
}
}
while padded.len() < (padded_h * padded_w * 3) {
padded.push(color[2]);
padded.push(color[1]);
padded.push(color[0]);
}
Ok(padded)
}
/// Convert an RGB &[u8] to BRG in-place by swapping bytes
#[inline]
fn rgb_to_brg(rgb: &mut [u8]) {
for pixel in rgb.chunks_exact_mut(3) {
pixel.swap(0, 2);
}
}
/// Resize an image to fit within the given dimensions, covering as much space as possible without
/// cropping.
pub fn img_resize_fit(
img: RgbImage,
dimensions: (u32, u32),
filter: FilterType,
padding_color: &[u8; 3],
) -> Result<Vec<u8>, String> {
let (width, height) = dimensions;
let (img_w, img_h) = img.dimensions();
if (img_w, img_h) != (width, height) {
// if our image is already scaled to fit, skip resizing it and just pad it directly
if img_w == width || img_h == height {
return img_pad(img, dimensions, padding_color);
}
let ratio = width as f32 / height as f32;
let img_r = img_w as f32 / img_h as f32;
let (trg_w, trg_h) = if ratio > img_r {
let scale = height as f32 / img_h as f32;
((img_w as f32 * scale) as u32, height)
} else {
let scale = width as f32 / img_w as f32;
(width, (img_h as f32 * scale) as u32)
};
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_w).unwrap(),
NonZeroU32::new(img_h).unwrap(),
img.into_raw(),
PixelType::U8x3,
) {
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, PixelType::U8x3);
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());
}
img_pad(
image::RgbImage::from_raw(trg_w, trg_h, dst.into_vec()).unwrap(),
dimensions,
padding_color,
)
} else {
let mut res = img.into_vec();
// The ARGB is 'little endian', so here we must put the order
// of bytes 'in reverse', so it needs to be BGRA.
rgb_to_brg(&mut res);
Ok(res)
}
}
pub fn img_resize_crop(
img: RgbImage,
dimensions: (u32, u32),
filter: FilterType,
) -> Result<Vec<u8>, String> {
let (width, height) = dimensions;
let (img_w, img_h) = img.dimensions();
let mut resized_img = if (img_w, img_h) != (width, height) {
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_w).unwrap(),
NonZeroU32::new(img_h).unwrap(),
img.into_raw(),
PixelType::U8x3,
) {
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, PixelType::U8x3);
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()
} else {
img.into_vec()
};
// The ARGB is 'little endian', so here we must put the order
// of bytes 'in reverse', so it needs to be BGRA.
rgb_to_brg(&mut resized_img);
Ok(resized_img)
}
pub fn make_transition(img: &cli::Img) -> ipc::Transition {
let mut angle = img.transition_angle;
let mut 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 => {
step = u8::MAX;
ipc::TransitionType::Simple
}
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,
}
}
07070100000024000081A400000000000000000000000165A5297300003BA9000000000000000000000000000000000000001700000000swww-0.8.2/src/main.rsuse clap::Parser;
use std::{os::unix::net::UnixStream, path::PathBuf, process::Stdio, time::Duration};
use utils::{
cache,
ipc::{self, get_socket_path, read_socket, AnimationRequest, Answer, ArchivedAnswer, 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, .. } = &swww {
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)?;
if *no_daemon {
return Ok(());
}
}
process_swww_args(&swww)?;
Ok(())
}
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(5, 100)?;
request.send(&socket)?;
let bytes = read_socket(&socket)?;
drop(socket);
match Answer::receive(&bytes) {
ArchivedAnswer::Err(msg) => return Err(msg.to_string()),
ArchivedAnswer::Info(info) => info.iter().for_each(|i| println!("{}", i)),
ArchivedAnswer::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:?}"
));
}
}
ArchivedAnswer::Init(configured) => {
let mut configured = *configured;
while !configured {
std::thread::sleep(Duration::from_millis(1));
let socket = connect_to_socket(5, 100)?;
Request::Init.send(&socket)?;
let bytes = read_socket(&socket)?;
let answer = Answer::receive(&bytes);
if let ArchivedAnswer::Init(c) = answer {
configured = *c;
} else {
return Err("Daemon did not return Answer::Init, as expected".to_string());
}
}
if let Swww::Init { no_cache, .. } = args {
if *no_cache {
return Ok(());
}
let (_, outputs) = get_dimensions_and_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: u8::MAX,
transition_duration: 0.0,
transition_fps: u8::MAX,
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(())
}
fn make_request(args: &Swww) -> Result<Option<Request>, String> {
match args {
Swww::Clear(c) => Ok(Some(Request::Clear(ipc::Clear {
color: c.color,
outputs: split_cmdline_outputs(&c.outputs),
}))),
Swww::ClearCache => {
cache::clean()?;
Ok(None)
}
Swww::Img(img) => {
let requested_outputs = split_cmdline_outputs(&img.outputs);
let (dims, outputs) = get_dimensions_and_outputs(&requested_outputs)?;
let imgbuf = ImgBuf::new(&img.path)?;
if imgbuf.is_animated() {
match std::thread::scope::<_, Result<_, String>>(|s1| {
let animations = s1.spawn(|| make_animation_request(img, &dims, &outputs));
let first_frame = imgbuf
.into_frames()?
.next()
.ok_or("missing first frame".to_owned())?
.map_err(|e| format!("unable to decode first frame: {e}"))?;
let img_request =
make_img_request(img, frame_to_rgb(first_frame), &dims, &outputs)?;
let animations = animations.join().unwrap_or_else(|e| Err(format!("{e:?}")));
let socket = connect_to_socket(5, 100)?;
Request::Img(img_request).send(&socket)?;
let bytes = read_socket(&socket)?;
drop(socket);
if let ArchivedAnswer::Err(e) = Answer::receive(&bytes) {
return Err(format!("daemon error when sending image: {e}"));
}
animations
}) {
Ok(animations) => Ok(Some(Request::Animation(animations))),
Err(e) => Err(format!("failed to create animated request: {e}")),
}
} else {
let img_raw = imgbuf.decode()?;
Ok(Some(Request::Img(make_img_request(
img, img_raw, &dims, &outputs,
)?)))
}
}
Swww::Init { .. } => Ok(Some(Request::Init)),
Swww::Kill => Ok(Some(Request::Kill)),
Swww::Query => Ok(Some(Request::Query)),
}
}
fn make_img_request(
img: &cli::Img,
img_raw: image::RgbImage,
dims: &[(u32, u32)],
outputs: &[Vec<String>],
) -> Result<ipc::ImageRequest, String> {
let transition = make_transition(img);
let mut unique_requests = Vec::with_capacity(dims.len());
for (dim, outputs) in dims.iter().zip(outputs) {
unique_requests.push((
ipc::Img {
img: match img.resize {
ResizeStrategy::No => img_pad(img_raw.clone(), *dim, &img.fill_color)?,
ResizeStrategy::Crop => {
img_resize_crop(img_raw.clone(), *dim, make_filter(&img.filter))?
}
ResizeStrategy::Fit => img_resize_fit(
img_raw.clone(),
*dim,
make_filter(&img.filter),
&img.fill_color,
)?,
}
.into_boxed_slice(),
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}"));
}
}
},
},
outputs.to_owned().into_boxed_slice(),
));
}
Ok((transition, unique_requests.into_boxed_slice()))
}
#[allow(clippy::type_complexity)]
fn get_dimensions_and_outputs(
requested_outputs: &[String],
) -> Result<(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(5, 100)?;
Request::Query.send(&socket)?;
let bytes = read_socket(&socket)?;
drop(socket);
let answer = Answer::receive(&bytes);
match answer {
ArchivedAnswer::Info(infos) => {
for info in infos.iter() {
let info_img = info.img.de();
let name = info.name.to_string();
if !requested_outputs.is_empty() && !requested_outputs.contains(&name) {
continue;
}
let real_dim = (
info.dim.0 * info.scale_factor as u32,
info.dim.1 * info.scale_factor as u32,
);
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);
}
}
if outputs.is_empty() {
Err("none of the requested outputs are valid".to_owned())
} else {
Ok((dims, outputs))
}
}
ArchivedAnswer::Err(e) => Err(format!("daemon error when sending query: {e}")),
_ => unreachable!(),
}
}
fn make_animation_request(
img: &cli::Img,
dims: &[(u32, u32)],
outputs: &[Vec<String>],
) -> Result<AnimationRequest, String> {
let filter = make_filter(&img.filter);
let mut animations = Vec::with_capacity(dims.len());
for (dim, outputs) in dims.iter().zip(outputs) {
//TODO: make cache work for all resize strategies
if img.resize == ResizeStrategy::Crop {
match cache::load_animation_frames(&img.path, *dim) {
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 imgbuf = ImgBuf::new(&img.path)?;
let animation = ipc::Animation {
path: img.path.to_string_lossy().to_string(),
dimensions: *dim,
animation: compress_frames(
imgbuf.into_frames()?,
*dim,
filter,
img.resize,
&img.fill_color,
)?
.into_boxed_slice(),
};
animations.push((animation, outputs.to_owned().into_boxed_slice()));
}
Ok(animations.into_boxed_slice())
}
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) -> Result<(), String> {
let mut cmd = std::process::Command::new("swww-daemon");
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}")),
}
}
}
/// We make sure the Stream is always set to blocking mode
///
/// * `tries` - how make times to attempt the connection
/// * `interval` - how long to wait between attempts, in milliseconds
fn connect_to_socket(tries: u8, interval: u64) -> Result<UnixStream, String> {
//Make sure we try at least once
let tries = if tries == 0 { 1 } else { tries };
let path = get_socket_path();
let mut error = None;
for _ in 0..tries {
match UnixStream::connect(&path) {
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}"))
}
fn is_daemon_running() -> Result<bool, String> {
let proc = PathBuf::from("/proc");
let entries = match proc.read_dir() {
Ok(e) => e,
Err(e) => return Err(e.to_string()),
};
for entry in entries.flatten() {
let dirname = entry.file_name();
if let Ok(pid) = dirname.to_string_lossy().parse::<u32>() {
if std::process::id() == pid {
continue;
}
let mut entry_path = entry.path();
entry_path.push("cmdline");
if let Ok(cmd) = std::fs::read_to_string(entry_path) {
let mut args = cmd.split(&[' ', '\0']);
if let Some(arg0) = args.next() {
if arg0.ends_with("swww-daemon") {
return Ok(true);
}
}
}
}
}
Ok(false)
}
07070100000025000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000001100000000swww-0.8.2/tests07070100000026000081A400000000000000000000000165A529730000136A000000000000000000000000000000000000002000000000swww-0.8.2/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 {
Command::cargo_bin("swww-daemon").unwrap()
}
#[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(100));
}
/// 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();
}
07070100000027000081A400000000000000000000000165A5297300000605000000000000000000000000000000000000002000000000swww-0.8.2/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",
"--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}"),
},
}
}
07070100000028000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000001100000000swww-0.8.2/utils07070100000029000081A400000000000000000000000165A5297300000122000000000000000000000000000000000000001C00000000swww-0.8.2/utils/Cargo.toml[package]
name = "utils"
version = "0.8.2"
authors = ["Leonardo Gibrowski FaƩ <leonardo.fae44@gmail.com>"]
edition = "2021"
[dependencies]
lazy_static = "1.4"
lzzzz = "=1.0.4"
rkyv = "0.7"
[dev-dependencies]
rand = "0.8"
criterion = "0.5"
[[bench]]
name = "compression"
harness = false
0707010000002A000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000001900000000swww-0.8.2/utils/benches0707010000002B000081A400000000000000000000000165A529730000067B000000000000000000000000000000000000002800000000swww-0.8.2/utils/benches/compression.rsuse criterion::{black_box, criterion_group, criterion_main, Criterion};
use utils::comp_decomp::BitPack;
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 comp = c.benchmark_group("compression");
comp.bench_function("Full", |b| {
b.iter(|| {
black_box(BitPack::pack(&prev, &cur).ok());
})
});
comp.finish();
let mut decomp = c.benchmark_group("decompression");
let bitpack = BitPack::pack(&prev, &cur).unwrap();
let mut canvas = buf_from(&prev);
decomp.bench_function("Full", |b| {
b.iter(|| {
black_box(bitpack.unpack(&mut canvas));
})
});
decomp.finish();
}
criterion_group!(compression, compression_and_decompression);
criterion_main!(compression);
0707010000002C000041ED00000000000000000000000265A5297300000000000000000000000000000000000000000000001500000000swww-0.8.2/utils/src0707010000002D000081A400000000000000000000000165A5297300001866000000000000000000000000000000000000001E00000000swww-0.8.2/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 rkyv::{Deserialize, Infallible};
use crate::ipc::Animation;
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);
let mut filepath = cache_dir()?;
filepath.push(&filename);
let bytes = match rkyv::to_bytes::<_, 1024>(animation) {
Ok(bytes) => bytes,
Err(e) => return Err(format!("Failed to serialize request: {e}")),
};
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),
) -> Result<Option<Animation>, String> {
let filename = animation_filename(path, dimensions);
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 = unsafe { rkyv::archived_root::<Animation>(&buf) };
let frames: Animation = frames.deserialize(&mut Infallible).unwrap();
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(_) => Ok(()),
Err(e) => Err(format!("failed to spawn child process: {e}")),
}
}
pub fn clean() -> Result<(), String> {
std::fs::remove_dir(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..] != 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)) -> PathBuf {
format!(
"{}__{}x{}_v{}",
path.to_string_lossy().replace('/', "_"),
dimensions.0,
dimensions.1,
env!("CARGO_PKG_VERSION"),
)
.into()
}
0707010000002E000081A400000000000000000000000165A5297300002CED000000000000000000000000000000000000002400000000swww-0.8.2/utils/src/comp_decomp.rs//! # Compression Strategy
//!
//! For every pixel, we drop the alpha part; 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
//!
use lzzzz::lz4f;
use rkyv::{Archive, Deserialize, Serialize};
lazy_static::lazy_static! {
static ref COMPRESSION_PREFERENCES: lz4f::Preferences = lz4f::PreferencesBuilder::new()
.block_size(lz4f::BlockSize::Max256KB)
.compression_level(9)
.build();
}
/// This calculates the difference between the current(cur) frame and the next(goal).
/// The closure you pass is run at every difference. It dictates the update logic of the current
/// frame. With that, you can control whether all different pixels changed are updated, or only the
/// ones at a certain position. It is meant to be used primarily when writing transitions
fn pack_bytes(cur: &[u8], goal: &[u8]) -> Box<[u8]> {
let mut v = Vec::with_capacity(goal.len());
let mut iter = zip_eq(pixels(cur), pixels(goal));
let mut to_add = Vec::with_capacity(333); // 100 pixels
while let Some((mut cur, mut goal)) = iter.next() {
let mut equals = 0;
while cur == goal {
equals += 1;
match iter.next() {
None => return v.into_boxed_slice(),
Some((c, g)) => {
cur = c;
goal = g;
}
}
}
let mut diffs = 0;
while cur != goal {
to_add.extend_from_slice(goal);
diffs += 1;
match iter.next() {
None => break,
Some((c, g)) => {
cur = c;
goal = g;
}
}
}
let j = v.len() + equals / 255;
v.resize(1 + v.len() + equals / 255 + diffs / 255, 255);
v[j] = (equals % 255) as u8;
v.push((diffs % 255) as u8);
v.append(&mut to_add);
}
v.push(0);
v.into_boxed_slice()
}
fn unpack_bytes(buf: &mut [u8], diff: &[u8]) {
let buf_chunks = pixels_mut(buf);
let mut diff_idx = 0;
let mut pix_idx = 0;
while diff_idx < diff.len() - 1 {
while diff[diff_idx] == u8::MAX {
pix_idx += u8::MAX as usize;
diff_idx += 1;
}
pix_idx += diff[diff_idx] as usize;
diff_idx += 1;
let mut to_cpy = 0;
while diff[diff_idx] == u8::MAX {
to_cpy += u8::MAX as usize;
diff_idx += 1;
}
to_cpy += diff[diff_idx] as usize;
diff_idx += 1;
for _ in 0..to_cpy {
unsafe {
buf_chunks
.get_unchecked_mut(pix_idx)
.clone_from_slice(diff.get_unchecked(diff_idx..diff_idx + 4));
}
diff_idx += 3;
pix_idx += 1;
}
pix_idx += 1;
}
}
/// This struct represents the cached difference between the previous frame and the next
#[derive(Archive, Serialize, Deserialize)]
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,
}
impl BitPack {
/// Compresses a frame of animation by getting the difference between the previous and the
/// current frame.
/// IMPORTANT: this will change `prev` into `cur`, that's why it needs to be 'mut'
pub fn pack(prev: &[u8], cur: &[u8]) -> Result<Self, String> {
let bit_pack = pack_bytes(prev, cur);
if bit_pack.is_empty() {
return Ok(BitPack {
inner: Box::new([]),
expected_buf_size: (cur.len() / 3) * 4,
});
}
let mut v = Vec::with_capacity(bit_pack.len() / 2);
match lzzzz::lz4f::compress_to_vec(&bit_pack, &mut v, &COMPRESSION_PREFERENCES) {
Ok(_) => Ok(BitPack {
inner: v.into_boxed_slice(),
expected_buf_size: (cur.len() / 3) * 4,
}),
Err(e) => Err(e.to_string()),
}
}
///return whether unpacking was successful. Note it can only fail if `buf.len() !=
///expected_buf_size`
#[must_use]
pub fn unpack(&self, buf: &mut [u8]) -> bool {
if buf.len() == self.expected_buf_size {
if !self.inner.is_empty() {
let mut v = Vec::with_capacity(self.inner.len() * 3);
// Note: panics will never happen because BitPacked is *always* only produced
// with correct lz4 compression
lz4f::decompress_to_vec(&self.inner, &mut v).unwrap();
unpack_bytes(buf, &v);
}
true
} else {
false
}
}
}
impl ArchivedBitPack {
///return whether unpacking was successful. Note it can only fail if `buf.len() !=
///expected_buf_size`
#[must_use]
pub fn unpack(&self, buf: &mut [u8]) -> bool {
if buf.len()
== self
.expected_buf_size
.deserialize(&mut rkyv::Infallible)
.unwrap()
{
if !self.inner.is_empty() {
let mut v = Vec::with_capacity(self.inner.len() * 3);
// Note: panics will never happen because BitPacked is *always* only produced
// with correct lz4 compression
lz4f::decompress_to_vec(&self.inner, &mut v).unwrap();
unpack_bytes(buf, &v);
}
true
} else {
false
}
}
}
// Utility functions. Largely copied from the Itertools and Bytemuck crates
/// An iterator which iterates two other iterators simultaneously
/// Copy pasted from the Iterator crate, and adapted for our purposes
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
struct ZipEq<'a, I> {
a: std::slice::Iter<'a, I>,
b: std::slice::Iter<'a, I>,
}
fn zip_eq<'a, I>(i: &'a [I], j: &'a [I]) -> ZipEq<'a, I> {
if i.len() != j.len() {
unreachable!(
"Iterators of zip_eq have different sizes: {}, {}",
i.len(),
j.len()
);
}
ZipEq {
a: i.iter(),
b: j.iter(),
}
}
impl<'a, I> Iterator for ZipEq<'a, I> {
type Item = (&'a I, &'a I);
fn next(&mut self) -> Option<Self::Item> {
match (self.a.next(), self.b.next()) {
(None, None) => None,
(Some(a), Some(b)) => Some((a, b)),
_ => unsafe { std::hint::unreachable_unchecked() },
}
}
}
// The functions below were copy pasted and adapted from the bytemuck crate:
#[inline]
fn pixels(img: &[u8]) -> &[[u8; 3]] {
if img.len() % 3 != 0 {
unreachable!("Calling pixels with a wrongly formatted image");
}
unsafe { core::slice::from_raw_parts(img.as_ptr().cast::<[u8; 3]>(), img.len() / 3) }
}
#[inline]
fn pixels_mut(img: &mut [u8]) -> &mut [[u8; 4]] {
if img.len() % 4 != 0 {
unreachable!("Calling pixels_mut with a wrongly formatted image");
}
unsafe { core::slice::from_raw_parts_mut(img.as_ptr() as *mut [u8; 4], img.len() / 4) }
}
#[cfg(test)]
mod tests {
use super::BitPack;
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]
//Use this when annoying problems show up
fn should_compress_and_decompress_to_same_info_small() {
let frame1 = [1, 2, 3, 4, 5, 6];
let frame2 = [1, 2, 3, 6, 5, 4];
let compressed = BitPack::pack(&frame1, &frame2).unwrap();
let mut buf = buf_from(&frame1);
assert!(compressed.unpack(&mut buf));
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 should_compress_and_decompress_to_same_info() {
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);
compressed.push(BitPack::pack(original.last().unwrap(), &original[0]).unwrap());
for i in 1..20 {
compressed.push(BitPack::pack(&original[i - 1], &original[i]).unwrap());
}
let mut buf = buf_from(original.last().unwrap());
for i in 0..20 {
assert!(compressed[i].unpack(&mut buf));
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 should_compress_and_decompress_to_same_info_with_equal_data() {
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..2000 {
v.push(random::<u8>());
}
for i in 0..1000 {
v.push((i % 255) as u8);
}
original.push(v);
}
let mut compressed = Vec::with_capacity(20);
compressed.push(BitPack::pack(original.last().unwrap(), &original[0]).unwrap());
for i in 1..20 {
compressed.push(BitPack::pack(&original[i - 1], &original[i]).unwrap());
}
let mut buf = buf_from(original.last().unwrap());
for i in 0..20 {
assert!(compressed[i].unpack(&mut buf));
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;
}
}
}
}
}
0707010000002F000081A400000000000000000000000165A5297300002608000000000000000000000000000000000000001C00000000swww-0.8.2/utils/src/ipc.rsuse rkyv::{Archive, Deserialize, Serialize};
use std::{
fmt,
io::{BufReader, BufWriter, Read, Write},
os::unix::net::UnixStream,
path::{Path, PathBuf},
time::Duration,
};
use crate::{cache, comp_decomp::BitPack};
#[derive(PartialEq, Archive, Serialize)]
#[archive_attr(derive(Clone))]
pub enum Coord {
Pixel(f32),
Percent(f32),
}
#[derive(PartialEq, Archive, Serialize)]
#[archive_attr(derive(Clone))]
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)
}
}
impl ArchivedPosition {
#[must_use]
pub fn to_pixel(&self, dim: (u32, u32), invert_y: bool) -> (f32, f32) {
let x = match self.x {
ArchivedCoord::Pixel(x) => x,
ArchivedCoord::Percent(x) => x * dim.0 as f32,
};
let y = match self.y {
ArchivedCoord::Pixel(y) => {
if invert_y {
dim.1 as f32 - y
} else {
y
}
}
ArchivedCoord::Percent(y) => {
if invert_y {
(1.0 - y) * dim.1 as f32
} else {
y * dim.1 as f32
}
}
};
(x, y)
}
}
#[derive(PartialEq, Clone, Archive, Serialize, Deserialize)]
#[archive_attr(derive(PartialEq))]
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}",),
}
}
}
impl ArchivedBgImg {
/// Deserialized the archived bg img
#[must_use]
pub fn de(&self) -> BgImg {
self.deserialize(&mut rkyv::Infallible).unwrap()
}
}
impl fmt::Display for ArchivedBgImg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArchivedBgImg::Color(color) => {
write!(f, "color: {:02X}{:02X}{:02X}", color[0], color[1], color[2])
}
ArchivedBgImg::Img(p) => write!(f, "image: {p}",),
}
}
}
#[derive(Clone, Archive, Serialize)]
pub struct BgInfo {
pub name: String,
pub dim: (u32, u32),
pub scale_factor: i32,
pub img: BgImg,
}
impl BgInfo {
#[must_use]
pub fn real_dim(&self) -> (u32, u32) {
(
self.dim.0 * self.scale_factor as u32,
self.dim.1 * self.scale_factor as u32,
)
}
}
impl fmt::Display for ArchivedBgInfo {
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(Archive, Serialize)]
#[archive_attr(derive(Clone))]
pub enum TransitionType {
Simple,
Fade,
Outer,
Wipe,
Grow,
Wave,
}
#[derive(Archive, Serialize)]
#[archive_attr(derive(Clone))]
pub struct Transition {
pub transition_type: TransitionType,
pub duration: f32,
pub step: u8,
pub fps: u8,
pub angle: f64,
pub pos: Position,
pub bezier: (f32, f32, f32, f32),
pub wave: (f32, f32),
pub invert_y: bool,
}
#[derive(Archive, Serialize)]
pub struct Clear {
pub color: [u8; 3],
pub outputs: Box<[String]>,
}
#[derive(Archive, Serialize)]
pub struct Img {
pub path: String,
pub img: Box<[u8]>,
}
#[derive(Archive, Serialize, Deserialize)]
pub struct Animation {
pub animation: Box<[(BitPack, Duration)]>,
pub path: String,
pub dimensions: (u32, u32),
}
pub type AnimationRequest = Box<[(Animation, Box<[String]>)]>;
pub type ImageRequest = (Transition, Box<[(Img, Box<[String]>)]>);
#[derive(Archive, Serialize)]
pub enum Request {
Animation(AnimationRequest),
Clear(Clear),
Init,
Kill,
Query,
Img(ImageRequest),
}
impl Request {
pub fn send(&self, stream: &UnixStream) -> Result<(), String> {
let bytes = match rkyv::to_bytes::<_, 1024>(self) {
Ok(bytes) => bytes,
Err(e) => return Err(format!("Failed to serialize request: {e}")),
};
std::thread::scope(|s| {
if let Self::Animation(animations) = self {
s.spawn(|| {
for (animation, _) in animations.iter() {
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((_, imgs)) = self {
for (Img { path, .. }, outputs) in imgs.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]
pub fn receive(bytes: &[u8]) -> &ArchivedRequest {
unsafe { rkyv::archived_root::<Self>(bytes) }
}
}
#[derive(Archive, Serialize)]
pub enum Answer {
Ok,
Err(String),
Info(Box<[BgInfo]>),
Init(bool),
}
impl Answer {
pub fn send(&self, stream: &UnixStream) -> Result<(), String> {
let bytes = match rkyv::to_bytes::<_, 256>(self) {
Ok(bytes) => bytes,
Err(e) => return Err(format!("Failed to serialize answer: {e}")),
};
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]
pub fn receive(bytes: &[u8]) -> &ArchivedAnswer {
unsafe { rkyv::archived_root::<Self>(bytes) }
}
}
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 runtime_dir = Path::new(&runtime_dir);
runtime_dir.join("swww.socket")
}
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)
}
07070100000030000081A400000000000000000000000165A5297300000031000000000000000000000000000000000000001C00000000swww-0.8.2/utils/src/lib.rspub mod cache;
pub mod comp_decomp;
pub mod ipc;
07070100000031000081ED00000000000000000000000165A529730000038A000000000000000000000000000000000000001600000000swww-0.8.2/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 -- --include-ignored
./doc/gen.sh # make sure the docs "compile"
# Cargo.toml:
sed "s/^version = .*/version = \"$1\"/" Cargo.toml > TMP \
&& mv TMP Cargo.toml
sed "s/^version = .*/version = \"$1\"/" utils/Cargo.toml > TMP \
&& mv TMP utils/Cargo.toml
sed "s/^version = .*/version = \"$1\"/" daemon/Cargo.toml > TMP \
&& mv TMP daemon/Cargo.toml
# CHANGELOG:
sed -e "s/^### Unreleased/### $1/" \
-e '1s/^/### Unreleased\n\n\n/' CHANGELOG.md > TMP \
&& mv TMP CHANGELOG.md
# Make sure it still builds (just to be 100% safe), and to update Cargo.lock
cargo build
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!520 blocks