├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── src ├── lib.rs ├── macros.rs ├── main.rs ├── options.rs ├── position.rs ├── screens.rs ├── shm.rs ├── xatoms.rs └── xcontext.rs └── tests └── samples ├── run-samples.sh ├── sample-1x1-one-frame.gif ├── sample-1x1.gif ├── sample-1x2.gif └── sample-2x1.gif /.gitignore: -------------------------------------------------------------------------------- 1 | TODO 2 | /doc 3 | /target 4 | /tests/samples/non-free 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased](https://github.com/calculon102/xgifwallpaper/tree/HEAD) 4 | 5 | [Full Changelog](https://github.com/calculon102/xgifwallpaper/compare/v0.3.2...master) 6 | 7 | Nothing until now. 8 | 9 | 10 | ## [v0.3.2](https://github.com/calculon102/xgifwallpaper/tree/v0.3.2) - 2023-02-12 11 | 12 | [Full Changelog](https://github.com/calculon102/xgifwallpaper/compare/v0.3.1...v0.3.2) 13 | 14 | ### Fixed 15 | 16 | - warning: getting the inner pointer of a temporary CString [\#5](https://github.com/calculon102/xgifwallpaper/issues/5) 17 | - fix usage of platform dependend `u8`, instead of `c_char` as parameter [\#10](https://github.com/calculon102/xgifwallpaper/issues/10) 18 | 19 | 20 | ## [v0.3.1](https://github.com/calculon102/xgifwallpaper/tree/v0.3.1) - 2020-11-30 21 | 22 | [Full Changelog](https://github.com/calculon102/xgifwallpaper/compare/v0.3.0...v0.3.1) 23 | 24 | ### Added 25 | 26 | - Compile feature `x11-integration-tests` to run tests against a running 27 | X11-server. 28 | - Experimental option `--scale-filter`. Default value `AUTO` does as as before 29 | but value `PIXEL` uses most simple algorithm for performance and mabye better 30 | suited for pixel-art. 31 | 32 | ### Fixed 33 | 34 | - Crash when use --scale option [\#3](https://github.com/calculon102/xgifwallpaper/issues/3) 35 | - Upscaling of pixel-art GIFs renders glitches [\#4](https://github.com/calculon102/xgifwallpaper/issues/4) 36 | 37 | 38 | ## [v0.3.0](https://github.com/calculon102/xgifwallpaper/tree/v0.3.0) - 2020-11-24 39 | 40 | [Full Changelog](https://github.com/calculon102/xgifwallpaper/compare/v0.2.0...v0.3.0) 41 | 42 | ### Added 43 | 44 | - Option `-w` to specify custom window to draw wallpaper on, instead of 45 | X11-root. Useful for some window managers, which create custom windows as 46 | background. Must be the same resolution as the screen though! This option 47 | may also reference an atom of the root window by name, which contains a window 48 | id. 49 | 50 | ### Fixed 51 | 52 | - Query existing pixmap-properties and kill the owning application. He he he... 53 | 54 | 55 | ## [v0.2.0](https://github.com/calculon102/xgifwallpaper/tree/v0.2.0) - 2020-10-04 56 | 57 | [Full Changelog](https://github.com/calculon102/xgifwallpaper/compare/v0.1.2...v0.2.0) 58 | 59 | ### Added 60 | 61 | - This changelog 62 | - Option `-s` to scale GIF to `FILL` screen or `MAX`-out as much as possible 63 | - Sample GIFs and run-script as starter for semi-automated integration-tests 64 | 65 | ### Fixed 66 | 67 | - Set background of root window to black on exit 68 | - Exit gracefully, if there is no X display to open 69 | - Exit gracefully, if given file is not a valid GIF 70 | 71 | 72 | ## [v0.1.2](https://github.com/calculon102/xgifwallpaper/tree/v0.1.2) - 2020-09-04 73 | 74 | [Full Changelog](https://github.com/calculon102/xgifwallpaper/compare/v0.1.1...v0.1.2) 75 | 76 | ### Fixed 77 | 78 | - Compositors get segmentation fault after closing program [\#2](https://github.com/calculon102/xgifwallpaper/issues/2) 79 | 80 | 81 | ## [v0.1.1](https://github.com/calculon102/xgifwallpaper/tree/v0.1.1) - 2020-09-03 82 | 83 | [Full Changelog](https://github.com/calculon102/xgifwallpaper/compare/v0.1.0...v0.1.1) 84 | 85 | ### Fixed 86 | 87 | - Colors are not respected [\#1](https://github.com/calculon102/xgifwallpaper/issues/1) 88 | 89 | ## [v0.1.0](https://github.com/calculon102/xgifwallpaper/tree/v0.1.0) - 2020-08-29 90 | 91 | [Full Changelog](https://github.com/calculon102/xgifwallpaper/compare/3b85a0131b52672b3f5c82d7d721b9a7c4da9769...v0.1.0) 92 | 93 | ### Added 94 | 95 | - Animate GIF as background on root-window of a X-session 96 | - Use `-b` to customize background-color for transparent or non-image pixels 97 | - Use `-d` to specifiy a default-delay between frames, if none specified 98 | - Use `-v` to be verbose about it 99 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ansi_term" 7 | version = "0.11.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.0.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "bytemuck" 39 | version = "1.7.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" 42 | 43 | [[package]] 44 | name = "cc" 45 | version = "1.0.72" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 48 | 49 | [[package]] 50 | name = "cfg-if" 51 | version = "1.0.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 54 | 55 | [[package]] 56 | name = "clap" 57 | version = "2.33.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 60 | dependencies = [ 61 | "ansi_term", 62 | "atty", 63 | "bitflags", 64 | "strsim", 65 | "textwrap", 66 | "unicode-width", 67 | "vec_map", 68 | ] 69 | 70 | [[package]] 71 | name = "ctrlc" 72 | version = "3.2.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" 75 | dependencies = [ 76 | "nix", 77 | "winapi", 78 | ] 79 | 80 | [[package]] 81 | name = "gift" 82 | version = "0.10.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "a9ac9f4c949147ba6721435188949f98f8f7beafef85652835b316094e459559" 85 | dependencies = [ 86 | "log", 87 | "pix", 88 | ] 89 | 90 | [[package]] 91 | name = "hermit-abi" 92 | version = "0.1.19" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 95 | dependencies = [ 96 | "libc", 97 | ] 98 | 99 | [[package]] 100 | name = "libc" 101 | version = "0.2.108" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" 104 | 105 | [[package]] 106 | name = "log" 107 | version = "0.4.14" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 110 | dependencies = [ 111 | "cfg-if", 112 | ] 113 | 114 | [[package]] 115 | name = "memoffset" 116 | version = "0.6.4" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" 119 | dependencies = [ 120 | "autocfg", 121 | ] 122 | 123 | [[package]] 124 | name = "nix" 125 | version = "0.23.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" 128 | dependencies = [ 129 | "bitflags", 130 | "cc", 131 | "cfg-if", 132 | "libc", 133 | "memoffset", 134 | ] 135 | 136 | [[package]] 137 | name = "pix" 138 | version = "0.13.1" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "bea9d5c668f13b4a1b97d848780e00cfabf76eb83538129c264c0c6d6a968047" 141 | 142 | [[package]] 143 | name = "pkg-config" 144 | version = "0.3.22" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" 147 | 148 | [[package]] 149 | name = "resize" 150 | version = "0.5.5" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "f2a08c42ea86684dc00256494c4eb8b54707890ddac50c05060a717f29669029" 153 | dependencies = [ 154 | "rgb", 155 | ] 156 | 157 | [[package]] 158 | name = "rgb" 159 | version = "0.8.29" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "a27fa03bb1e3e2941f52d4a555a395a72bf79b0a85fbbaab79447050c97d978c" 162 | dependencies = [ 163 | "bytemuck", 164 | ] 165 | 166 | [[package]] 167 | name = "strsim" 168 | version = "0.8.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 171 | 172 | [[package]] 173 | name = "textwrap" 174 | version = "0.11.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 177 | dependencies = [ 178 | "unicode-width", 179 | ] 180 | 181 | [[package]] 182 | name = "unicode-width" 183 | version = "0.1.9" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 186 | 187 | [[package]] 188 | name = "vec_map" 189 | version = "0.8.2" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 192 | 193 | [[package]] 194 | name = "winapi" 195 | version = "0.3.9" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 198 | dependencies = [ 199 | "winapi-i686-pc-windows-gnu", 200 | "winapi-x86_64-pc-windows-gnu", 201 | ] 202 | 203 | [[package]] 204 | name = "winapi-i686-pc-windows-gnu" 205 | version = "0.4.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 208 | 209 | [[package]] 210 | name = "winapi-x86_64-pc-windows-gnu" 211 | version = "0.4.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 214 | 215 | [[package]] 216 | name = "x11" 217 | version = "2.19.1" 218 | source = "git+https://github.com/calculon102/x11-rs/?branch=master#ecefe07a8cb7134562bb76f6743d738e6fb8ab55" 219 | dependencies = [ 220 | "libc", 221 | "pkg-config", 222 | ] 223 | 224 | [[package]] 225 | name = "xgifwallpaper" 226 | version = "0.4.0-alpha" 227 | dependencies = [ 228 | "clap", 229 | "ctrlc", 230 | "gift", 231 | "libc", 232 | "pix", 233 | "resize", 234 | "x11", 235 | ] 236 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Frank Großgasteiger "] 3 | build = "build.rs" 4 | categories = ["command-line-utilities", "graphics"] 5 | description = "Use an animated GIF as wallpaper on X11-systems" 6 | edition = "2018" 7 | homepage = "https://github.com/calculon102/xgifwallwaper" 8 | keywords = ["cli", "x11", "wallpaper", "gif"] 9 | license = "GPL-3.0" 10 | links = "X11 Xinerama Xext" 11 | name = "xgifwallpaper" 12 | readme = "README.md" 13 | repository = "https://github.com/calculon102/xgifwallpaper" 14 | version = "0.4.0-alpha" 15 | 16 | [lib] 17 | name = "xgifwallpaper" 18 | path = "src/lib.rs" 19 | 20 | [[bin]] 21 | name = "xgifwallpaper" 22 | path = "src/main.rs" 23 | doc = false 24 | 25 | [dependencies] 26 | clap = "2.33" 27 | ctrlc = "3.2" 28 | gift = "0.10" 29 | libc = "0.2" 30 | pix = "0.13" 31 | resize = "0.5" 32 | x11 = { git="https://github.com/calculon102/x11-rs/", branch="master" } 33 | 34 | [features] 35 | # Compiles test-cases, that require a real x11-server running in the same 36 | # session. 37 | x11-integration-tests = [] 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![aur.archlinux.org](https://img.shields.io/aur/version/xgifwallpaper)](https://aur.archlinux.org/packages/xgifwallpaper) 2 | 3 | # `xgifwallpaper` 4 | 5 | Use an animated GIF as wallpaper on X11-systems. 6 | 7 | By using shared memory between X11 client and server, this is not as 8 | performance-inefficient as it may seem at first. Nonetheless, expect some 9 | memory to be used for bigger GIFs with a lot of frames. 10 | 11 | Due to using the shared memory extension of X11, this program will not work 12 | in X11 sessions over the network. 13 | 14 | In its current state, `xgifwallpaper` will always use all available screens. 15 | 16 | ## Compatibility 17 | 18 | `xgifwallpaper` will work with all window managers, that expose the root 19 | window of X. Also, X compositors supporting 20 | [pseudo-transparency](https://en.wikipedia.org/wiki/Pseudo-transparency#XROOTPMAP_ID_and_ESETROOT_PMAP_ID_properties), 21 | should work with `xgifwallpaper`, like `xcompmgr` or `picom`. 22 | 23 | Currently known to work with 24 | 25 | * [bspwm](https://github.com/baskerville/bspwm) 26 | * [Cinnamon](https://github.com/linuxmint/Cinnamon) 27 | * [dwm](https://dwm.suckless.org) 28 | * [i3](https://i3wm.org) 29 | * [Lxde](http://www.lxde.org) - Use -w option with the first windows ID from 30 | atom `_NET_CLIENT_LIST_STACKING(WINDOW)` of root window. See examples. 31 | * [Mate](https://mate-desktop.org) - Use -w with `CAJA_DESKTOP_WINDOW_ID` 32 | * [Openbox](https://github.com/danakj/openbox) 33 | * [qtile](http://www.qtile.org/) 34 | * [Xfce](https://www.xfce.org) - Use -w with `XFCE_DESKTOP_WINDOW` 35 | * [xmonad](https://xmonad.org) 36 | * [Awesome WM](https://awesomewm.org/) 37 | 38 | Known not work with 39 | 40 | * [Budgie](https://github.com/solus-project/budgie-desktop) 41 | * [Englightenment](https://www.enlightenment.org) 42 | * [Gnome3](https://www.gnome.org/gnome-3) / 43 | [Mutter](https://gitlab.gnome.org/GNOME/mutter) 44 | * [KDE Plasma 5](https://kde.org/plasma-desktop) 45 | 46 | Every feedback and testing is appreciated! 47 | 48 | ## Usage 49 | 50 | See output of `--help`: 51 | 52 | ```console 53 | USAGE: 54 | xgifwallpaper [FLAGS] [OPTIONS] 55 | 56 | FLAGS: 57 | -v Verbose mode 58 | -h, --help Prints help information 59 | -V, --version Prints version information 60 | 61 | OPTIONS: 62 | -b, --background-color X11 compilant color-name to paint background. [default: #000000] 63 | -d, --default-delay Delay in centiseconds between frames, if unspecified in GIF. [default: 10] 64 | -s, --scale Scale GIF-frames, relative to available screen. [default: NONE] [possible 65 | values: NONE, FILL, MAX] 66 | --scale-filter Filter to use in combination with scale-option. Experimental feature. 67 | [default: AUTO] [possible values: AUTO, PIXEL] 68 | -w, --window-id ID of window to animate wallpaper on its background, insted of the root 69 | window. As decimal, hex or name of root-atom. 70 | 71 | ARGS: 72 | Path to GIF-file 73 | ``` 74 | 75 | ### Examples 76 | 77 | Center `mybackground.gif` on all screens: 78 | 79 | `xgifwallpaper mybackground.gif` 80 | 81 | Set background color to `#ffaa00`: 82 | 83 | `xgifwallpaper -b "#ffaa00" mybackground.gif` 84 | 85 | Override default delay with 100 centiseconds: 86 | 87 | `xgifwallpaper -d 100 mybackground.gif` 88 | 89 | Scale `mybackground.gif` to fill the entire screen and be verbose: 90 | 91 | `xgifwallpaper -v -s FILL mybackground.gif` 92 | 93 | Scale `mybackground.gif` to maximize used screen-space, but without cutting the 94 | image. Also, set background-color to white, override default-delay with 30 95 | centiseconds and be verbose: 96 | 97 | `xgifwallpaper -v -b white -d 30 -s MAX mybackground.gif` 98 | 99 | Use window, referenced by specified atom of root-window, to draw wallpaper, 100 | instead of the root window itself: 101 | 102 | ```bash 103 | # Use with Mate 104 | xgifwallpaper -w 'CAJA_DESKTOP_WINDOW_ID' my_wallpaper.gif 105 | 106 | # Use with XFCE 107 | xgifwallpaper -w 'XFCE_DESKTOP_WINDOW' my_wallpaper.gif 108 | ``` 109 | 110 | Use background the first window in stacking order to draw wallpaper, instead of 111 | the root window. To be used with `Lxde`: 112 | 113 | ```bash 114 | xgifwallpaper -w $(xprop -root | awk '/_NET_CLIENT_LIST_STACKING\(WINDOW\)/{print $5}' | tr -d ,) mybackground.gif 115 | ``` 116 | 117 | ## Install 118 | 119 | There is an [AUR-package](https://aur.archlinux.org/packages/xgifwallpaper/) 120 | for arch-based linux-systems. 121 | 122 | For other systems, you will need to build this yourself. 123 | 124 | ### Runtime dependencies 125 | 126 | Dynamically links mainly X11-libs: 127 | 128 | * `xlib` 129 | * `xinerama` 130 | * `xshm` 131 | 132 | There will be build-specific dependencies 133 | 134 | ## Build 135 | 136 | ### Install Rust development environment 137 | 138 | See [Installing Rust](https://www.rust-lang.org/learn/get-started). 139 | 140 | ### Install dependencies 141 | 142 | You need the header files for `X11` and its extensions `Xinerama` and `XShm`. 143 | Further dependencies are `libc` and a C-compiler-suite like `gcc`, rust will 144 | need to link the C-bindings. 145 | 146 | #### Arch 147 | 148 | On *Arch*-based-systems, the packages needed are listed as `depends` and 149 | `makedepends` in the 150 | [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=xgifwallpaper) 151 | of the AUR-package: 152 | 153 | ```console 154 | # pacman -S gcc gcc-libs git glibc libx11 libxau libxcb libxdmcp libxext libxinerama 155 | ``` 156 | 157 | Rust is not included, as I would suggest installing it the way described above. 158 | 159 | #### Ubuntu 160 | 161 | On *Ubuntu*-based-systems, use 162 | 163 | ```console 164 | $ sudo apt install libx11-dev libxinerama-dev libxext-dev 165 | ``` 166 | 167 | `git`, `rust`, `libc` and a C-compiler-suite need to be installed. 168 | 169 | This should also work on Debian, but this is not verified. 170 | 171 | ### Actual build 172 | 173 | From project-root: 174 | 175 | ```console 176 | $ cargo build --release 177 | ``` 178 | 179 | The result is built as `target/release/xgifwallpaper`. 180 | 181 | ### Testing 182 | 183 | Run `cargo test` for all unit-tests. 184 | 185 | Run `cargo test --features x11-integration-tests` to run additional tests in a 186 | X11-session, against the running server. 187 | 188 | Run `run_samples.sh` from directory `tests/samples` for an e2e-smoke-test of 189 | some configurations. 190 | 191 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-lib=dylib=X11"); 3 | println!("cargo:rustc-link-lib=dylib=Xinerama"); 4 | println!("cargo:rustc-link-lib=dylib=Xext"); // For Xshm 5 | } 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | pub mod macros; 3 | 4 | pub mod options; 5 | mod position; 6 | pub mod screens; 7 | mod shm; 8 | mod xatoms; 9 | pub mod xcontext; 10 | 11 | use pix::rgb::Rgba8; 12 | 13 | use std::collections::HashMap; 14 | use std::ffi::c_void; 15 | use std::fs::File; 16 | use std::os::raw::{c_uchar, c_uint}; 17 | use std::rc::Rc; 18 | use std::sync::atomic::{AtomicBool, Ordering}; 19 | use std::sync::Arc; 20 | use std::{thread, time}; 21 | 22 | use x11::xlib::*; 23 | 24 | use options::Options; 25 | use position::*; 26 | use screens::*; 27 | use shm::*; 28 | use xatoms::*; 29 | use xcontext::XContext; 30 | 31 | const EXIT_INVALID_FILE: i32 = 103; 32 | 33 | const VERSION: &str = "0.3.2"; 34 | 35 | /// Screens to render wallpapers on, with needed resolution. And the pre- 36 | /// rendered frames in a seperate map. 37 | pub struct Wallpapers { 38 | screens: Vec, 39 | frames_by_resolution: HashMap>, 40 | } 41 | 42 | /// Resolution and placement of a wallpaper on a screen. 43 | struct WallpaperOnScreen { 44 | placement: ImagePlacement, 45 | resolution: Resolution, 46 | _screen: screens::Screen, // TODO Check if useful at some time 47 | } 48 | 49 | /// Combines x-structs, raster- and metadata for a singe frame. 50 | struct Frame { 51 | delay: time::Duration, 52 | raster: Rc>, 53 | ximage: Box, 54 | xshminfo: Box, // Must exist as long as ximage 55 | } 56 | 57 | /// Pre-render wallpaper-frames for all needed resolutions, determined by 58 | /// actual screens, options and image-data 59 | pub fn render_wallpapers( 60 | xcontext: &Box, 61 | xscreens: Screens, 62 | options: Arc, 63 | running: Arc, 64 | ) -> Wallpapers { 65 | // Decode gif-frames into raster-steps 66 | let path_to_gif = options.path_to_gif.as_str(); 67 | 68 | // TODO Try using only low-level frames 69 | // TODO Prevent double-encoding, by re-using iterator? 70 | let methods = gather_disposal_methods(path_to_gif); 71 | 72 | // Determine image-resolution 73 | let first_step_result = create_decoder(path_to_gif) 74 | .into_steps() 75 | .nth(0) 76 | .expect("No steps decoded"); 77 | 78 | if first_step_result.is_err() { 79 | eprintln!( 80 | "File {} is not a valid GIF: {:?}", 81 | path_to_gif, 82 | first_step_result.err().unwrap() 83 | ); 84 | std::process::exit(EXIT_INVALID_FILE); 85 | } 86 | 87 | let first_step = first_step_result.unwrap(); 88 | let raster = first_step.raster(); 89 | let image_resolution = Resolution { 90 | width: raster.width(), 91 | height: raster.height(), 92 | }; 93 | 94 | // Build wallpapers by screen 95 | let mut screens: Vec = Vec::new(); 96 | let mut frames_by_resolution: HashMap> = HashMap::new(); 97 | 98 | for screen in xscreens.screens { 99 | logln!(options, "Prepare wallpaper for {:?}", screen); 100 | 101 | // Gather target-resolution and image-placement for particular screen 102 | let screen_resolution = Resolution { 103 | width: screen.width, 104 | height: screen.height, 105 | }; 106 | 107 | let target_resolution = 108 | image_resolution.fit_to_screen(&screen_resolution, &options.scaling); 109 | 110 | let wallpaper_on_screen = WallpaperOnScreen { 111 | placement: target_resolution.position_on_screen(&screen, Alignment::CENTER), 112 | resolution: target_resolution.clone(), 113 | _screen: screen.clone(), 114 | }; 115 | 116 | // If frames were not already rendered for given resolution, do so 117 | if !frames_by_resolution.contains_key(&target_resolution) { 118 | frames_by_resolution.insert( 119 | target_resolution, 120 | render_frames( 121 | xcontext, 122 | &wallpaper_on_screen, 123 | create_decoder(path_to_gif).into_steps().by_ref(), 124 | &methods, 125 | options.clone(), 126 | running.clone(), 127 | ), 128 | ); 129 | } else { 130 | logln!( 131 | options, 132 | "Reuse already rendered frames for {:?}", 133 | target_resolution 134 | ); 135 | } 136 | 137 | screens.push(wallpaper_on_screen); 138 | } 139 | 140 | Wallpapers { 141 | screens, 142 | frames_by_resolution, 143 | } 144 | } 145 | 146 | /// Create GIF-decoder from file. 147 | fn create_decoder(path_to_gif: &str) -> gift::Decoder { 148 | gift::Decoder::new(File::open(path_to_gif).expect("Unable to read file")) 149 | } 150 | 151 | /// Parse GIF to gather the disposal-method for each frame. 152 | fn gather_disposal_methods(path_to_gif: &str) -> Vec { 153 | let mut methods: Vec = Vec::new(); 154 | let frames = create_decoder(path_to_gif).into_frames(); 155 | for frame in frames { 156 | if frame.is_ok() { 157 | let f = frame.unwrap(); 158 | 159 | if f.graphic_control_ext.is_some() { 160 | methods.push(f.graphic_control_ext.unwrap().disposal_method()); 161 | } 162 | 163 | continue; 164 | } 165 | 166 | methods.push(gift::block::DisposalMethod::NoAction); 167 | } 168 | 169 | methods 170 | } 171 | 172 | /// Render GIF-frames as bitmaps for a specific screen. 173 | fn render_frames( 174 | xcontext: &Box, 175 | wallpaper_on_screen: &WallpaperOnScreen, 176 | steps: &mut gift::decode::Steps, 177 | methods: &Vec, 178 | options: Arc, 179 | running: Arc, 180 | ) -> Vec { 181 | let mut rendered_frames: Vec = Vec::new(); 182 | let mut frame_index = 0; 183 | 184 | let xscreen = unsafe { XDefaultScreenOfDisplay(xcontext.display) }; 185 | let xvisual = unsafe { XDefaultVisualOfScreen(xscreen) }; 186 | 187 | // Convert rasters to frames 188 | for step_option in steps.by_ref() { 189 | if !running.load(Ordering::SeqCst) { 190 | break; 191 | } 192 | 193 | let step = step_option.expect("Unexpected end of steps"); 194 | let raster = step.raster(); 195 | 196 | let image_resolution = Resolution { 197 | width: raster.width(), 198 | height: raster.height(), 199 | }; 200 | 201 | let target_resolution = wallpaper_on_screen.resolution.clone(); 202 | 203 | logln!( 204 | options, 205 | "Convert step {} (delay: {:?}, method: {:?}, width: {}, height: {}) to XImage (width: {}, height: {})", 206 | frame_index, 207 | step.delay_time_cs(), 208 | methods[frame_index], 209 | image_resolution.width, 210 | image_resolution.height, 211 | target_resolution.width, 212 | target_resolution.height 213 | ); 214 | 215 | // Build target raster 216 | // TODO Better naming of vectors 217 | 218 | // Create shared memory segment and image structure 219 | let image_byte_size = (target_resolution.width * target_resolution.height * 4) as usize; 220 | 221 | let mut xshminfo = create_xshm_sgmnt_inf(image_byte_size).unwrap(); 222 | let ximage = create_xshm_image( 223 | xcontext.display, 224 | xvisual, 225 | &mut xshminfo, 226 | target_resolution.width as u32, 227 | target_resolution.height as u32, 228 | 24, 229 | ) 230 | .unwrap(); 231 | 232 | let is_rgb = unsafe { (*ximage).byte_order == x11::xlib::MSBFirst }; 233 | let rgba_indices = if is_rgb { 234 | [0, 1, 2, 3] // RGBA 235 | } else { 236 | [2, 1, 0, 3] // BGRA 237 | }; 238 | 239 | let color = xcontext.background_color; 240 | let background_rgba = [ 241 | (color.red / 256) as u8, 242 | (color.green / 256) as u8, 243 | (color.blue / 256) as u8, 244 | 255 as u8, 245 | ]; 246 | 247 | // Get previous frame as raster or plain color pane, if non-existing 248 | let prev_raster: Rc> = { 249 | if rendered_frames.len() > 0 { 250 | rendered_frames.last().unwrap().raster.clone() 251 | } else { 252 | let capacity: usize = raster.width() as usize 253 | * raster.height() as usize 254 | * std::mem::size_of::(); 255 | 256 | // TODO Too big, needs only the size of original frame 257 | let mut solid_color: Vec = Vec::with_capacity(capacity); 258 | let mut solid_color_index: usize = 0; 259 | 260 | while solid_color_index < capacity { 261 | solid_color.push(background_rgba[rgba_indices[0]]); 262 | solid_color.push(background_rgba[rgba_indices[1]]); 263 | solid_color.push(background_rgba[rgba_indices[2]]); 264 | solid_color.push(background_rgba[rgba_indices[3]]); 265 | 266 | solid_color_index += 4; 267 | } 268 | 269 | Rc::new(solid_color) 270 | } 271 | }; 272 | 273 | let u8_slice = raster.as_u8_slice(); 274 | let mut i = 0; 275 | 276 | let mut data: Vec = Vec::with_capacity(image_byte_size); 277 | while i < u8_slice.len() { 278 | let alpha = u8_slice[i + 3]; 279 | 280 | if alpha == 255 { 281 | data.push(u8_slice[i + rgba_indices[0]]); 282 | data.push(u8_slice[i + rgba_indices[1]]); 283 | data.push(u8_slice[i + rgba_indices[2]]); 284 | data.push(u8_slice[i + rgba_indices[3]]); 285 | } else if methods[frame_index] == gift::block::DisposalMethod::Keep { 286 | data.push(prev_raster[i + 0]); 287 | data.push(prev_raster[i + 1]); 288 | data.push(prev_raster[i + 2]); 289 | data.push(prev_raster[i + 3]); 290 | } else { 291 | data.push(background_rgba[rgba_indices[0]]); 292 | data.push(background_rgba[rgba_indices[1]]); 293 | data.push(background_rgba[rgba_indices[2]]); 294 | data.push(alpha); 295 | } 296 | 297 | i += 4; 298 | } 299 | 300 | let frame_ptr = Rc::new(data); 301 | let resized_frame = resize_raster( 302 | frame_ptr.clone(), 303 | &image_resolution, 304 | &target_resolution, 305 | options.clone(), 306 | ); 307 | 308 | logln!( 309 | options, 310 | "Resized {}={}", 311 | resized_frame.len(), 312 | image_byte_size 313 | ); 314 | 315 | // Copy raw data into shared memory segment of XImage 316 | unsafe { 317 | libc::memcpy( 318 | xshminfo.shmaddr as *mut c_void, 319 | resized_frame.as_ptr().clone() as *mut _, 320 | image_byte_size, 321 | ); 322 | (*ximage).data = xshminfo.shmaddr; 323 | x11::xshm::XShmAttach(xcontext.display, xshminfo.as_mut() as *mut _); 324 | }; 325 | 326 | rendered_frames.push(Frame { 327 | delay: get_step_duration(&step, options.clone()), 328 | raster: frame_ptr, 329 | ximage: unsafe { Box::new(*ximage) }, 330 | xshminfo, 331 | }); 332 | 333 | frame_index = frame_index + 1; 334 | } 335 | 336 | rendered_frames 337 | } 338 | 339 | /// Get delay from step. Normalize or use default if not below zere or not 340 | /// given. 341 | fn get_step_duration(step: &gift::Step, options: Arc) -> time::Duration { 342 | let mut delay = step.delay_time_cs().unwrap_or(options.default_delay); 343 | 344 | if delay <= 0 { 345 | delay = options.default_delay; 346 | } 347 | 348 | time::Duration::from_millis((delay * 10) as u64) 349 | } 350 | 351 | /// Resize given RGBA-raster to target-resolution. 352 | fn resize_raster( 353 | raster: Rc>, 354 | image_resolution: &Resolution, 355 | target_resolution: &Resolution, 356 | options: Arc, 357 | ) -> Rc> { 358 | let src_w = image_resolution.width as usize; 359 | let src_h = image_resolution.height as usize; 360 | let dst_w = target_resolution.width as usize; 361 | let dst_h = target_resolution.height as usize; 362 | 363 | let must_resize = src_w != dst_w || src_h != dst_h; 364 | 365 | if !must_resize { 366 | return raster.clone(); 367 | } 368 | 369 | let (resize_type, type_name) = match options.scaling_filter { 370 | ScalingFilter::PIXEL => (resize::Type::Point, "Point"), 371 | ScalingFilter::AUTO => { 372 | if (src_w * src_h) > (dst_w * dst_h) { 373 | (resize::Type::Lanczos3, "Lanczos3") 374 | } else { 375 | (resize::Type::Mitchell, "Mitchell") 376 | } 377 | } 378 | }; 379 | 380 | logln!( 381 | options, 382 | "Resize raster from {}x{} to {}x{}, using filter {}", 383 | src_w, 384 | src_h, 385 | dst_w, 386 | dst_h, 387 | type_name 388 | ); 389 | 390 | let sample_size = dst_w * dst_h * 4; 391 | 392 | let mut dst: Vec = Vec::with_capacity(sample_size); 393 | dst.resize(sample_size, 0); 394 | 395 | let mut resizer = resize::new(src_w, src_h, dst_w, dst_h, resize::Pixel::RGBA, resize_type); 396 | 397 | resizer.resize(&raster, &mut dst); 398 | 399 | Rc::new(dst) 400 | } 401 | 402 | /// Clear previous backgrounds on root. 403 | pub fn clear_background(xcontext: &Box, options: Arc) { 404 | remove_root_pixmap_atoms(&xcontext, options.clone()); 405 | 406 | unsafe { 407 | XClearWindow(xcontext.display, xcontext.root); 408 | XSync(xcontext.display, False); 409 | } 410 | } 411 | 412 | /// Loops the pre-renders wallpapers on each screen. Will only stop on 413 | /// interrupt-signal. 414 | pub fn do_animation( 415 | xcontext: &Box, 416 | wallpapers: &mut Wallpapers, 417 | options: Arc, 418 | running: Arc, 419 | ) { 420 | logln!(options, "Loop animation..."); 421 | 422 | let display = xcontext.display; 423 | let pixmap = xcontext.pixmap; 424 | let gc = xcontext.gc; 425 | let root = xcontext.root; 426 | 427 | let atom_root = get_root_pixmap_atom(display); 428 | let atom_eroot = get_eroot_pixmap_atom(display); 429 | 430 | let mut i: usize = 0; 431 | let mut delay: std::time::Duration = std::time::Duration::new(0, 0); 432 | 433 | while running.load(Ordering::SeqCst) { 434 | if !running.load(Ordering::SeqCst) { 435 | break; 436 | } 437 | 438 | for screen in &wallpapers.screens { 439 | let frames = wallpapers 440 | .frames_by_resolution 441 | .get_mut(&screen.resolution) 442 | .unwrap(); 443 | 444 | // The following assumptions only work, while there is a single GIF 445 | // to render. Different GIFs per screen would require a rewrite. 446 | 447 | // Assumption: All framesets have same length 448 | if frames.len() <= i { 449 | i = 0; 450 | } 451 | 452 | // Assumption: All frames with same index have same delay 453 | delay = frames[i].delay; 454 | 455 | //logln!(options, "Put frame {} on screen {:?}", i, screen.placement); 456 | 457 | unsafe { 458 | x11::xshm::XShmPutImage( 459 | display, 460 | pixmap, 461 | gc, 462 | &mut *frames[i].ximage, 463 | screen.placement.src_x, 464 | screen.placement.src_y, 465 | screen.placement.dest_x, 466 | screen.placement.dest_y, 467 | screen.placement.width as c_uint, 468 | screen.placement.height as c_uint, 469 | False, 470 | ); 471 | } 472 | } 473 | 474 | i = i + 1; 475 | 476 | if !update_root_pixmap_atoms(display, root, &pixmap, atom_root, atom_eroot) { 477 | eprintln!("set_root_atoms failed!"); 478 | } 479 | 480 | unsafe { 481 | XClearWindow(display, root); 482 | XSetWindowBackgroundPixmap(display, root, pixmap); 483 | XSync(display, False); 484 | } 485 | 486 | thread::sleep(delay); 487 | } 488 | 489 | logln!(options, "Stop animation-loop"); 490 | 491 | delete_atom(&xcontext, atom_root); 492 | delete_atom(&xcontext, atom_eroot); 493 | } 494 | 495 | /// Clears reference and (shared-)-memory. 496 | pub fn clean_up(xcontext: Box, mut wallpapers: Wallpapers, options: Arc) { 497 | logln!(options, "Free images in shared memory"); 498 | 499 | for frames in wallpapers.frames_by_resolution.values_mut() { 500 | for i in 0..(frames.len()) { 501 | // Don't need to call XDestroy image - heap is freed by rust-guarantees. :) 502 | unsafe { 503 | x11::xshm::XShmDetach(xcontext.display, frames[i].xshminfo.as_mut() as *mut _) 504 | }; 505 | destroy_xshm_sgmnt_inf(&mut frames[i].xshminfo); 506 | } 507 | } 508 | } 509 | 510 | #[cfg(all(test, feature = "x11-integration-tests"))] 511 | mod test { 512 | /// Test for github-issue #3: 513 | /// [https://github.com/calculon102/xgifwallpaper/issues/3] 514 | #[test] 515 | fn when_render_for_multiple_resolutions_then_dont_panic() { 516 | // Prepare 517 | use crate::options::Options; 518 | use crate::render_wallpapers; 519 | use crate::screens::*; 520 | use crate::xcontext::XContext; 521 | use std::sync::atomic::AtomicBool; 522 | use std::sync::Arc; 523 | 524 | let options = Arc::new(Options::_from_params(vec![ 525 | "xgifwallpaper", 526 | "--scale", 527 | "FILL", 528 | "tests/samples/sample-1x1.gif", 529 | ])); 530 | 531 | let xcontext = Box::new(XContext::new(options.clone()).unwrap()); 532 | let running = Arc::new(AtomicBool::new(true)); 533 | 534 | let screens = Screens { 535 | root_per_screen: false, 536 | screens: vec![ 537 | Screen { 538 | screen_number: 0, 539 | x_org: 0, 540 | y_org: 0, 541 | width: 800, 542 | height: 600, 543 | }, 544 | Screen { 545 | screen_number: 1, 546 | x_org: 400, 547 | y_org: 300, 548 | width: 1920, 549 | height: 1080, 550 | }, 551 | ], 552 | }; 553 | 554 | // Act 555 | let wallpapers = render_wallpapers(&xcontext, screens, options.clone(), running.clone()); 556 | 557 | assert_eq!(wallpapers.screens.len(), 2); 558 | assert_eq!(wallpapers.frames_by_resolution.len(), 2); 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | //! Custom macros for `xgifwallpaper`. 2 | 3 | /// Prints message, if verbose is `true` in given `Options`, without implicit 4 | /// line-break. 5 | /// 6 | /// # Examples 7 | /// 8 | /// ``` 9 | /// # #[macro_use] extern crate xgifwallpaper; 10 | /// # fn main() { 11 | /// # use xgifwallpaper::options::Options; 12 | /// let options = Options::_from_params(vec!["xgifwallpaper", "-v", "foobar.gif"]); 13 | /// assert_eq!(options.verbose, true); 14 | /// 15 | /// log!(options, "This should be logged ... "); 16 | /// log!(options, "on the same line."); 17 | /// # } 18 | /// ``` 19 | /// 20 | /// ``` 21 | /// # #[macro_use] extern crate xgifwallpaper; 22 | /// # fn main() { 23 | /// # use xgifwallpaper::options::Options; 24 | /// let options = Options::_from_params(vec!["xgifwallpaper", "foobar.gif"]); 25 | /// assert_eq!(options.verbose, false); 26 | /// 27 | /// log!(options, "This will not be logged ... "); 28 | /// log!(options, "in no way."); 29 | /// # } 30 | /// ``` 31 | #[macro_export] 32 | macro_rules! log { 33 | ($is_verbose:ident, $message:expr) => { 34 | if $is_verbose.verbose { 35 | print!($message); 36 | } 37 | }; 38 | 39 | ($is_verbose:ident, $message:expr, $($args:expr),*) => { 40 | if $is_verbose.verbose { 41 | print!($message $(,$args)*); 42 | } 43 | }; 44 | } 45 | 46 | /// Prints message, if verbose is `true` in given `Options`, without implicit 47 | /// line-break. 48 | /// 49 | /// # Examples 50 | /// 51 | /// ``` 52 | /// # #[macro_use] extern crate xgifwallpaper; 53 | /// # fn main() { 54 | /// # use xgifwallpaper::options::Options; 55 | /// let options = Options::_from_params(vec!["xgifwallpaper", "-v", "foobar.gif"]); 56 | /// assert_eq!(options.verbose, true); 57 | /// 58 | /// logln!(options, "This should be logged ... "); 59 | /// logln!(options, "on two lines."); 60 | /// # } 61 | /// ``` 62 | /// 63 | /// ``` 64 | /// # #[macro_use] extern crate xgifwallpaper; 65 | /// # fn main() { 66 | /// # use xgifwallpaper::options::Options; 67 | /// let options = Options::_from_params(vec!["xgifwallpaper", "foobar.gif"]); 68 | /// assert_eq!(options.verbose, false); 69 | /// 70 | /// logln!(options, "This will not be logged ... "); 71 | /// logln!(options, "and never on two lines."); 72 | /// # } 73 | /// ``` 74 | #[macro_export] 75 | macro_rules! logln { 76 | ($is_verbose:ident, $message:expr) => { 77 | if $is_verbose.verbose { 78 | println!($message); 79 | } 80 | }; 81 | 82 | ($is_verbose:ident, $message:expr, $($args:expr),*) => { 83 | if $is_verbose.verbose { 84 | println!($message $(,$args)*); 85 | } 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | use std::sync::Arc; 3 | 4 | use xgifwallpaper::options::Options; 5 | use xgifwallpaper::screens::Screens; 6 | use xgifwallpaper::xcontext::XContext; 7 | use xgifwallpaper::*; 8 | 9 | /// Application entry-point 10 | fn main() { 11 | let options = Arc::new(Options::from_args()); 12 | let running = Arc::new(AtomicBool::new(true)); 13 | 14 | init_sigint_handler(options.clone(), running.clone()); 15 | 16 | let xcontext = match XContext::new(options.clone()) { 17 | Ok(xcontext) => Box::new(xcontext), 18 | Err(e) => { 19 | eprintln!("{}", e); 20 | std::process::exit(e.code); 21 | } 22 | }; 23 | 24 | let mut wallpapers = render_wallpapers( 25 | &xcontext, 26 | Screens::query_x_screens(), 27 | options.clone(), 28 | running.clone(), 29 | ); 30 | 31 | clear_background(&xcontext, options.clone()); 32 | 33 | do_animation(&xcontext, &mut wallpapers, options.clone(), running.clone()); 34 | 35 | clean_up(xcontext, wallpapers, options); 36 | } 37 | 38 | /// Register handler for interrupt-signal. 39 | fn init_sigint_handler<'a>(options: Arc, running: Arc) { 40 | let verbose = options.verbose; 41 | 42 | ctrlc::set_handler(move || { 43 | running.store(false, Ordering::SeqCst); 44 | 45 | if verbose { 46 | println!("SIGINT received"); 47 | } 48 | }) 49 | .expect("Error setting Ctrl-C handler"); 50 | } 51 | -------------------------------------------------------------------------------- /src/options.rs: -------------------------------------------------------------------------------- 1 | //! Defines options of `xgifwallpaper` and parses these from command line- 2 | //! arguments. 3 | 4 | use clap::{value_t, App, Arg, ArgMatches}; 5 | 6 | use super::position::Scaling; 7 | use super::position::ScalingFilter; 8 | use super::VERSION; 9 | 10 | const ARG_COLOR: &str = "COLOR"; 11 | const ARG_DELAY: &str = "DELAY"; 12 | const ARG_PATH_TO_GIF: &str = "PATH_TO_GIF"; 13 | const ARG_SCALE: &str = "SCALE"; 14 | const ARG_SCALE_FILTER: &str = "SCALE_FILTER"; 15 | const ARG_VERBOSE: &str = "VERBOSE"; 16 | const ARG_WINDOW_ID: &str = "WINDOW_ID"; 17 | 18 | const DEFAULT_DELAY: u16 = 10; 19 | const DEFAULT_DELAY_STR: &str = "10"; 20 | 21 | /// Runtime options as given by the caller of this program. 22 | #[derive(Debug)] 23 | pub struct Options { 24 | /// X11-compilant color-name 25 | pub background_color: String, 26 | pub default_delay: u16, 27 | pub path_to_gif: String, 28 | /// Scaling-method to use 29 | pub scaling: Scaling, 30 | pub scaling_filter: ScalingFilter, 31 | pub verbose: bool, 32 | /// Window-Id as decimal or hex-number (0x-prefix) or name of atom with Id 33 | /// to use. 34 | pub window_id: String, 35 | } 36 | 37 | impl Options { 38 | /// Parse options from command-line. 39 | /// 40 | /// ```no_run 41 | /// # extern crate xgifwallpaper; 42 | /// # use xgifwallpaper::options::Options; 43 | /// let options = Options::from_args(); 44 | /// ``` 45 | pub fn from_args() -> Options { 46 | parse_args(init_args().get_matches()) 47 | } 48 | 49 | /// Parse options as strings, in order given. 50 | /// 51 | /// ``` 52 | /// # extern crate xgifwallpaper; 53 | /// # use xgifwallpaper::options::Options; 54 | /// let options = Options::_from_params( 55 | /// vec!["xgifwallpaper", "-v", "-b", "#FF0000", "foobar.gif"] 56 | /// ); 57 | /// 58 | /// assert_eq!(options.background_color, "#FF0000".to_string()); 59 | /// assert_eq!(options.path_to_gif, "foobar.gif".to_string()); 60 | /// assert_eq!(options.verbose, true); 61 | /// ``` 62 | pub fn _from_params(params: Vec<&str>) -> Options { 63 | parse_args(init_args().get_matches_from(params)) 64 | } 65 | } 66 | 67 | /// Declare command-line-arguments. 68 | fn init_args<'a, 'b>() -> App<'a, 'b> { 69 | App::new("xgifwallpaper") 70 | .version(VERSION) 71 | .author("Frank Großgasteiger ") 72 | .about("Animates a GIF as wallpaper in your X-session") 73 | .arg( 74 | Arg::with_name(ARG_COLOR) 75 | .short("b") 76 | .long("background-color") 77 | .takes_value(true) 78 | .value_name("X11-color") 79 | .default_value("#000000") 80 | .help("X11 compilant color-name to paint background."), 81 | ) 82 | .arg( 83 | Arg::with_name(ARG_DELAY) 84 | .short("d") 85 | .long("default-delay") 86 | .takes_value(true) 87 | .value_name("default-delay") 88 | .default_value(DEFAULT_DELAY_STR) 89 | .help("Delay in centiseconds between frames, if unspecified in GIF."), 90 | ) 91 | .arg(Arg::with_name(ARG_VERBOSE).short("v").help("Verbose mode")) 92 | .arg( 93 | Arg::with_name(ARG_PATH_TO_GIF) 94 | .help("Path to GIF-file") 95 | .required(true) 96 | .index(1), 97 | ) 98 | .arg( 99 | Arg::with_name(ARG_SCALE) 100 | .short("s") 101 | .long("scale") 102 | .takes_value(true) 103 | .possible_values(&["NONE", "FILL", "MAX"]) 104 | .default_value("NONE") 105 | .help("Scale GIF-frames, relative to available screen."), 106 | ) 107 | .arg( 108 | Arg::with_name(ARG_SCALE_FILTER) 109 | .long("scale-filter") 110 | .takes_value(true) 111 | .possible_values(&["AUTO", "PIXEL"]) 112 | .default_value("AUTO") 113 | .help("Filter to use in combination with scale-option. Experimental feature."), 114 | ) 115 | .arg( 116 | Arg::with_name(ARG_WINDOW_ID) 117 | .help( 118 | "ID of window to animate wallpaper on its background, \ 119 | insted of the root window. As decimal, hex or name of \ 120 | root-atom.", 121 | ) 122 | .long("window-id") 123 | .short("w") 124 | .takes_value(true), 125 | ) 126 | } 127 | 128 | /// Parse arguments from command line. 129 | fn parse_args<'a>(args: ArgMatches<'a>) -> Options { 130 | let delay = value_t!(args, ARG_DELAY, u16).unwrap_or_else(|_e| { 131 | eprintln!( 132 | "Use a value between {} and {} as default-delay.", 133 | u16::MIN, 134 | u16::MAX 135 | ); 136 | DEFAULT_DELAY 137 | }); 138 | 139 | let scaling = match args.value_of(ARG_SCALE).unwrap() { 140 | "NONE" => Scaling::NONE, 141 | "FILL" => Scaling::FILL, 142 | "MAX" => Scaling::MAX, 143 | &_ => Scaling::NONE, // Cannot happen, due to guarantee of args 144 | }; 145 | 146 | let scaling_filter = match args.value_of(ARG_SCALE_FILTER).unwrap() { 147 | "AUTO" => ScalingFilter::AUTO, 148 | "PIXEL" => ScalingFilter::PIXEL, 149 | &_ => ScalingFilter::AUTO, // Cannot happen, due to guarantee of args 150 | }; 151 | 152 | Options { 153 | background_color: args.value_of(ARG_COLOR).unwrap().to_owned(), 154 | default_delay: delay, 155 | path_to_gif: args.value_of(ARG_PATH_TO_GIF).unwrap().to_owned(), 156 | scaling, 157 | scaling_filter, 158 | verbose: args.is_present(ARG_VERBOSE), 159 | window_id: args.value_of(ARG_WINDOW_ID).unwrap_or("").to_string(), 160 | } 161 | } 162 | 163 | // Test only xgifwallpaper-specifics via arguments. Don't test behaviour of 164 | // clap, like mandatory fields, valid options or order of arguments. 165 | #[cfg(test)] 166 | mod tests { 167 | use super::Options; 168 | use super::Scaling; 169 | use super::ScalingFilter; 170 | 171 | const PATH_TO_GIF: &str = "wallpaper.gif"; 172 | 173 | #[test] 174 | fn use_argument_path_to_gif() { 175 | let options = Options::_from_params(_create_params(vec![])); 176 | assert_eq!(options.path_to_gif, PATH_TO_GIF); 177 | } 178 | 179 | #[test] 180 | fn use_defaults_for_omitted_arguments() { 181 | let options = Options::_from_params(_create_params(vec![])); 182 | assert_eq!(options.background_color, "#000000"); 183 | assert_eq!(options.default_delay, 10); 184 | assert_eq!(options.verbose, false); 185 | assert_eq!(options.scaling, Scaling::NONE); 186 | assert_eq!(options.scaling_filter, ScalingFilter::AUTO); 187 | } 188 | 189 | #[test] 190 | fn when_argument_default_delay_is_not_u16_then_use_default() { 191 | // Test arbritary string 192 | let options = Options::_from_params(_create_params(vec!["-d", "a"])); 193 | assert_eq!(options.default_delay, 10); 194 | 195 | let min_boundary: i32 = u16::MIN as i32 - 1; 196 | let min_boundary_str = "\"".to_owned() + &min_boundary.to_string() + "\""; 197 | let options = Options::_from_params(_create_params(vec!["-d", &min_boundary_str])); 198 | assert_eq!(options.default_delay, 10); 199 | 200 | let max_boundary: i32 = u16::MAX as i32 + 1; 201 | let options = Options::_from_params(_create_params(vec!["-d", &max_boundary.to_string()])); 202 | assert_eq!(options.default_delay, 10); 203 | } 204 | 205 | #[test] 206 | fn when_argument_background_color_is_given_then_use_it() { 207 | let options = Options::_from_params(_create_params(vec!["-b", "white"])); 208 | assert_eq!(options.background_color, "white"); 209 | } 210 | 211 | #[test] 212 | fn when_argument_default_delay_is_given_then_use_it() { 213 | let options = Options::_from_params(_create_params(vec!["-d", "666"])); 214 | assert_eq!(options.default_delay, 666); 215 | } 216 | 217 | #[test] 218 | fn when_argument_verbose_is_given_then_be_it() { 219 | let options = Options::_from_params(_create_params(vec!["-v"])); 220 | assert_eq!(options.verbose, true); 221 | } 222 | 223 | #[test] 224 | fn when_argument_scale_is_none_then_match_enum() { 225 | let options = Options::_from_params(_create_params(vec!["-s", "NONE"])); 226 | assert_eq!(options.scaling, Scaling::NONE); 227 | } 228 | 229 | #[test] 230 | fn when_argument_scale_is_fill_then_match_enum() { 231 | let options = Options::_from_params(_create_params(vec!["-s", "FILL"])); 232 | assert_eq!(options.scaling, Scaling::FILL); 233 | } 234 | 235 | #[test] 236 | fn when_argument_scale_is_max_then_match_enum() { 237 | let options = Options::_from_params(_create_params(vec!["-s", "MAX"])); 238 | assert_eq!(options.scaling, Scaling::MAX); 239 | } 240 | 241 | #[test] 242 | fn when_argument_scale_filter_is_auto_then_match_enum() { 243 | let options = Options::_from_params(_create_params(vec!["--scale-filter", "AUTO"])); 244 | assert_eq!(options.scaling_filter, ScalingFilter::AUTO); 245 | } 246 | 247 | #[test] 248 | fn when_argument_scale_filter_is_pixel_then_match_enum() { 249 | let options = Options::_from_params(_create_params(vec!["--scale-filter", "PIXEL"])); 250 | assert_eq!(options.scaling_filter, ScalingFilter::PIXEL); 251 | } 252 | 253 | #[test] 254 | fn when_argument_window_id_is_given_then_use_it() { 255 | let options = Options::_from_params(_create_params(vec!["-w", "foobar"])); 256 | assert_eq!(options.window_id, "foobar"); 257 | } 258 | 259 | #[test] 260 | fn when_argument_window_id_is_not_given_then_option_is_empty_string() { 261 | let options = Options::_from_params(_create_params(vec![])); 262 | assert_eq!(options.window_id, ""); 263 | } 264 | 265 | fn _create_params(custom_params: Vec<&str>) -> Vec<&str> { 266 | [vec!["xgifwallpaper"], custom_params, vec![PATH_TO_GIF]].concat() 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/position.rs: -------------------------------------------------------------------------------- 1 | //! Compute position and resolution of images according to screen-resolutions 2 | //! and options for placement and scaling. 3 | use crate::screens::*; 4 | 5 | /// Alignments of 2-dimensional rectangles. 6 | pub enum Alignment { 7 | /// Center horizontally and vertically, relative to parent. 8 | CENTER, 9 | } 10 | 11 | /// Scaling-options. All options respect aspect-ratio. 12 | #[derive(Debug, PartialEq, Eq)] 13 | pub enum Scaling { 14 | /// Don't scale 15 | NONE, 16 | /// Fill the whole screen, even if content is cut off. 17 | FILL, 18 | /// Be as large as possible, without losing content. 19 | MAX, 20 | } 21 | 22 | /// Filter to use for scaling. 23 | #[derive(Debug, PartialEq, Eq)] 24 | pub enum ScalingFilter { 25 | /// Use best-quality for up- and down-scaling. 26 | AUTO, 27 | /// Use most simple and perfomant filter. 28 | PIXEL, 29 | } 30 | 31 | /// Coordinates to place an image. 32 | #[derive(Debug, Eq, PartialEq)] 33 | pub struct ImagePlacement { 34 | /// visible x-origin. 35 | pub src_x: i32, 36 | /// visible y-origin. 37 | pub src_y: i32, 38 | /// position on x-axis. 39 | pub dest_x: i32, 40 | /// position on y-axis. 41 | pub dest_y: i32, 42 | /// visible width, relative to src_x. 43 | pub width: u32, 44 | /// visible height, relative to src_y. 45 | pub height: u32, 46 | } 47 | 48 | impl ImagePlacement { 49 | pub fn new( 50 | src_x: i32, 51 | src_y: i32, 52 | dest_x: i32, 53 | dest_y: i32, 54 | width: u32, 55 | height: u32, 56 | ) -> ImagePlacement { 57 | ImagePlacement { 58 | src_x, 59 | src_y, 60 | dest_x, 61 | dest_y, 62 | width, 63 | height, 64 | } 65 | } 66 | } 67 | 68 | /// Width and height as one unit. 69 | #[derive(PartialEq, Eq, Clone, PartialOrd, Ord, Hash, Debug)] 70 | pub struct Resolution { 71 | pub width: u32, 72 | pub height: u32, 73 | } 74 | 75 | impl Resolution { 76 | /// Creates a new instance of `Resolution`. 77 | pub fn new(width: u32, height: u32) -> Resolution { 78 | Resolution { width, height } 79 | } 80 | 81 | /// Calculates the resolution an image should have to respect given scaling. 82 | pub fn fit_to_screen(&self, screen_resolution: &Resolution, scaling: &Scaling) -> Resolution { 83 | match *scaling { 84 | Scaling::NONE => self.clone(), 85 | Scaling::FILL => Resolution::scale_image_to_screen(self, screen_resolution, true), 86 | Scaling::MAX => Resolution::scale_image_to_screen(self, screen_resolution, false), 87 | } 88 | } 89 | 90 | /// Calculates the resolution an image should have to respect given scaling. 91 | /// 92 | /// If `fill` is `true` the whole screen is used, at cost of image-information. 93 | /// 94 | /// If `fill` is `false` the image is scaled as large as possible, without 95 | /// losing information. 96 | fn scale_image_to_screen( 97 | image_resolution: &Resolution, 98 | screen_resolution: &Resolution, 99 | fill: bool, 100 | ) -> Resolution { 101 | let mut result = Resolution::new(0, 0); 102 | 103 | let same_resolution = screen_resolution.width == image_resolution.width 104 | && screen_resolution.height == image_resolution.height; 105 | 106 | if same_resolution { 107 | result.width = screen_resolution.width; 108 | result.height = screen_resolution.height; 109 | } else { 110 | let screen_ratio = screen_resolution.width as f32 / screen_resolution.height as f32; 111 | let image_ratio = image_resolution.width as f32 / image_resolution.height as f32; 112 | 113 | let scale_to_width = if fill { 114 | screen_ratio > image_ratio 115 | } else { 116 | screen_ratio < image_ratio 117 | }; 118 | 119 | if scale_to_width { 120 | result.width = screen_resolution.width; 121 | 122 | let scale = screen_resolution.width as f32 / image_resolution.width as f32; 123 | result.height = (image_resolution.height as f32 * scale) as u32; 124 | } else { 125 | result.height = screen_resolution.height; 126 | 127 | let scale = screen_resolution.height as f32 / image_resolution.height as f32; 128 | result.width = (image_resolution.width as f32 * scale) as u32; 129 | } 130 | } 131 | 132 | result 133 | } 134 | 135 | /// Computes coordinates of image for given alignment on a screen. 136 | pub fn position_on_screen(&self, screen: &Screen, alignment: Alignment) -> ImagePlacement { 137 | match alignment { 138 | Alignment::CENTER => Resolution::center_on_screen(self.width, self.height, screen), 139 | } 140 | } 141 | 142 | /// Places an image on screen, aligned to the center. Both horizontally and 143 | /// vertically. 144 | fn center_on_screen(width: u32, height: u32, screen: &Screen) -> ImagePlacement { 145 | let mut out = ImagePlacement::new(0, 0, screen.x_org, screen.y_org, width, height); 146 | 147 | if width > screen.width { 148 | out.src_x = ((width - screen.width) / 2) as i32; 149 | out.width = screen.width; 150 | } 151 | 152 | if height > screen.height { 153 | out.src_y = ((height - screen.height) / 2) as i32; 154 | out.height = screen.height; 155 | } 156 | 157 | if screen.width > width { 158 | out.dest_x = screen.x_org + ((screen.width - width) / 2) as i32; 159 | } 160 | 161 | if screen.height > height { 162 | out.dest_y = screen.y_org + ((screen.height - height) / 2) as i32; 163 | } 164 | 165 | return out; 166 | } 167 | } 168 | 169 | #[cfg(test)] 170 | mod tests { 171 | use super::Alignment; 172 | use super::ImagePlacement; 173 | use super::Resolution; 174 | use super::Scaling; 175 | use super::Screen; 176 | 177 | #[test] 178 | fn when_position_is_center_then_target_resolutions_equals_image_resolution() { 179 | use std::collections::HashSet; 180 | 181 | let mut screen_resolutions: HashSet = HashSet::new(); 182 | screen_resolutions.insert(Resolution::new(1920, 1080)); 183 | screen_resolutions.insert(Resolution::new(1080, 1920)); 184 | screen_resolutions.insert(Resolution::new(2000, 2000)); 185 | 186 | let image_resolution = Resolution::new(1000, 1000); 187 | 188 | let actual = image_resolution.fit_to_screen(&Resolution::new(1080, 1920), &Scaling::NONE); 189 | 190 | assert_eq!(true, actual == image_resolution); 191 | } 192 | 193 | #[test] 194 | fn when_image_1000x1000_screen_1920_1080_position_fill_then_target_1920_1920() { 195 | _test_compute_fill_resolution( 196 | Resolution::new(1000, 1000), 197 | Resolution::new(1920, 1080), 198 | Resolution::new(1920, 1920), 199 | ); 200 | } 201 | 202 | #[test] 203 | fn when_image_1000x1000_screen_1080_1920_position_fill_then_target_1920_1920() { 204 | _test_compute_fill_resolution( 205 | Resolution::new(1000, 1000), 206 | Resolution::new(1080, 1920), 207 | Resolution::new(1920, 1920), 208 | ); 209 | } 210 | 211 | #[test] 212 | fn when_image_1920x1080_screen_1000_1000_position_fill_then_target_1777_1000() { 213 | _test_compute_fill_resolution( 214 | Resolution::new(1920, 1080), 215 | Resolution::new(1000, 1000), 216 | Resolution::new(1777, 1000), 217 | ); 218 | } 219 | 220 | #[test] 221 | fn when_image_1080x1920_screen_1000_1000_position_fill_then_target_1000_1777() { 222 | _test_compute_fill_resolution( 223 | Resolution::new(1080, 1920), 224 | Resolution::new(1000, 1000), 225 | Resolution::new(1000, 1777), 226 | ); 227 | } 228 | 229 | #[test] 230 | fn when_image_1920x1080_screen_1920_1080_position_fill_then_target_1920_1080() { 231 | _test_compute_fill_resolution( 232 | Resolution::new(1920, 1080), 233 | Resolution::new(1920, 1080), 234 | Resolution::new(1920, 1080), 235 | ); 236 | } 237 | 238 | #[test] 239 | fn when_image_1000x1000_screen_1500_500_position_fill_then_target_1500_1500() { 240 | _test_compute_fill_resolution( 241 | Resolution::new(1000, 1000), 242 | Resolution::new(1500, 500), 243 | Resolution::new(1500, 1500), 244 | ); 245 | } 246 | 247 | #[test] 248 | fn when_image_1000x1000_screen_500_1500_position_fill_then_target_1500_1500() { 249 | _test_compute_fill_resolution( 250 | Resolution::new(1000, 1000), 251 | Resolution::new(500, 1500), 252 | Resolution::new(1500, 1500), 253 | ); 254 | } 255 | 256 | #[test] 257 | fn when_image_2x1_screen_2560_1440_position_fill_then_target_2560_2560() { 258 | _test_compute_fill_resolution( 259 | Resolution::new(2, 1), 260 | Resolution::new(2560, 1440), 261 | Resolution::new(2880, 1440), 262 | ); 263 | } 264 | 265 | #[test] 266 | fn when_image_1000x1000_screen_1920_1080_position_max_then_target_1080_1080() { 267 | _test_compute_max_resolution( 268 | Resolution::new(1000, 1000), 269 | Resolution::new(1920, 1080), 270 | Resolution::new(1080, 1080), 271 | ); 272 | } 273 | 274 | #[test] 275 | fn when_image_1000x1000_screen_1080_1920_position_max_then_target_1080_1080() { 276 | _test_compute_max_resolution( 277 | Resolution::new(1000, 1000), 278 | Resolution::new(1080, 1920), 279 | Resolution::new(1080, 1080), 280 | ); 281 | } 282 | 283 | #[test] 284 | fn when_image_1920x1080_screen_1000_1000_position_max_then_target_1000_562() { 285 | _test_compute_max_resolution( 286 | Resolution::new(1920, 1080), 287 | Resolution::new(1000, 1000), 288 | Resolution::new(1000, 562), 289 | ); 290 | } 291 | 292 | #[test] 293 | fn when_image_1080x1920_screen_1000_1000_position_max_then_target_562_1000() { 294 | _test_compute_max_resolution( 295 | Resolution::new(1080, 1920), 296 | Resolution::new(1000, 1000), 297 | Resolution::new(562, 1000), 298 | ); 299 | } 300 | 301 | #[test] 302 | fn when_image_1920x1080_screen_1920_1080_position_max_then_target_1920_1080() { 303 | _test_compute_max_resolution( 304 | Resolution::new(1920, 1080), 305 | Resolution::new(1920, 1080), 306 | Resolution::new(1920, 1080), 307 | ); 308 | } 309 | 310 | #[test] 311 | fn when_image_1000x1000_screen_1500_500_position_max_then_target_500_500() { 312 | _test_compute_max_resolution( 313 | Resolution::new(1000, 1000), 314 | Resolution::new(1500, 500), 315 | Resolution::new(500, 500), 316 | ); 317 | } 318 | 319 | #[test] 320 | fn when_image_1000x1080_screen_500_1500_position_max_then_target_500_500() { 321 | _test_compute_max_resolution( 322 | Resolution::new(1000, 1000), 323 | Resolution::new(500, 1500), 324 | Resolution::new(500, 500), 325 | ); 326 | } 327 | 328 | #[test] 329 | fn when_image_2x1_screen_2560_1440_position_max_then_target_2560_2560() { 330 | _test_compute_max_resolution( 331 | Resolution::new(2, 1), 332 | Resolution::new(2560, 1440), 333 | Resolution::new(2560, 1280), 334 | ); 335 | } 336 | 337 | #[test] 338 | fn when_image_1x1_screen_3x3_then_center() { 339 | _test_center_on_screen(1, 1, ImagePlacement::new(0, 0, 1, 1, 1, 1)); 340 | } 341 | 342 | #[test] 343 | fn when_image_1x3_screen_3x3_then_center() { 344 | _test_center_on_screen(1, 3, ImagePlacement::new(0, 0, 1, 0, 1, 3)); 345 | } 346 | 347 | #[test] 348 | fn when_image_3x1_screen_3x3_then_center() { 349 | _test_center_on_screen(3, 1, ImagePlacement::new(0, 0, 0, 1, 3, 1)); 350 | } 351 | 352 | #[test] 353 | fn when_image_3x3_screen_3x3_then_center() { 354 | _test_center_on_screen(3, 3, ImagePlacement::new(0, 0, 0, 0, 3, 3)); 355 | } 356 | 357 | #[test] 358 | fn when_image_1x5_screen_3x3_then_center() { 359 | _test_center_on_screen(1, 5, ImagePlacement::new(0, 1, 1, 0, 1, 3)); 360 | } 361 | 362 | #[test] 363 | fn when_image_5x1_screen_3x3_then_center() { 364 | _test_center_on_screen(5, 1, ImagePlacement::new(1, 0, 0, 1, 3, 1)); 365 | } 366 | 367 | #[test] 368 | fn when_image_5x5_screen_3x3_then_center() { 369 | let screen = _create_screen0_3x3(); 370 | let actual = Resolution::new(5, 5).position_on_screen(&screen, Alignment::CENTER); 371 | assert_eq!(actual, ImagePlacement::new(1, 1, 0, 0, 3, 3)); 372 | } 373 | 374 | fn _test_compute_fill_resolution(image: Resolution, screen: Resolution, expected: Resolution) { 375 | _test_compute_resolution(image, screen, Scaling::FILL, expected); 376 | } 377 | 378 | fn _test_compute_max_resolution(image: Resolution, screen: Resolution, expected: Resolution) { 379 | _test_compute_resolution(image, screen, Scaling::MAX, expected); 380 | } 381 | 382 | fn _test_compute_resolution( 383 | image: Resolution, 384 | screen: Resolution, 385 | scaling: Scaling, 386 | expected: Resolution, 387 | ) { 388 | let actual = image.fit_to_screen(&screen, &scaling); 389 | 390 | if actual != expected { 391 | eprintln!("actual != expected: {:?} != {:?}", actual, expected); 392 | } 393 | 394 | assert_eq!(true, actual == expected); 395 | } 396 | 397 | fn _create_screen0_3x3() -> Screen { 398 | Screen { 399 | screen_number: 0, 400 | x_org: 0, 401 | y_org: 0, 402 | width: 3, 403 | height: 3, 404 | } 405 | } 406 | 407 | fn _test_center_on_screen(width: u32, height: u32, expected: ImagePlacement) { 408 | let screen = _create_screen0_3x3(); 409 | let actual = Resolution::new(width, height).position_on_screen(&screen, Alignment::CENTER); 410 | assert_eq!(actual, expected); 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /src/screens.rs: -------------------------------------------------------------------------------- 1 | //! Query `Screens` and define `Screen`-structure. 2 | 3 | use std::os::raw::c_int; 4 | use std::ptr; 5 | 6 | use x11::xinerama; 7 | use x11::xlib; 8 | 9 | /// Collection of screens. 10 | #[derive(Debug)] 11 | pub struct Screens { 12 | /// `true`, if every screen has its own root-window. 13 | pub root_per_screen: bool, 14 | /// Ordered list of `Screen`. 15 | pub screens: Vec, 16 | } 17 | 18 | /// Information about a single screen. 19 | #[derive(Clone, Debug)] 20 | pub struct Screen { 21 | /// Logical number / id of screen. 22 | pub screen_number: i32, 23 | /// Origin on x-axis of this screen in a combined display-arrangement. 24 | pub x_org: i32, 25 | /// Origin on y-axis of this screen in a combined display-arrangement. 26 | pub y_org: i32, 27 | /// Width of this screen. 28 | pub width: u32, 29 | /// Height of this screen. 30 | pub height: u32, 31 | } 32 | 33 | impl Screens { 34 | /// Queries the running x-server for available screens. 35 | pub fn query_x_screens() -> Screens { 36 | let mut root_per_screen = false; 37 | let mut screens: Vec = Vec::new(); 38 | 39 | unsafe { 40 | let display = xlib::XOpenDisplay(ptr::null()); 41 | 42 | if Screens::use_xinerama(display) { 43 | let mut screen_count = 0; 44 | let xscreens = xinerama::XineramaQueryScreens(display, &mut screen_count); 45 | 46 | for i in 0..(screen_count) { 47 | screens.push(Screen { 48 | screen_number: (*xscreens.offset(i as isize)).screen_number, 49 | x_org: (*xscreens.offset(i as isize)).x_org as i32, 50 | y_org: (*xscreens.offset(i as isize)).y_org as i32, 51 | width: (*xscreens.offset(i as isize)).width as u32, 52 | height: (*xscreens.offset(i as isize)).height as u32, 53 | }); 54 | } 55 | } else { 56 | root_per_screen = true; 57 | 58 | let screen_count = xlib::XScreenCount(display); 59 | 60 | for i in 0..(screen_count) { 61 | screens.push(Screen { 62 | screen_number: i, 63 | x_org: 0, 64 | y_org: 0, 65 | width: xlib::XDisplayWidth(display, i) as u32, 66 | height: xlib::XDisplayHeight(display, i) as u32, 67 | }); 68 | } 69 | } 70 | 71 | xlib::XCloseDisplay(display); 72 | } 73 | 74 | Screens { 75 | root_per_screen, 76 | screens, 77 | } 78 | } 79 | 80 | /// `true` if current x-session supports Xinerama 81 | unsafe fn use_xinerama(display: *mut xlib::Display) -> bool { 82 | let mut event_base_return: c_int = 0; 83 | let mut error_base_return: c_int = 0; 84 | 85 | let has_extension = xinerama::XineramaQueryExtension( 86 | display, 87 | &mut event_base_return, 88 | &mut error_base_return, 89 | ); 90 | 91 | let is_active = xinerama::XineramaIsActive(display); 92 | 93 | return has_extension == xlib::True && is_active == xlib::True; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/shm.rs: -------------------------------------------------------------------------------- 1 | //! Encapsule handling with the xshm-extension of the X-server. 2 | //! 3 | //! The shared memory is used to avoid expensive transfer of frame-images 4 | //! between client and server. Thus bringing a significant performance-boost. 5 | 6 | use std::mem; 7 | use std::os::raw::{c_char, c_int}; 8 | use std::ptr::{null, null_mut}; 9 | 10 | use x11::xlib::*; 11 | use x11::xshm; 12 | 13 | /// Returns `true` if X-Server supports xshm. 14 | pub fn is_xshm_available(display: *mut Display) -> bool { 15 | unsafe { xshm::XShmQueryExtension(display) == True } 16 | } 17 | 18 | /// Creates info-structure for the shared-memory-segment. This structure must 19 | /// exist as long as the segment and data itself. 20 | pub fn create_xshm_sgmnt_inf(size: usize) -> Result, u8> { 21 | use libc::size_t; 22 | let shmid: c_int = 23 | unsafe { libc::shmget(libc::IPC_PRIVATE, size as size_t, libc::IPC_CREAT | 0o777) }; 24 | if shmid < 0 { 25 | return Err(1); 26 | } 27 | let shmaddr: *mut libc::c_void = unsafe { libc::shmat(shmid, null(), 0) }; 28 | if shmaddr == ((usize::max_value()) as *mut libc::c_void) { 29 | return Err(2); 30 | } 31 | let mut shmidds: libc::shmid_ds = unsafe { mem::zeroed() }; 32 | unsafe { libc::shmctl(shmid, libc::IPC_RMID, &mut shmidds) }; 33 | 34 | Ok(Box::new(xshm::XShmSegmentInfo { 35 | shmseg: 0, 36 | shmid, 37 | shmaddr: (shmaddr as *mut c_char), 38 | readOnly: 0, 39 | })) 40 | } 41 | 42 | /// Destroys the given info-structure and frees its memory. 43 | pub fn destroy_xshm_sgmnt_inf(seginf: &mut Box) { 44 | unsafe { libc::shmdt(seginf.shmaddr as *mut libc::c_void) }; 45 | } 46 | 47 | /// Creates a new `XImage`-instance, representing the data of the shared memory 48 | /// segment. 49 | pub fn create_xshm_image( 50 | dspl: *mut Display, 51 | vsl: *mut Visual, 52 | xshminfo: &mut Box, 53 | width: u32, 54 | height: u32, 55 | depth: u32, 56 | ) -> Result<*mut XImage, u8> { 57 | unsafe { 58 | let ximg = xshm::XShmCreateImage( 59 | dspl, 60 | vsl, 61 | depth, 62 | ZPixmap, 63 | null_mut(), 64 | xshminfo.as_mut() as *mut _, 65 | width, 66 | height, 67 | ); 68 | if ximg == null_mut() { 69 | return Err(1); 70 | } 71 | Ok(ximg) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/xatoms.rs: -------------------------------------------------------------------------------- 1 | //! This module encapsules the atom-handling with the X-server. 2 | //! 3 | //! There are two atoms on an X-server which hold the id of the pixmap used as 4 | //! background for the root window. An X-compositors listens to changes on these 5 | //! atoms and informs X-clients accordingly, so they may redraw their pseudo- 6 | //! transparent background. 7 | 8 | use crate::Options; 9 | use crate::XContext; 10 | 11 | use std::ffi::CString; 12 | 13 | use std::os::raw::{c_char, c_int, c_uchar, c_ulong}; 14 | use std::sync::Arc; 15 | 16 | use x11::xlib::{ 17 | Display, False, Pixmap, PropModeReplace, True, Window, XChangeProperty, XGetWindowProperty, 18 | XInternAtom, XKillClient, XA_PIXMAP, XA_WINDOW, 19 | }; 20 | 21 | const ATOM_XROOTPMAP_ID: &str = "_XROOTPMAP_ID"; 22 | const ATOM_ESETROOT_PMAP_ID: &str = "ESETROOT_PMAP_ID"; 23 | 24 | /// Convenience: Get or create atom-id with name `_XROOTPMAP_ID`. 25 | pub fn get_root_pixmap_atom(display: *mut Display) -> c_ulong { 26 | get_atom(display, get_atom_name(ATOM_XROOTPMAP_ID).as_ptr(), False) 27 | } 28 | 29 | /// Convenience: Get or create atom-id with name `ESETROOT_PMAP_ID`. 30 | pub fn get_eroot_pixmap_atom(display: *mut Display) -> c_ulong { 31 | get_atom( 32 | display, 33 | get_atom_name(ATOM_ESETROOT_PMAP_ID).as_ptr(), 34 | False, 35 | ) 36 | } 37 | 38 | fn get_atom_name(name: &str) -> CString { 39 | CString::new(name).unwrap() 40 | } 41 | 42 | /// Gets the atom id. May create the atom on the server. If that fails or the 43 | /// atom does not exist and should not be created, may return `xlib::False`. 44 | pub fn get_atom(display: *mut Display, name: *const c_char, only_if_exists: c_int) -> c_ulong { 45 | unsafe { XInternAtom(display, name, only_if_exists) } 46 | } 47 | 48 | /// Convenience: Remove the pixmap related atoms on the root window. 49 | pub fn remove_root_pixmap_atoms(xcontext: &Box, options: Arc) -> bool { 50 | let mut removed_atoms = false; 51 | 52 | let atom_root = get_atom( 53 | xcontext.display, 54 | get_atom_name(ATOM_XROOTPMAP_ID).as_ptr(), 55 | True, 56 | ); 57 | if atom_root != 0 { 58 | removed_atoms = remove_root_pixmap_atom(&xcontext, atom_root, options.clone()); 59 | } 60 | 61 | let atom_eroot = get_atom( 62 | xcontext.display, 63 | get_atom_name(ATOM_ESETROOT_PMAP_ID).as_ptr(), 64 | True, 65 | ); 66 | if atom_eroot != 0 { 67 | removed_atoms = 68 | removed_atoms || remove_root_pixmap_atom(&xcontext, atom_eroot, options.clone()); 69 | } 70 | 71 | removed_atoms 72 | } 73 | 74 | fn remove_root_pixmap_atom(xcontext: &Box, atom: c_ulong, options: Arc) -> bool { 75 | let pixmap_result = query_window_propery_as_pixmap_id(xcontext.display, xcontext.root, atom); 76 | 77 | if pixmap_result.is_ok() { 78 | let pixmap = pixmap_result.unwrap(); 79 | 80 | if xcontext.pixmap != pixmap { 81 | if options.verbose { 82 | println!("Kill client responsible for _XROOTPMAP_ID {}", pixmap); 83 | } 84 | 85 | delete_atom(&xcontext, atom); 86 | unsafe { XKillClient(xcontext.display, pixmap) }; 87 | return true; 88 | } 89 | } 90 | 91 | false 92 | } 93 | 94 | pub fn query_window_propery_as_pixmap_id( 95 | display: *mut Display, 96 | window: c_ulong, 97 | atom: c_ulong, 98 | ) -> Result { 99 | let (_, ptype, _, _, _, data_ptr) = query_window_propery(display, window, atom, XA_PIXMAP); 100 | 101 | if ptype != XA_PIXMAP { 102 | return Err("Given atom is not a pixmap-id".to_string()); 103 | } 104 | 105 | let pixmap = unsafe { Box::from_raw(data_ptr as *mut x11::xlib::Pixmap) }; 106 | 107 | Ok(*pixmap) 108 | } 109 | 110 | pub fn query_window_propery_as_window_id( 111 | display: *mut Display, 112 | window: c_ulong, 113 | atom: c_ulong, 114 | ) -> Result { 115 | let (_, ptype, _, _, _, data_ptr) = query_window_propery(display, window, atom, XA_WINDOW); 116 | 117 | if ptype != XA_WINDOW { 118 | return Err("Given atom is not a window-id".to_string()); 119 | } 120 | 121 | let window = unsafe { Box::from_raw(data_ptr as *mut Window) }; 122 | 123 | Ok(*window) 124 | } 125 | 126 | /// Queries the specified atom, if existing. 127 | /// Return tuple specifies 128 | /// * result 129 | /// * ptype 130 | /// * format 131 | /// * length 132 | /// * pointer to data 133 | /// 134 | /// As specified in the X-manual: 135 | /// https://www.x.org/releases/X11R7.7/doc/man/man3/XGetWindowProperty.3.xhtml 136 | fn query_window_propery( 137 | display: *mut Display, 138 | window: c_ulong, 139 | atom: c_ulong, 140 | property_type: c_ulong, 141 | ) -> (c_int, c_ulong, c_int, c_ulong, c_ulong, *mut c_uchar) { 142 | let mut ptr = std::mem::MaybeUninit::<*mut c_uchar>::uninit(); 143 | 144 | let mut ptype = 0 as u64; 145 | let mut format = 0 as i32; 146 | let mut length = 0 as u64; 147 | let mut after = 0 as u64; 148 | 149 | let result = unsafe { 150 | XGetWindowProperty( 151 | display, 152 | window, 153 | atom, 154 | 0, 155 | 1, 156 | False, 157 | property_type, 158 | &mut ptype, 159 | &mut format, 160 | &mut length, 161 | &mut after, 162 | ptr.as_mut_ptr(), 163 | ) 164 | }; 165 | 166 | (result, ptype, format, length, after, unsafe { 167 | ptr.assume_init() 168 | }) 169 | } 170 | 171 | pub fn delete_atom(xcontext: &Box, atom: c_ulong) -> bool { 172 | unsafe { x11::xlib::XDeleteProperty(xcontext.display, xcontext.root, atom) == True } 173 | } 174 | 175 | pub fn update_root_pixmap_atoms( 176 | display: *mut Display, 177 | root: u64, 178 | pixmap_ptr: *const Pixmap, 179 | atom_root: c_ulong, 180 | atom_eroot: c_ulong, 181 | ) -> bool { 182 | // The pixmap itself has not changed, but its content. XChangeProperty 183 | // generates messages to all X-clients, to update their own rendering, if 184 | // needed. 185 | update_root_pixmap_atom(display, root, pixmap_ptr, atom_root); 186 | update_root_pixmap_atom(display, root, pixmap_ptr, atom_eroot); 187 | 188 | true 189 | } 190 | 191 | fn update_root_pixmap_atom( 192 | display: *mut Display, 193 | root: u64, 194 | pixmap_ptr: *const Pixmap, 195 | atom: c_ulong, 196 | ) { 197 | unsafe { 198 | XChangeProperty( 199 | display, 200 | root, 201 | atom, 202 | XA_PIXMAP, 203 | 32, 204 | PropModeReplace, 205 | pixmap_ptr as *const u8, 206 | 1, 207 | ) 208 | }; 209 | } 210 | -------------------------------------------------------------------------------- /src/xcontext.rs: -------------------------------------------------------------------------------- 1 | //! X11-specific control-data, references and connection-handling. 2 | 3 | use std::error::Error; 4 | use std::ffi::CString; 5 | use std::os::raw::{c_char, c_int, c_uint, c_ulong}; 6 | use std::ptr; 7 | use std::result::*; 8 | use std::sync::Arc; 9 | 10 | use x11::xlib::{ 11 | Display, FillSolid, Pixmap, XAllocColor, XClearWindow, XCloseDisplay, XColor, 12 | XConnectionNumber, XCreatePixmap, XDefaultColormap, XDefaultDepth, XDefaultGC, XDefaultScreen, 13 | XDisplayHeight, XDisplayWidth, XDrawRectangle, XFillRectangle, XFreePixmap, XOpenDisplay, 14 | XParseColor, XRootWindow, XSetBackground, XSetFillStyle, XSetForeground, XSetWindowBackground, 15 | GC, 16 | }; 17 | 18 | use crate::options::Options; 19 | use crate::shm::is_xshm_available; 20 | use crate::xatoms::{get_atom, query_window_propery_as_window_id}; 21 | 22 | const EXIT_NO_XDISPLAY: i32 = 100; 23 | const EXIT_XSHM_UNSUPPORTED: i32 = 101; 24 | const EXIT_UNKOWN_COLOR: i32 = 102; 25 | const EXIT_INVALID_WINDOW_ID: i32 = 104; 26 | 27 | /// X11-specific control-data and references. 28 | #[derive(Debug)] 29 | pub struct XContext { 30 | pub background_color: XColor, 31 | pub display: *mut Display, 32 | pub gc: GC, 33 | pub pixmap: Pixmap, 34 | pub root: c_ulong, 35 | pub screen: c_int, 36 | options: Arc, 37 | } 38 | 39 | impl XContext { 40 | /// Start the X11-lifecycle: 41 | /// 42 | /// * Creates a connection to the default display of X 43 | /// * Checks if XSHM is available, exits the process otherwise 44 | /// * Queries defaults for screen, gc and root window 45 | /// * Parses given color in option as X11-color 46 | /// * Prepares the pixmap for frame-drawing 47 | /// * Parses the option for alternate window-id, than root 48 | pub fn new(opts: Arc) -> Result { 49 | let display = unsafe { XOpenDisplay(ptr::null()) }; 50 | 51 | log!(opts, "Open X-display: "); 52 | 53 | if display.is_null() { 54 | return Err(XContextError::with( 55 | EXIT_NO_XDISPLAY, 56 | "Failed to open display. Is X running in your session?".to_string(), 57 | )); 58 | } 59 | 60 | return XContext::new_with_display(opts, display); 61 | } 62 | 63 | fn new_with_display( 64 | opts: Arc, 65 | display: *mut Display, 66 | ) -> Result { 67 | if !is_xshm_available(display) { 68 | return Err(XContextError::with( 69 | EXIT_XSHM_UNSUPPORTED, 70 | "The X server in use does not support the shared memory extension (xshm)." 71 | .to_string(), 72 | )); 73 | } 74 | 75 | logln!(opts, "connection-number={:?}", unsafe { 76 | XConnectionNumber(display) 77 | }); 78 | 79 | log!(opts, "Query context from X server: "); 80 | 81 | let screen = unsafe { XDefaultScreen(display) }; 82 | let gc = unsafe { XDefaultGC(display, screen) }; 83 | let root = unsafe { XRootWindow(display, screen) }; 84 | 85 | let window = if opts.window_id.is_empty() { 86 | root 87 | } else { 88 | parse_window_id(display, root, &opts.window_id)? 89 | }; 90 | 91 | logln!( 92 | opts, 93 | "DefaultScreen={:?}, DefaultGC={:?}, RootWindow={:?}, WindowToUse={:?}", 94 | screen, 95 | gc, 96 | root, 97 | window 98 | ); 99 | 100 | let background_color = parse_color(display, screen, opts.clone())?; 101 | let pixmap = prepare_pixmap(display, screen, gc, window, &background_color); 102 | 103 | Ok(XContext { 104 | background_color, 105 | display, 106 | gc, 107 | pixmap, 108 | root: window, 109 | screen, 110 | options: opts.clone(), 111 | }) 112 | } 113 | } 114 | 115 | impl Drop for XContext { 116 | fn drop(&mut self) { 117 | let options = self.options.clone(); 118 | 119 | unsafe { 120 | logln!(options, "Free pixmap used for background"); 121 | XFreePixmap(self.display, self.pixmap); 122 | 123 | logln!(options, "Reset background to solid black and clear window"); 124 | XSetWindowBackground( 125 | self.display, 126 | self.root, 127 | x11::xlib::XBlackPixel(self.display, self.screen), 128 | ); 129 | XClearWindow(self.display, self.root); 130 | 131 | XCloseDisplay(self.display); 132 | } 133 | } 134 | } 135 | 136 | fn parse_window_id( 137 | display: *mut Display, 138 | root: c_ulong, 139 | window_id: &str, 140 | ) -> Result { 141 | // Check if decimal 142 | let decimal: Result = window_id.parse(); 143 | if decimal.is_ok() { 144 | return Ok(decimal.unwrap()); 145 | } 146 | 147 | // Else check if hexadecimal 148 | if window_id.starts_with("0x") { 149 | let decimal = c_ulong::from_str_radix(window_id.trim_start_matches("0x"), 16); 150 | if decimal.is_ok() { 151 | return Ok(decimal.unwrap()); 152 | } 153 | } 154 | 155 | // Else ask root window for property, then check if hexadecimal or decimal 156 | let atom = get_atom(display, window_id.as_ptr() as *const c_char, x11::xlib::True); 157 | if atom == x11::xlib::False as u64 { 158 | return Err(XContextError::with( 159 | EXIT_INVALID_WINDOW_ID, 160 | "Given window_id is neither a decimal or hexadecimal value, nor 161 | does an atom with name exists on root window." 162 | .to_string(), 163 | )); 164 | } 165 | 166 | match query_window_propery_as_window_id(display, root, atom) { 167 | Ok(prop_window_id) => Ok(prop_window_id), 168 | Err(e) => Err(XContextError::with(EXIT_INVALID_WINDOW_ID, e)), 169 | } 170 | } 171 | 172 | /// Parse string as X11-color. 173 | fn parse_color( 174 | display: *mut Display, 175 | screen: c_int, 176 | opts: Arc, 177 | ) -> Result { 178 | let mut xcolor: XColor = XColor { 179 | pixel: 0, 180 | red: 0, 181 | green: 0, 182 | blue: 0, 183 | flags: 0, 184 | pad: 0, 185 | }; 186 | 187 | let xcolor_ptr: *mut XColor = &mut xcolor; 188 | let color_str = opts.background_color.as_str(); 189 | let color_cstr = CString::new(color_str).unwrap(); 190 | let cmap = unsafe { XDefaultColormap(display, screen) }; 191 | 192 | log!(opts, "Parse \"{}\" as X11-color: ", color_str); 193 | 194 | let result = unsafe { XParseColor(display, cmap, color_cstr.as_ptr(), xcolor_ptr) }; 195 | 196 | if result == 0 { 197 | unsafe { XCloseDisplay(display) }; 198 | 199 | return Err(XContextError::with( 200 | EXIT_UNKOWN_COLOR, 201 | format!( 202 | "Unable to parse {} as X11-color. Try hex-color format: #RRGGBB.", 203 | color_str 204 | ), 205 | )); 206 | } 207 | 208 | unsafe { XAllocColor(display, cmap, xcolor_ptr) }; 209 | 210 | logln!(opts, "{:?}", xcolor); 211 | 212 | Ok(xcolor) 213 | } 214 | 215 | /// Create and prepare the pixmap, where the wallpaper is drawn onto. 216 | fn prepare_pixmap( 217 | dsp: *mut Display, 218 | scr: c_int, 219 | gc: GC, 220 | root: c_ulong, 221 | background_color: &XColor, 222 | ) -> Pixmap { 223 | unsafe { 224 | let dsp_width = XDisplayWidth(dsp, scr) as c_uint; 225 | let dsp_height = XDisplayHeight(dsp, scr) as c_uint; 226 | let depth = XDefaultDepth(dsp, scr) as c_uint; 227 | 228 | let pixmap = XCreatePixmap(dsp, root, dsp_width, dsp_height, depth); 229 | 230 | XSetForeground(dsp, gc, background_color.pixel); 231 | XSetBackground(dsp, gc, background_color.pixel); 232 | XSetFillStyle(dsp, gc, FillSolid); 233 | 234 | XDrawRectangle(dsp, pixmap, gc, 0, 0, dsp_width, dsp_height); 235 | XFillRectangle(dsp, pixmap, gc, 0, 0, dsp_width, dsp_height); 236 | 237 | pixmap 238 | } 239 | } 240 | 241 | #[derive(Debug, Clone)] 242 | pub struct XContextError { 243 | pub code: i32, 244 | pub message: String, 245 | } 246 | 247 | impl XContextError { 248 | fn with(code: i32, message: String) -> XContextError { 249 | XContextError { code, message } 250 | } 251 | } 252 | 253 | impl Error for XContextError {} 254 | 255 | impl std::fmt::Display for XContextError { 256 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 257 | write!(f, "{}", self.message) 258 | } 259 | } 260 | 261 | #[cfg(all(test, feature = "x11-integration-tests"))] 262 | mod tests { 263 | use std::ffi::CString; 264 | use std::os::raw::{c_uint, c_ulong}; 265 | use std::sync::Arc; 266 | 267 | use x11::xlib::*; 268 | 269 | use super::Options; 270 | use super::XContext; 271 | use super::EXIT_INVALID_WINDOW_ID; 272 | use super::EXIT_UNKOWN_COLOR; 273 | 274 | use crate::position::Scaling; 275 | use crate::position::ScalingFilter; 276 | 277 | #[test] 278 | fn when_option_window_id_is_decimal_then_use_as_root() { 279 | let display = open_display(); // Display is automatically Closed by XCoontext-desctructor 280 | 281 | let window_id = create_window(display); 282 | 283 | let xcontext = 284 | XContext::new_with_display(create_options(window_id.to_string().as_str()), display) 285 | .unwrap(); 286 | 287 | assert_eq!(xcontext.root, window_id); 288 | } 289 | 290 | #[test] 291 | fn when_option_window_id_is_hexdecimal_then_use_as_root() { 292 | let display = open_display(); // Display is automatically Closed by XCoontext-desctructor 293 | 294 | let window_id = create_window(display); 295 | let hex_value = format!("0x{:X}", window_id); 296 | 297 | let xcontext = XContext::new_with_display(create_options(&hex_value), display).unwrap(); 298 | 299 | assert_eq!(xcontext.root, window_id); 300 | } 301 | 302 | #[test] 303 | fn when_option_window_id_is_empty_then_use_root_window() { 304 | guard_x11_test(); 305 | 306 | let xcontext = XContext::new(create_options("")).unwrap(); 307 | let root = unsafe { x11::xlib::XRootWindow(xcontext.display, xcontext.screen) }; 308 | 309 | assert_eq!(xcontext.root, root); 310 | } 311 | 312 | #[test] 313 | fn when_option_window_id_is_not_an_atom_name_then_return_error() { 314 | guard_x11_test(); 315 | 316 | match XContext::new(create_options("foobar")) { 317 | Ok(_) => assert!(false, "Creation of XContext must fail, when arbritary string is given, but is not an atom on root."), 318 | Err(e) => assert_eq!(EXIT_INVALID_WINDOW_ID, e.code) 319 | } 320 | } 321 | 322 | #[test] 323 | fn when_option_window_id_is_an_atom_name_then_parse_its_value() { 324 | let display = open_display(); // Display is automatically Closed by XCoontext-desctructor 325 | 326 | let window_id = create_window(display); 327 | create_atom_window_id("test_atom", window_id as u32, Some(display)); 328 | 329 | let xcontext = XContext::new(create_options("test_atom")); 330 | 331 | delete_atom("test_atom", Some(display)); 332 | 333 | match xcontext { 334 | Ok(xcontext) => assert_eq!(xcontext.root, window_id), 335 | Err(_) => assert!(false), 336 | }; 337 | } 338 | 339 | #[test] 340 | fn when_option_window_id_is_an_atom_name_then_parse_its_value_and_fail_if_not_an_id() { 341 | guard_x11_test(); 342 | 343 | create_atom_string("foo", "bar", None); 344 | 345 | let xcontext = XContext::new(create_options("foo")); 346 | 347 | delete_atom("foo", None); 348 | 349 | match xcontext { 350 | Ok(_) => assert!(false), 351 | Err(e) => assert_eq!(EXIT_INVALID_WINDOW_ID, e.code), 352 | }; 353 | } 354 | 355 | #[test] 356 | fn when_background_color_is_hex_rgb_then_use_as_xcolor() { 357 | guard_x11_test(); 358 | 359 | let result = XContext::new(create_option_with_color("#00ffff")); 360 | 361 | match result { 362 | Ok(xcontext) => { 363 | assert_eq!(65535, xcontext.background_color.pixel); 364 | assert_eq!(0, xcontext.background_color.red); 365 | assert_eq!(65535, xcontext.background_color.green); 366 | assert_eq!(65535, xcontext.background_color.blue); 367 | } 368 | Err(_) => assert!(false), 369 | }; 370 | } 371 | 372 | #[test] 373 | fn when_background_color_is_x11_color_name_then_use_as_xcolor() { 374 | guard_x11_test(); 375 | 376 | let result = XContext::new(create_option_with_color("red")); 377 | 378 | match result { 379 | Ok(xcontext) => { 380 | assert_eq!(16711680, xcontext.background_color.pixel); 381 | assert_eq!(65535, xcontext.background_color.red); 382 | assert_eq!(0, xcontext.background_color.green); 383 | assert_eq!(0, xcontext.background_color.blue); 384 | } 385 | Err(_) => assert!(false), 386 | }; 387 | } 388 | 389 | #[test] 390 | fn when_background_color_is_not_x11_compilant_then_fail() { 391 | guard_x11_test(); 392 | 393 | let result = XContext::new(create_option_with_color("foobar")); 394 | 395 | match result { 396 | Ok(_) => assert!(false, "foobar must not result in valid background_color."), 397 | Err(e) => assert_eq!(EXIT_UNKOWN_COLOR, e.code), 398 | }; 399 | } 400 | 401 | fn open_display() -> *mut Display { 402 | let display = unsafe { x11::xlib::XOpenDisplay(std::ptr::null()) }; 403 | 404 | if display.is_null() { 405 | assert!(false, "This test must run in a X11-session."); 406 | } 407 | 408 | display 409 | } 410 | 411 | fn guard_x11_test() { 412 | let display = open_display(); 413 | 414 | unsafe { x11::xlib::XCloseDisplay(display) }; 415 | } 416 | 417 | fn create_window(display: *mut Display) -> c_ulong { 418 | unsafe { 419 | let screen = XDefaultScreen(display); 420 | let root = XRootWindow(display, screen); 421 | let dsp_width = XDisplayWidth(display, screen) as c_uint; 422 | let dsp_height = XDisplayHeight(display, screen) as c_uint; 423 | let black_pixel = XBlackPixel(display, screen); 424 | 425 | return XCreateSimpleWindow( 426 | display, 427 | root, 428 | 0, 429 | 0, 430 | dsp_width, 431 | dsp_height, 432 | 0, 433 | black_pixel, 434 | black_pixel, 435 | ); 436 | } 437 | } 438 | 439 | fn create_atom_window_id(name: &str, window_id: u32, existing_display: Option<*mut Display>) { 440 | create_atom( 441 | name, 442 | Box::into_raw(Box::new(window_id)) as *const u8, 443 | x11::xlib::XA_WINDOW, 444 | existing_display, 445 | ); 446 | } 447 | 448 | fn create_atom_string(name: &str, value: &str, existing_display: Option<*mut Display>) { 449 | create_atom( 450 | name, 451 | CString::new(value).unwrap().into_raw() as *const u8, 452 | x11::xlib::XA_STRING, 453 | existing_display, 454 | ); 455 | } 456 | 457 | fn create_atom( 458 | name: &str, 459 | data_ptr: *const u8, 460 | ptype: u64, 461 | existing_display: Option<*mut Display>, 462 | ) { 463 | unsafe { 464 | let display = existing_display.unwrap_or(XOpenDisplay(std::ptr::null())); 465 | let name_cstr = CString::new(name).unwrap(); 466 | 467 | let atom = x11::xlib::XInternAtom(display, name_cstr.as_ptr(), x11::xlib::False); 468 | 469 | let screen = x11::xlib::XDefaultScreen(display); 470 | let root = x11::xlib::XRootWindow(display, screen); 471 | 472 | x11::xlib::XChangeProperty( 473 | display, 474 | root, 475 | atom, 476 | ptype, 477 | 32, 478 | x11::xlib::PropModeReplace, 479 | data_ptr, 480 | 1, 481 | ); 482 | 483 | x11::xlib::XFlush(display); 484 | if existing_display.is_none() { 485 | x11::xlib::XCloseDisplay(display); 486 | } 487 | } 488 | } 489 | 490 | fn delete_atom(name: &str, existing_display: Option<*mut Display>) { 491 | unsafe { 492 | let display = existing_display.unwrap_or(XOpenDisplay(std::ptr::null())); 493 | let name_cstr = CString::new(name).unwrap(); 494 | 495 | let atom = x11::xlib::XInternAtom(display, name_cstr.as_ptr(), x11::xlib::False); 496 | 497 | let screen = x11::xlib::XDefaultScreen(display); 498 | let root = x11::xlib::XRootWindow(display, screen); 499 | 500 | x11::xlib::XDeleteProperty(display, root, atom); 501 | 502 | if existing_display.is_none() { 503 | x11::xlib::XCloseDisplay(display); 504 | } 505 | } 506 | } 507 | 508 | fn create_options(window_id: &str) -> Arc { 509 | Arc::new(Options { 510 | background_color: "#000000".to_string(), 511 | default_delay: 100, 512 | path_to_gif: "foo.gif".to_string(), 513 | scaling: Scaling::FILL, 514 | scaling_filter: ScalingFilter::AUTO, 515 | verbose: false, 516 | window_id: window_id.to_string(), 517 | }) 518 | } 519 | 520 | fn create_option_with_color(color: &str) -> Arc { 521 | Arc::new(Options { 522 | background_color: color.to_string(), 523 | default_delay: 100, 524 | path_to_gif: "foo.gif".to_string(), 525 | scaling: Scaling::FILL, 526 | scaling_filter: ScalingFilter::AUTO, 527 | verbose: false, 528 | window_id: "".to_string(), 529 | }) 530 | } 531 | } 532 | -------------------------------------------------------------------------------- /tests/samples/run-samples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Run from tests-directory 4 | 5 | function run_debug { 6 | set -x 7 | ../../target/release/xgifwallpaper $VERBOSE -b $1 -s $2 $3 & 8 | { set +x; } 2> /dev/null 9 | 10 | last_pid=$! 11 | sleep $SLEEP_TIME 12 | kill -INT $last_pid 13 | } 14 | 15 | BACKGROUND="white" 16 | FILE="sample-1x1.gif" 17 | POSITION="FILL" 18 | SLEEP_TIME="3s" 19 | VERBOSE="" 20 | 21 | cargo build --release 22 | 23 | run_debug $BACKGROUND $POSITION "sample-1x1.gif" 24 | run_debug $BACKGROUND $POSITION "sample-2x1.gif" 25 | run_debug $BACKGROUND $POSITION "sample-1x2.gif" 26 | run_debug $BACKGROUND $POSITION "sample-1x1-one-frame.gif" 27 | 28 | POSITION="MAX" 29 | run_debug $BACKGROUND $POSITION "sample-1x1.gif" 30 | run_debug $BACKGROUND $POSITION "sample-2x1.gif" 31 | run_debug $BACKGROUND $POSITION "sample-1x2.gif" 32 | run_debug $BACKGROUND $POSITION "sample-1x1-one-frame.gif" 33 | -------------------------------------------------------------------------------- /tests/samples/sample-1x1-one-frame.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calculon102/xgifwallpaper/226ff958b19c90b64a0b454fcaf5588c8a0ee0fb/tests/samples/sample-1x1-one-frame.gif -------------------------------------------------------------------------------- /tests/samples/sample-1x1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calculon102/xgifwallpaper/226ff958b19c90b64a0b454fcaf5588c8a0ee0fb/tests/samples/sample-1x1.gif -------------------------------------------------------------------------------- /tests/samples/sample-1x2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calculon102/xgifwallpaper/226ff958b19c90b64a0b454fcaf5588c8a0ee0fb/tests/samples/sample-1x2.gif -------------------------------------------------------------------------------- /tests/samples/sample-2x1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calculon102/xgifwallpaper/226ff958b19c90b64a0b454fcaf5588c8a0ee0fb/tests/samples/sample-2x1.gif --------------------------------------------------------------------------------