diff --git a/.github/blur-0.png b/.github/blur-0.png new file mode 100644 index 0000000..3e8a6ec Binary files /dev/null and b/.github/blur-0.png differ diff --git a/.github/blur-10.png b/.github/blur-10.png new file mode 100644 index 0000000..ae4627f Binary files /dev/null and b/.github/blur-10.png differ diff --git a/.github/blur-25.png b/.github/blur-25.png new file mode 100644 index 0000000..d624eb6 Binary files /dev/null and b/.github/blur-25.png differ diff --git a/.github/invert.png b/.github/invert.png deleted file mode 100644 index dd48d4a..0000000 Binary files a/.github/invert.png and /dev/null differ diff --git a/.github/no-invert.png b/.github/no-invert.png deleted file mode 100644 index d35c598..0000000 Binary files a/.github/no-invert.png and /dev/null differ diff --git a/Cargo.lock b/Cargo.lock index be398fa..b9c4ce2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,14 +1,22 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "adler32" -version = "1.0.3" +name = "ansi_term" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "autocfg" -version = "0.1.4" +name = "atty" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "bitflags" @@ -16,8 +24,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "byteorder" -version = "1.3.1" +name = "cc" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -30,116 +38,132 @@ name = "clap" version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "deflate" -version = "0.7.19" +name = "heck" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "i3lockr" -version = "0.1.2" +version = "1.0.0" dependencies = [ - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "image 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "xcb 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "xcb-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "image" -version = "0.21.1" +name = "libc" +version = "0.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "png 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "inflate" -version = "0.4.5" +name = "num_cpus" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "libc" -version = "0.2.55" +name = "numtoa" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "log" -version = "0.4.6" +name = "proc-macro2" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "lzw" -version = "0.10.0" +name = "quote" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "num-integer" -version = "0.1.41" +name = "redox_syscall" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "num-iter" -version = "0.1.39" +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "structopt" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "num-rational" -version = "0.2.1" +name = "structopt-derive" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "num-traits" -version = "0.2.8" +name = "syn" +version = "0.15.34" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "png" -version = "0.14.1" +name = "termion" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "deflate 0.7.19 (registry+https://github.com/rust-lang/crates.io-index)", - "inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -150,54 +174,75 @@ dependencies = [ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unicode-segmentation" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-width" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "xcb" -version = "0.8.2" +name = "unicode-xid" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "xcb-util" -version = "0.2.0" +name = "winapi" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", - "xcb 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "yaml-rust" -version = "0.3.5" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "xcb" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] -"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" -"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" +"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum deflate 0.7.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8a6abb26e16e8d419b5c78662aa9f82857c2386a073da266840e474d5055ec86" -"checksum image 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)" = "293e54ce142a936a39da748ba8178ae6aa1914b82d846a4278f11590c89bf116" -"checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "42914d39aad277d9e176efbdad68acb1d5443ab65afe0e0e4f0d49352a950880" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" -"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" -"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" -"checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e" -"checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" -"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" -"checksum png 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63daf481fdd0defa2d1d2be15c674fbfa1b0fd71882c303a91f9a79b3252c359" +"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" +"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" +"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3d0760c312538987d363c36c42339b55f5ee176ea8808bbe4543d484a291c8d1" +"checksum structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "528aeb7351d042e6ffbc2a6fb76a86f9b622fdf7c25932798e7a82cb03bc94c6" +"checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" +"checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum xcb 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de" -"checksum xcb-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b6ee166167b2d8dbc758fb8fe06757c02e54517ee668831427253bc41e44c83" -"checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" diff --git a/Cargo.toml b/Cargo.toml index fee93ac..4da004a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,49 +1,35 @@ [package] name = "i3lockr" -version = "0.1.2" +description = "Distort a screenshot and run i3lock" +version = "1.0.0" license = "MIT OR Apache-2.0" -edition = "2018" -repository = "https://github.com/owenthewizard/i3lockr" -keywords = ["i3", "i3lock", "i3lockr", "i3lock-color", "lock"] authors = ["Owen Walpole "] -description = "Distort a screenshot and run i3lock" -publish = false +repository = "https://github.com/owenthewizard/i3lockr" +readme = "README.md" +keywords = ["i3lockr", "i3lock", "i3lock-fancy", "blur", "i3lock-color" ] +categories = ["command-line-utilities", "multimedia::images"] +edition = "2018" [dependencies] -#font-loader = "0.7.0" -libc = "0.2.46" - -[dependencies.clap] -version = "2.32.0" -default-features = false -features = ["yaml"] - -#[dependencies.structopt] -#version = "0.2.14" -#default-features = false +libc = "0.2" # should be same as xcb +num_cpus = "1" -[dependencies.image] -version = "0.21.0" +[dependencies.structopt] +version = "0.2" default-features = false -features = ["png_codec"] [dependencies.xcb] -version = "0.8.2" -default-features = false -features = ["randr"] +version = "0.8" +features = ["randr", "shm"] -[dependencies.xcb-util] -version = "0.2.0" -default-features = false -features = ["image"] +[build-dependencies] +cc = "1" -[profile.release] -codegen-units = 1 -lto = "thin" - -[profile.bench] -codegen-units = 1 -lto = "thin" +[features] +suggestions = ["structopt/suggestions"] +color = ["structopt/color"] +default = ["suggestions", "color"] -[profile.dev] -opt-level = 2 +[profile.release] +lto = true +codegen-units=1 diff --git a/README.md b/README.md index 598c653..09671da 100644 --- a/README.md +++ b/README.md @@ -2,31 +2,34 @@ Distort a screenshot and run `i3lock`. -## Quick Start [[Documentation]](USAGE.md) +## Quick start [[Documentation]](USAGE.md) -Signed binary releases are availible [here](https://github.com/owenthewizard/i3lockr/releases). +Signed binary releases are availible on the [releases page](https://github.com/owenthewizard/i3lockr/releases). +Or build it yourself: ```bash git clone --depth=1 git://github.com/owenthewizard/i3lockr.git && cd i3lockr -cargo build --release -sudo strip -s target/release/i3lockr /usr/local/bin/i3lockr -i3lockr --invert -- --nofork --noempty # or your favorite args +cargo build --release # you may adjust features here +sudo strip -s target/release/i3lockr -o /usr/local/bin/i3lockr +i3lockr --blur 25 -- --nofork --ignore-empty-password # use your favorite args ``` ## Screenshots -Without `--invert` -![screenshot without --invert](.github/no-invert.png) +Without `--blur` +![screenshot without blur](.github/blur-0) -With `--invert` -![screenshot with --invert](.github/invert.png) +With `--blur=10` +![screenshot with blur 10](.github/blur-10) -With the default options on a 1080p monitor, `i3lockr` takes less than half a second to run! +With `--blur=25` +![screenshot with blur 25](.github/blur-25) + +`i3lockr` (since v1.0.0) is incredibly fast at all blur levels, try timing it yourself with `time`. ## Important Notes -The exit status of `i3lockr` is not reliable! -That means that `i3lockr && systemctl suspend` may not lock the screen if there was an error. +`i3lockr` always exits with `EXIT_SUCCESS`. This means that commands such as `i3lockr && systemctl suspend` may not lock the screen if there was an error. ### Coding Style @@ -34,7 +37,9 @@ Obey `rustfmt` and Rust 2018 conventions. ## Contributing -Pull requests are always welcome. See [TODO](TODO.md). +Pull requests are always welcome. + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed under the terms of both the MIT License and the Apache License (Version 2.0). ## Versioning @@ -42,7 +47,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm Changes are documented in the [Changelog](CHANGELOG.md). -See the [tags on this repository](https://github.com/owenthewizard/i3lockr/tags) for available releases. +See the [tags](https://github.com/owenthewizard/i3lockr/tags) for available releases. ## Authors @@ -50,11 +55,10 @@ See [the list of contributors](https://github.com/owenthewizard/i3lockr/contribu ## License -`i3lockr` is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0). - See [LICENSE-APACHE](LICENSE-APACHE.md) and [LICENSE-MIT](LICENSE-MIT.md) for details. ## Acknowledgments -* [i3lock-fancy](https://github.com/meskarune/i3lock-fancy) by Dolores Portalatin for inspiration. -* [Padlock Icon](padlock.svg) made by [Chanut](https://www.flaticon.com/authors/chanut) from [Flaticon](https://www.flaticon.com) is licensed by [CC 3.0 BY](https://creativecommons.org/licenses/by/3.0/). +* [i3lock](https://github.com/i3/i3lock) by [Michael Stapelberg](https://github.com/stapelberg) and [contributers](https://github.com/i3/i3lock/graphs/contributors) +* [i3lock-fancy](https://github.com/meskarune/i3lock-fancy) by [Dolores Portalatin](https://github.com/meskarune) for inspiration. +* [Martin Dørum](https://github.com/mortie) for contributions to `i3lock` that made this possible. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index ad3f1fd..0000000 --- a/TODO.md +++ /dev/null @@ -1,5 +0,0 @@ -- [ ] Draw text -- [ ] Vector lock icon -- [ ] Text position -- [ ] Shell completions -- [ ] manpages diff --git a/USAGE.md b/USAGE.md index 7e6c7f1..19008a2 100644 --- a/USAGE.md +++ b/USAGE.md @@ -3,7 +3,7 @@ See the help: ``` -i3lockr v0.1.0 (unknown-branch@unknown-commit) +i3lockr 1.0.0 Owen Walpole Distort a screenshot and run i3lock @@ -11,22 +11,22 @@ USAGE: i3lockr [FLAGS] [OPTIONS] [i3lock]... FLAGS: - -h, --help Prints help information - --invert Draw the lock image as an invert mask on the background, i.e. invert every pixel on the background - where the same pixel on the lock image has >50% alpha - -V, --version Prints version information + -v, --verbose Print how long each step takes, among other things. Always enabled in debug builds. + -h, --help Prints help information + --invert Interpret the icon as a mask, inverting masked pixels [NYI] on the screenshot. Try it to see an + example. + --one-icon Only place one icon. Default is to place an icon on each monitor. [NYI] + -V, --version Prints version information OPTIONS: - -d, --dark Darkens image by an amount [default: -36] - -g, --gravity Text position [default: south] [possible values: north, east, south, west] - -i, --iter Number of blur iterations [default: 1] - -l, --lock Path to lock image - -f, --scale Scale factor for faux-blur. Divisor of 1, so 5 == 20% [default: 2] - -s, --strength Blur strength [default: 3] - -t, --text Text to draw on the screen [UNINPLEMENTED] [default: ] + -i, --icon Path to icon to overlay on screenshot. [NYI] + -u, --position Icon placement, "center" to center, [NYI] "x, y" (from top-left), or "-x,-y" (from + bottom-right). Has no effect without --icon. Example: "(945, -20)" [default: + Center] + -b, --blur Blur strength. Example: 10 ARGS: - ... Args to pass to i3lock + ... Arguments to pass to i3lock. '--' must be used. Example: "-- --nofork --ignore-empty-password" ``` -[default.png](default.png) is embedded into the executable at build-time as the default lock image. +Items marked `[NYI]` are `Not Yet Implemented` and may function partially or not at all. diff --git a/build.rs b/build.rs index 3cc426b..c11f9c7 100644 --- a/build.rs +++ b/build.rs @@ -1,23 +1,52 @@ +use std::env; +use std::path::Path; use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; fn main() { - println!("cargo:rustc-env=GIT_BRANCH=unknown-branch"); - println!("cargo:rustc-env=GIT_HASH=unknown-commit"); - if let Ok(output) = Command::new("git") - .args(&["rev-parse", "--abbrev-ref", "HEAD"]) + // Build C code for stackblur and statically link + let c_src = Path::new("src").join("C"); + cc::Build::new() + .file(c_src.join("stackblur.c")) + .include(c_src) + .compile("stackblur"); + // + + // Export build target, build time, and git commit + println!( + "cargo:rustc-env=TARGET={}", + env::var("TARGET").unwrap_or("Unknown Target".to_owned()) + ); + + let mut git_branch = "Unknown Branch".to_owned(); + let mut git_commit = "Unknown Commit".to_owned(); + + if let Ok(out) = Command::new("git") + .args(vec!["rev-parse", "--abbrev-ref", "HEAD"]) .output() { - if output.status.success() { - let git_branch = String::from_utf8(output.stdout).unwrap(); - println!("cargo:rustc-env=GIT_BRANCH={}", git_branch); + if !out.stdout.is_empty() { + git_branch = String::from_utf8(out.stdout).unwrap_or("Unknown Branch".to_owned()); } } - if let Ok(output) = Command::new("git").args(&["rev-parse", "HEAD"]).output() { - if output.status.success() { - let git_hash = String::from_utf8(output.stdout).unwrap(); - println!("cargo:rustc-env=GIT_HASH={}", git_hash.get(0..7).unwrap()); + + if let Ok(out) = Command::new("git") + .args(vec!["rev-parse", "--short", "HEAD"]) + .output() + { + if !out.stdout.is_empty() { + git_commit = String::from_utf8(out.stdout).unwrap_or("Unknown Commit".to_owned()); } } - // TODO: clap gen shell completion & manpages + println!("cargo:rustc-env=GIT_BRANCH={}", git_branch); + println!("cargo:rustc-env=GIT_COMMIT={}", git_commit); + println!( + "cargo:rustc-env=TIME={}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + ); + // } diff --git a/default.png b/default.png deleted file mode 100644 index 5078351..0000000 Binary files a/default.png and /dev/null differ diff --git a/src/C/stackblur.c b/src/C/stackblur.c new file mode 100644 index 0000000..2bb05f6 --- /dev/null +++ b/src/C/stackblur.c @@ -0,0 +1,258 @@ +#include "stackblur.h" +#include + +void *HStackRenderingThread(void *arg) { + StackBlurRenderingParams *rp=(StackBlurRenderingParams*)arg; + int rinsum,ginsum,binsum,routsum,goutsum,boutsum,rsum,gsum,bsum,x,y,i,yi,yw,rbs,p,sp; + int div=rp->radius+rp->radius+1; + int *stackr=malloc(div*sizeof(int)); + int *stackg=malloc(div*sizeof(int)); + int *stackb=malloc(div*sizeof(int)); + yw=yi=rp->y*rp->w; + int r1=rp->radius+1; + for (y=rp->y;yy2;y++){ + rinsum=ginsum=binsum=routsum=goutsum=boutsum=rsum=gsum=bsum=0; + for(i=-rp->radius;i<=rp->radius;i++){ + p=(yi+MIN(rp->wm,MAX(i,0)))*4; + sp=i+rp->radius; + stackr[sp]=rp->pix[p]; + stackg[sp]=rp->pix[p+1]; + stackb[sp]=rp->pix[p+2]; + rbs=r1-abs(i); + rsum+=stackr[sp]*rbs; + gsum+=stackg[sp]*rbs; + bsum+=stackb[sp]*rbs; + if (i>0){ + rinsum+=stackr[sp]; + ginsum+=stackg[sp]; + binsum+=stackb[sp]; + } else { + routsum+=stackr[sp]; + goutsum+=stackg[sp]; + boutsum+=stackb[sp]; + } + } + int stackpointer; + int stackstart; + stackpointer=rp->radius; + + for (x=rp->x;xw;x++){ + rp->r[yi]=rp->dv[rsum]; + rp->g[yi]=rp->dv[gsum]; + rp->b[yi]=rp->dv[bsum]; + + rsum-=routsum; + gsum-=goutsum; + bsum-=boutsum; + + stackstart=stackpointer-rp->radius+div; + sp=stackstart%div; + + routsum-=stackr[sp]; + goutsum-=stackg[sp]; + boutsum-=stackb[sp]; + + p=(yw+rp->vminx[x])*4; + stackr[sp]=rp->pix[p]; + stackg[sp]=rp->pix[p+1]; + stackb[sp]=rp->pix[p+2]; + + rinsum+=stackr[sp]; + ginsum+=stackg[sp]; + binsum+=stackb[sp]; + + rsum+=rinsum; + gsum+=ginsum; + bsum+=binsum; + + stackpointer=(stackpointer+1)%div; + sp=stackpointer%div; + + routsum+=stackr[sp]; + goutsum+=stackg[sp]; + boutsum+=stackb[sp]; + + rinsum-=stackr[sp]; + ginsum-=stackg[sp]; + binsum-=stackb[sp]; + + yi++; + } + yw+=rp->w; + } + free(stackr); + free(stackg); + free(stackb); + stackr=stackg=stackb=NULL; + pthread_exit(NULL); +} + +void *VStackRenderingThread(void *arg) { + StackBlurRenderingParams *rp=(StackBlurRenderingParams*)arg; + int rinsum,ginsum,binsum,routsum,goutsum,boutsum,rsum,gsum,bsum,x,y,i,yi,yp,rbs,p,sp; + int div=rp->radius+rp->radius+1; + int divsum=(div+1)>>1; + divsum*=divsum; + int *stackr=malloc(div*sizeof(int)); + int *stackg=malloc(div*sizeof(int)); + int *stackb=malloc(div*sizeof(int)); + int r1=rp->radius+1; + int hm=rp->H-rp->y-1; + for (x=rp->x;xw;x++) { + rinsum=ginsum=binsum=routsum=goutsum=boutsum=rsum=gsum=bsum=0; + yp=(rp->y-rp->radius)*rp->w; + for(i=-rp->radius;i<=rp->radius;i++) { + yi=MAX(0,yp)+x; + sp=i+rp->radius; + + stackr[sp]=rp->r[yi]; + stackg[sp]=rp->g[yi]; + stackb[sp]=rp->b[yi]; + + rbs=r1-abs(i); + + rsum+=rp->r[yi]*rbs; + gsum+=rp->g[yi]*rbs; + bsum+=rp->b[yi]*rbs; + + if (i>0){ + rinsum+=stackr[sp]; + ginsum+=stackg[sp]; + binsum+=stackb[sp]; + } else { + routsum+=stackr[sp]; + goutsum+=stackg[sp]; + boutsum+=stackb[sp]; + } + + if(iw; + } + } + yi=rp->y*rp->w+x; + int stackpointer; + int stackstart; + stackpointer=rp->radius; + + for (y=rp->y;yy2;y++) { + p=yi*4; + rp->pix[p]=(unsigned char)(rp->dv[rsum]); + rp->pix[p+1]=(unsigned char)(rp->dv[gsum]); + rp->pix[p+2]=(unsigned char)(rp->dv[bsum]); + rp->pix[p+3]=0xff; + + rsum-=routsum; + gsum-=goutsum; + bsum-=boutsum; + + stackstart=stackpointer-rp->radius+div; + sp=stackstart%div; + + routsum-=stackr[sp]; + goutsum-=stackg[sp]; + boutsum-=stackb[sp]; + + p=x+rp->vminy[y]; + stackr[sp]=rp->r[p]; + stackg[sp]=rp->g[p]; + stackb[sp]=rp->b[p]; + + rinsum+=stackr[sp]; + ginsum+=stackg[sp]; + binsum+=stackb[sp]; + + rsum+=rinsum; + gsum+=ginsum; + bsum+=binsum; + + stackpointer=(stackpointer+1)%div; + + routsum+=stackr[stackpointer]; + goutsum+=stackg[stackpointer]; + boutsum+=stackb[stackpointer]; + + rinsum-=stackr[stackpointer]; + ginsum-=stackg[stackpointer]; + binsum-=stackb[stackpointer]; + + yi+=rp->w; + } + } + free(stackr); + free(stackg); + free(stackb); + stackr=stackg=stackb=NULL; + pthread_exit(NULL); +} + +void stackblur(unsigned char *pix,int x, int y,int w,int h,int radius, int num_threads) { + if (radius<1) + return; + int wh=w*h; + int *r=malloc(wh*sizeof(int)); + int *g=malloc(wh*sizeof(int)); + int *b=malloc(wh*sizeof(int)); + int i; + + int div=radius+radius+1; + int divsum=(div+1)>>1; + divsum*=divsum; + int *dv=malloc(256*divsum*sizeof(int)); + for (i=0;i<256*divsum;i++) { + dv[i]=(i/divsum); + } + int *vminx=malloc(w*sizeof(int)); + for (i=0;i + +// One of my first steps with Processing. I am a fan +// of blurring. Especially as you can use blurred images +// as a base for other effects. So this is something I +// might get back to in later experiments. +// +// What you see is an attempt to implement a Gaussian Blur algorithm +// which is exact but fast. I think that this one should be +// relatively fast because it uses a special trick by first +// making a horizontal blur on the original image and afterwards +// making a vertical blur on the pre-processed image-> This +// is a mathematical correct thing to do and reduces the +// calculation a lot. +// +// In order to avoid the overhead of function calls I unrolled +// the whole convolution routine in one method. This may not +// look nice, but brings a huge performance boost. +// +// +// v1.1: I replaced some multiplications by additions +// and added aome minor pre-caclulations. +// Also add correct rounding for float->int conversion +// +// v1.2: I completely got rid of all floating point calculations +// and speeded up the whole process by using a +// precalculated multiplication table. Unfortunately +// a precalculated division table was becoming too +// huge. But maybe there is some way to even speed +// up the divisions. +// +// v1.3: Fixed a bug that caused blurs that start at y>0 +// to go wrong. Thanks to Jeroen Schellekens for +// finding it! + +typedef struct { + unsigned char *pix; + int x; + int y; + int w; + int y2; + int H; + int wm; + int wh; + int *r; + int *g; + int *b; + int *dv; + int radius; + int *vminx; + int *vminy; +} StackBlurRenderingParams; + +#include + +#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) +#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y)) + +void *HStackRenderingThread(void *arg); + +void *VStackRenderingThread(void *arg); + +void stackblur(unsigned char *image,int x, int y,int w,int h,int radius, int num_threads); + diff --git a/src/cli.yaml b/src/cli.yaml deleted file mode 100644 index 2342857..0000000 --- a/src/cli.yaml +++ /dev/null @@ -1,58 +0,0 @@ -name: "this text gets replaced but it has to be here for the arguments to parse correctly" - -settings: - - TrailingVarArg - -args: - - text: - help: "Text to draw on the screen [UNINPLEMENTED]" - short: "t" - long: "text" - default_value: "" - - - gravity: - help: "Text position" - short: "g" - long: "gravity" - default_value: "south" - possible_values: [ "north", "east", "south", "west" ] - - - iter: - help: "Number of blur iterations" - short: "i" - long: "iter" - default_value: "1" - - - strength: - help: "Blur strength" - short: "s" - long: "strength" - default_value: "3" - - - dark: - help: "Darkens image by an amount" - short: "d" - long: "dark" - default_value: "-36" - allow_hyphen_values: true - - - scale: - help: "Scale factor for faux-blur. Divisor of 1, so 5 == 20%" - short: "f" - long: "scale" - default_value: "2" - - - invert: - help: "Draw the lock image as an invert mask on the background, i.e. invert every pixel on the background where the same pixel on the lock image has >50% alpha" - long: "--invert" - - - lock: - help: "Path to lock image" - short: "l" - long: "lock" - takes_value: true - - - i3lock: - help: "Args to pass to i3lock" - index: 1 - multiple: true diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..b36d2b0 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,109 @@ +use std::ffi::OsString; +use std::num::ParseIntError; +use std::path::PathBuf; +use std::str::FromStr; + +use structopt::StructOpt; + +mod validators; + +/// Distort a screenshot and run i3lock +// Needs to be fixed upstream in StructOpt +// TODO: checked if my PR is merged +#[derive(StructOpt, Debug)] +pub struct Cli { + /// Prints version information + #[structopt(short = "V", long = "version")] + pub version: bool, + + /// Print how long each step takes, among other things. + /// Always enabled in debug builds. + #[structopt(short = "v", long = "verbose")] + pub debug: bool, + + /// Blur strength. Example: 10 + #[structopt( + short = "b", + long = "blur", + raw(validator = "validators::greater_than(0)") + )] + pub radius: Option, + + /// Only place one icon. Default is to place an icon on each monitor. [NYI] + #[structopt(long = "one-icon")] + pub one_icon: bool, + + /// Interpret the icon as a mask, inverting masked pixels [NYI] + /// on the screenshot. Try it to see an example. + #[structopt(long = "invert")] + pub invert: bool, + + /// Icon placement, "center" to center, [NYI] + /// "x, y" (from top-left), or "-x,-y" (from bottom-right). + /// Has no effect without --icon. + /// Example: "(945, -20)" + #[structopt( + short = "u", + long = "position", + allow_hyphen_values = true, + value_name = "coords|center", + default_value = "Center" + )] + pub pos: Position, + + /// Path to icon to overlay on screenshot. [NYI] + #[structopt( + short = "i", + long = "icon", + value_name = "file.png", + parse(from_os_str), + raw(validator_os = "validators::is_png") + )] + pub path: Option, + + /// Arguments to pass to i3lock. '--' must be used. Example: "-- --nofork + /// --ignore-empty-password" + #[structopt( + value_name = "i3lock", + takes_value = true, + multiple = true, + parse(from_os_str) + )] + pub i3lock: Vec, +} + +#[derive(Debug)] +pub enum Position { + Center, + Coords(isize, isize), +} + +impl FromStr for Position { + type Err = ParseIntError; + + // if you can improve this submit a PR + fn from_str(s: &str) -> Result { + if s.eq_ignore_ascii_case("center") { + return Ok(Position::Center); + } + let mut coords = s + .trim_matches(|p| p == '(' || p == ')') + .split(',') + .map(str::trim); + + // TODO replace expect with IntErrorKind::Empty once it's stable + let x = coords + .next() + .expect("--position takes exactly two integers") + .parse::()?; + let y = coords + .next() + .expect("--position takes exactly two integers") + .parse::()?; + assert!( + coords.next().is_none(), + "--position takes exactly two integers" + ); + Ok(Position::Coords(x, y)) + } +} diff --git a/src/cli/validators.rs b/src/cli/validators.rs new file mode 100644 index 0000000..b2787b1 --- /dev/null +++ b/src/cli/validators.rs @@ -0,0 +1,66 @@ +use std::ffi::{OsStr, OsString}; +use std::fmt::Display; +use std::fs; +use std::str::FromStr; + +/// Returns a closure that checks if the argument is greater than ```n```. +pub fn greater_than(n: T) -> impl Fn(String) -> Result<(), String> +where + T: Display + FromStr + PartialOrd, + T::Err: ToString, +{ + move |s: String| { + let num = match s.parse::() { + Ok(val) => val, + Err(e) => return Err(e.to_string()), + }; + if num > n { + Ok(()) + } else { + Err(format!("must be greater than {}", n)) + } + } +} + +/// Checks that ```s``` is the path to a valid PNG file. +/// Mostly unimplemented +// sadly can't be AsRef because of what structopt expects +pub fn is_png(s: &OsStr) -> Result<(), OsString> { + eprintln!("Warning: PNG check bypassed"); + let metadata = match fs::metadata(s) { + Ok(meta) => meta, + Err(e) => return Err(e.to_string().into()), + }; + if metadata.is_file() { + Ok(()) + } else { + Err("must be a PNG file".into()) + } + //TODO add PNG header check or whatever +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn greater_than_validator() { + let four = "4".to_string(); + let five = "5".to_string(); + let six = "6".to_string(); + + let greater_than_five = greater_than(5); + + assert!(greater_than_five(four).is_err()); + assert!(greater_than_five(five).is_err()); + assert!(greater_than_five(six).is_ok()); + } + + #[test] + fn is_png_validator() { + assert!(is_png(OsStr::new("/some/nonexist/file")).is_err()); + assert!(is_png(OsStr::new("/")).is_err()); + assert!(is_png(OsStr::new(".github/readme.png")).is_ok()); + unimplemented!(); + } +} diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..afadbb6 --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,12 @@ +use libc::c_int; + +extern "C" { + fn stackblur(buffer: *mut u8, x: c_int, y: c_int, w: c_int, h: c_int, r: c_int, n: c_int); +} + +pub fn blur(buffer: &mut [u8], w: c_int, h: c_int, r: c_int) { + assert!(r > 0); + unsafe { + stackblur(buffer.as_mut_ptr(), 0, 0, w, h, r, num_cpus::get() as i32); + } +} diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..c5fd6f8 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,31 @@ +pub static mut DEBUG: bool = false; + +#[macro_export] +macro_rules! debug { + ($($arg:tt)*) => { + if cfg!(debug_assertions) || unsafe {DEBUG} { + eprintln!("{f}:{l}:{c} {fmt}", f=file!(), l=line!(), c=column!(), fmt=format!($($arg)*)); + } + } +} + +#[macro_export] +macro_rules! timer_start { + ($timer:ident) => { + let $timer = Instant::now(); + }; +} + +#[macro_export] +macro_rules! timer_time { + ($s:expr, $timer:ident) => { + debug!("{} took {:#?}", $s, $timer.elapsed()); + }; +} + +#[macro_export] +macro_rules! color_panic { + ($($arg:tt)*) => { + panic!("{}", Format::Error(format!($($arg)*))); + } +} diff --git a/src/main.rs b/src/main.rs index d172de8..4693bf8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,259 +1,90 @@ -use std::cmp::min; +use std::ffi::OsStr; use std::io::Write; +use std::panic; use std::process::{Command, Stdio}; use std::time::Instant; -use clap::{crate_authors, crate_description, crate_name, crate_version, load_yaml, value_t, App}; +use structopt::clap::Format; +use structopt::StructOpt; -use image::DynamicImage::ImageRgba8; -use image::{imageops, DynamicImage, FilterType, GenericImageView, Pixel, Rgba, RgbaImage}; - -use xcb::{randr, Connection}; - -use xcb_util::image as xcb_img; - -const LOCK_SCALE: u32 = 4; -const DEFAULT_LOCK: &[u8] = include_bytes!("../default.png"); - -// not sure if i should macro this or not -macro_rules! time_it { - ($e:expr, $s:stmt) => { - let now = Instant::now(); - $s; - debug!("{} took {:#?}", $e, now.elapsed()); - }; -} - -macro_rules! debug { - ($($arg:tt)*) => { - if cfg!(debug_assertions) { - eprintln!("{f}:{l}:{c} {fmt}", f=file!(), l=line!(), c=column!(), fmt=format!($($arg)*)); - } - } -} +mod cli; +use cli::Cli; +mod ffi; +mod screenshot; +use screenshot::Screenshot; +mod macros; +use macros::*; fn main() { - // parse args, handle -h|-V - // TODO validate args - // validating args from yaml is impossible (afaik) - // temporary hack: call value_t! early - let yml = load_yaml!("cli.yaml"); - let vers = format!( - "v{} ({}@{})", - crate_version!(), - env!("GIT_BRANCH"), - env!("GIT_HASH") - ); - - let args = App::from_yaml(yml) - .name(crate_name!()) - .version(&vers[..]) - .author(crate_authors!()) - .about(crate_description!()) - .get_matches(); - - // parse necesary arguments - let scale = value_t!(args, "scale", u32).unwrap_or_else(|e| panic!(e.message)); - let dark = value_t!(args, "dark", i32).unwrap_or_else(|e| panic!(e.message)); - let strength = value_t!(args, "strength", f32).unwrap_or_else(|e| panic!(e.message)); - let iter = value_t!(args, "iter", u8).unwrap_or_else(|e| panic!(e.message)); - - // sanity check: we have a lock icon - let lock = match args.occurrences_of("lock") { - 0 => match image::load_from_memory_with_format(DEFAULT_LOCK, image::ImageFormat::PNG) { - Ok(img) => img, - Err(e) => panic!("Error decoding default lock image: {}", e), - }, - _ => match image::open(args.value_of("lock").unwrap()) { - Ok(img) => img, - Err(e) => panic!("Error opening lock image: {}", e), - }, - }; - - // take the screenshot - let mut shot = ImageRgba8(take_screenshot()); - - // process it - process_screenshot(&mut shot, scale, dark, iter, strength); - - // scale lock image/mask to an appropriate size - let size = min(shot.width(), shot.height()) / LOCK_SCALE; - time_it!("Resizing lock", let lock = lock.resize(size, size, FilterType::Triangle).to_rgba()); - - let mut shot = shot.to_rgba(); - draw_stuff(&mut shot, &lock, args.occurrences_of("invert") > 0); - - let mut nofork = false; - let mut i3lock_args = match args.values_of("i3lock") { - Some(args) => { - let v = args.collect::>(); - if v.contains(&"-n") || v.contains(&"--nofork") { - nofork = true; - } - v - } - None => Vec::with_capacity(2), - }; - let arg = format!("--raw={}x{}:rgbx", shot.width(), shot.height()); - i3lock_args.extend_from_slice(&["-i", "/dev/stdin", &arg]); - /* - i3lock_args.push("-i"); - i3lock_args.push("/dev/stdin"); - */ - debug!("Calling i3lock with arguments: {:?}", i3lock_args); - let mut out = Command::new("i3lock").args(i3lock_args).stdin(Stdio::piped()).spawn().unwrap(); - out.stdin.as_mut().unwrap().write_all(&shot.into_vec()).unwrap(); - - if nofork { - let _ = out.wait(); - } -} - -fn process_screenshot(img: &mut DynamicImage, scale: u32, darkness: i32, blur_i: u8, blur_s: f32) { - // scale it down - if scale > 1 { - time_it!( - "Downscaling", - *img = img.resize_exact( - img.width() / scale, - img.height() / scale, - FilterType::Nearest - ) + let args = Cli::from_args(); + if args.version { + eprintln!( + "{} v{} compiled for '{}' at {} ({}@{})", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + env!("TARGET"), + env!("TIME"), + env!("GIT_BRANCH"), + env!("GIT_COMMIT") ); + return; } - - // darken it - if darkness != 0 { - time_it!("Darkening", *img = img.brighten(darkness)); - } - - // blur it - if blur_s > 0.0 && blur_i > 0 { - for i in 0..blur_i { - time_it!(format!("Blurring pass {}", i + 1), *img = img.blur(blur_s)); - } - } - - // scale it back up - if scale > 1 { - time_it!( - "Upscaling", - *img = img.resize_exact( - img.width() * scale, - img.height() * scale, - FilterType::Triangle - ) + unsafe { DEBUG = args.debug }; + debug!("Found args: {:?}", args); + + timer_start!(screenshot); + let shot = + Screenshot::capture().unwrap_or_else(|e| color_panic!("Failed to take screenshot: {}", e)); + timer_time!("Capturing screenshot", screenshot); + debug!("Found monitors: {:?}", shot.monitors()); + + if args.radius.is_some() { + timer_start!(blur); + ffi::blur( + shot.data, + shot.width() as libc::c_int, + shot.height() as libc::c_int, + args.radius.unwrap_or_else(|| unreachable!()), // should be safe because validators ran already ); + timer_time!("Blurring", blur); } -} - -fn take_screenshot() -> RgbaImage { - let (conn, screen_num) = match Connection::connect(None) { - Ok((c, n)) => (c, n), - Err(e) => panic!("Failed to open X11 display: {}", e), - }; - let setup = conn.get_setup(); - let screen = setup.roots().nth(screen_num as usize).unwrap(); - let root = screen.root(); - let now = Instant::now(); - let ximg = match xcb_img::get( - &conn, - root, - 0, - 0, - screen.width_in_pixels(), - screen.height_in_pixels(), - !0u32, - 0x02u32, - ) { - Ok(img) => img, - Err(()) => panic!("Failed to take screenshot!"), - }; - debug!("Taking the screenshot took {:#?}", now.elapsed()); - - let now = Instant::now(); - // @liftoff (Dan McDougall) - let ret = RgbaImage::from_fn( - screen.width_in_pixels() as u32, - screen.height_in_pixels() as u32, - |x, y| { - let bgrx = ximg.get(x, y); - let b = bgrx & 0x000000FF; - let g = (bgrx & 0x0000FF00) >> 8; - let r = (bgrx & 0x00FF0000) >> 16; - Rgba::from_channels(r as u8, g as u8, b as u8, 255) - }, - ); - debug!("Decoding the XImage took {:#?}", now.elapsed()); - ret -} + //TODO overlay + invert -fn draw_stuff(img: &mut RgbaImage, icon: &RgbaImage, invert: bool) { - let (conn, screen_num) = match Connection::connect(None) { - Ok((c, n)) => (c, n), - Err(e) => panic!("Failed to open X11 display: {}", e), - }; - let setup = conn.get_setup(); - let screen = setup.roots().nth(screen_num as usize).unwrap(); - let root = screen.root(); + //TODO draw text - let cookie = randr::get_screen_resources(&conn, root); - let reply = match cookie.get_reply() { - Ok(r) => r, - Err(e) => panic!("Failed to query RandR screen resources: {}", e), - }; - let crtcs = reply.crtcs(); - let mut crtc_cookies = Vec::with_capacity(crtcs.len()); - let timestamp = reply.timestamp(); - for crtc in crtcs { - crtc_cookies.push(randr::get_crtc_info(&conn, *crtc, timestamp)); + // this is a bit gross + let mut nofork = false; + if args.i3lock.contains(&OsStr::new("-n").to_os_string()) + || args.i3lock.contains(&OsStr::new("--nofork").to_os_string()) + { + nofork = true; } - let now = Instant::now(); - for (i, crtc_cookie) in crtc_cookies.iter().enumerate() { - let reply = match crtc_cookie.get_reply() { - Ok(r) => r, - Err(e) => panic!("Failed to query crtc info for CRTC-{}: {}", i, e), - }; - // only get displays that are powered on/active - if reply.mode() == 0 { - continue; - } + debug!("Calling i3lock with args: {:?}", args.i3lock); + let mut cmd = Command::new("i3lock") + .args(&[ + "-i", + "/dev/stdin", + &format!("--raw={}x{}:bgrx", shot.width(), shot.height()), + ]) + .args(args.i3lock) + .stdin(Stdio::piped()) + .spawn() + .unwrap_or_else(|e| color_panic!("Failed to call i3lock: {}", e)); + + cmd.stdin + .as_mut() + .unwrap_or_else(|| color_panic!("Failed to open i3lock stdin!")) + .write_all(shot.data) + .unwrap_or_else(|e| color_panic!("Failed to write image to i3lock stdin: {}", e)); - let now = Instant::now(); - if invert { - let mask = enlarge_image_canvas(icon, (*img).width(), (*img).height()); - for (mask_pixel, image_pixel) in - mask.enumerate_pixels().zip((*img).enumerate_pixels_mut()) - { - if mask_pixel.2[3] > 127 { - image_pixel.2.invert(); - } - } - } else { - imageops::overlay( - img, - icon, - reply.width() as u32 / 2 - (*icon).width() / 2, - reply.height() as u32 / 2 - (*icon).height() / 2, - ); - } - /* - * DRAW TEXT HERE - */ - debug!("Drawing on CRTC-{} took {:#?}", i, now.elapsed()); + if nofork { + debug!("Asked i3lock not to fork, calling wait()"); + let _ = cmd.wait(); } - debug!("Total drawing time: {:#?}", now.elapsed()); -} -fn enlarge_image_canvas(icon: &RgbaImage, w: u32, h: u32) -> RgbaImage { - let mut bot = RgbaImage::new(w, h); - imageops::overlay( - &mut bot, - icon, - w / 2 - (*icon).width() / 2, - h / 2 - (*icon).height() / 2, - ); - bot + for pixel in shot.data.chunks_exact_mut(4) { + unsafe { pixel.get_unchecked_mut(0..4).reverse() }; + } } diff --git a/src/screenshot.rs b/src/screenshot.rs new file mode 100644 index 0000000..c13f398 --- /dev/null +++ b/src/screenshot.rs @@ -0,0 +1,227 @@ +use std::error::Error; +use std::io::Error as IoError; +use std::{fmt, ptr, slice}; + +use xcb::randr; +use xcb::shm::attach_checked as xcb_attach; +use xcb::shm::detach_checked as xcb_detach; +use xcb::shm::get_image; +use xcb::{ConnError, Connection, Drawable, GenericError}; + +use self::I3LockrError::*; + +macro_rules! handle_reply { + ($e:expr) => { + match $e { + Ok(r) => r, + Err(e) => return Err(XcbGeneric(e)), + } + }; + + ($e:expr, $cleanup:stmt) => { + match $e { + Ok(r) => r, + Err(e) => { + $cleanup; + return Err(XcbGeneric(e)); + } + } + }; +} + +#[derive(Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct Screenshot<'a> { + pub data: &'a mut [u8], + monitors: Vec<(u16, u16)>, + width: u16, + height: u16, + shmid: i32, +} + +impl<'a> Screenshot<'a> { + pub fn capture() -> Result { + let (conn, screen_num) = match Connection::connect(None) { + Ok((c, n)) => (c, n), + Err(e) => return Err(XcbConn(e)), + }; + + // connect to X11 and get some useful vars + let setup = conn.get_setup(); + let screen = match setup.roots().nth(screen_num as usize) { + Some(n) => n, + None => unreachable!(), + }; + + // get handle on monitors + let cookie = randr::get_screen_resources(&conn, screen.root()); + let reply = handle_reply!(cookie.get_reply()); + + // grab their widths and heights + /* + let mut monitors = Vec::with_capacity(reply.crtcs().len()); + for crtc in reply.crtcs() { + let cookie = randr::get_crtc_info(&conn, *crtc, reply.timestamp()); + let reply = handle_reply!(cookie.get_reply()); + + // skip disconnected outputs + if reply.mode() != 0 { + monitors.push((reply.width() as usize, reply.height() as usize)); + } + } + monitors.shrink_to_fit(); + */ + // this silently throws away errors... + let monitors = reply + .crtcs() + .iter() + //.map(|x| randr::get_crtc_info(&conn, *x, reply.timestamp())) + //.filter_map(|x| x.get_reply().ok()) + .filter_map(|x| { + randr::get_crtc_info(&conn, *x, reply.timestamp()) + .get_reply() + .ok() + }) + .filter(|x| x.mode() != 0) + .map(|x| (x.width(), x.height())) + .collect(); + + // real work done here + let (img, shmid) = match Self::real_capture( + &conn, + screen.root(), + screen.width_in_pixels(), + screen.height_in_pixels(), + ) { + Ok((sl, id)) => (sl, id), + Err(e) => return Err(e), + }; + + // BGRA --> RGBA + /* + assert!(img.len() % 4 == 0); + unsafe { + for pixel in img.chunks_exact_mut(4) { + pixel.get_unchecked_mut(0..3).reverse(); + // alpha byte is unreliable, 255 by default but sometimes 0 (e.g. with compton and glx + // backend), so let's set it to always be 255 + *pixel.get_unchecked_mut(3) = 255; + } + } + */ + + Ok(Screenshot { + data: img, + monitors, + width: screen.width_in_pixels(), + height: screen.height_in_pixels(), + shmid, + }) + } + + fn real_capture( + c: &Connection, + d: Drawable, + w: u16, + h: u16, + ) -> Result<(&'a mut [u8], i32), I3LockrError> { + // setup POSIX SHM + let shmid; + unsafe { + shmid = libc::shmget( + libc::IPC_PRIVATE, + w as usize * h as usize * 4, + libc::IPC_CREAT | 0o600, + ); + } + if shmid == -1 { + return Err(ShmIo(IoError::last_os_error())); + } + + // generate XCB Segment + let xid = c.generate_id(); + + // attach X to SHM + let buffer; + let cookie = xcb_attach(c, xid, shmid as u32, false); + handle_reply!(cookie.request_check(), unsafe { + libc::shmctl(shmid, libc::IPC_RMID, ptr::null_mut()); + }); + + // take screenshot + let cookie = get_image( + c, d, 0, 0, w, h, !0, /* XAllPlanes */ + 0x02, /* Z_PIXMAP */ + xid, 0, + ); + handle_reply!(cookie.get_reply(), unsafe { + libc::shmctl(shmid, libc::IPC_RMID, ptr::null_mut()); + xcb_detach(c, xid); + }); + + // detach + let cookie = xcb_detach(c, xid); + handle_reply!(cookie.request_check(), unsafe { + libc::shmctl(shmid, libc::IPC_RMID, ptr::null_mut()); + }); + + // we're done + unsafe { + buffer = libc::shmat(shmid, ptr::null(), 0); + Ok(( + slice::from_raw_parts_mut(buffer as *mut u8, w as usize * h as usize * 4), + shmid, + )) + } + } + + pub const fn width(&self) -> u16 { + self.width + } + + pub const fn height(&self) -> u16 { + self.height + } + + pub const fn monitors(&self) -> &Vec<(u16, u16)> { + &self.monitors + } + + /* + pub fn get_pixel(&self, x: usize, y: usize) -> &[u8] { + assert!(x < self.width); + assert!(y < self.height); + let i = x + self.width * y * 4; + unsafe { + self.data.get_unchecked(i..i + 4) + } + } + */ +} + +impl<'a> Drop for Screenshot<'a> { + fn drop(&mut self) { + unsafe { + libc::shmdt(self.data.as_mut_ptr() as *mut libc::c_void); + libc::shmctl(self.shmid, libc::IPC_RMID, ptr::null_mut()); + } + } +} + +#[derive(Debug)] +pub enum I3LockrError { + XcbGeneric(GenericError), + XcbConn(ConnError), + ShmIo(IoError), +} + +impl Error for I3LockrError {} + +impl fmt::Display for I3LockrError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + XcbConn(e) => write!(f, "XCB {}", e), + XcbGeneric(e) => write!(f, "XCB {}", e), + ShmIo(e) => write!(f, "SHM I/O error: {}", e), + } + } +}