├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── History.md ├── LICENSE ├── Readme.md └── src ├── client.rs ├── main.rs └── trigger.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "arc-swap" 13 | version = "0.4.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "atty" 18 | version = "0.2.13" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "bitflags" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | 30 | [[package]] 31 | name = "caffeinate" 32 | version = "0.1.0" 33 | dependencies = [ 34 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 37 | "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 40 | ] 41 | 42 | [[package]] 43 | name = "cc" 44 | version = "1.0.40" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | 47 | [[package]] 48 | name = "cfg-if" 49 | version = "0.1.9" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | 52 | [[package]] 53 | name = "clap" 54 | version = "2.33.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | dependencies = [ 57 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 64 | ] 65 | 66 | [[package]] 67 | name = "crossbeam-channel" 68 | version = "0.3.9" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | dependencies = [ 71 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 72 | ] 73 | 74 | [[package]] 75 | name = "crossbeam-utils" 76 | version = "0.6.6" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | dependencies = [ 79 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 81 | ] 82 | 83 | [[package]] 84 | name = "ctrlc" 85 | version = "3.1.3" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | dependencies = [ 88 | "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 90 | ] 91 | 92 | [[package]] 93 | name = "itoa" 94 | version = "0.4.6" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | 97 | [[package]] 98 | name = "lazy_static" 99 | version = "1.3.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | 102 | [[package]] 103 | name = "libc" 104 | version = "0.2.60" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | 107 | [[package]] 108 | name = "nix" 109 | version = "0.14.1" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | dependencies = [ 112 | "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 117 | ] 118 | 119 | [[package]] 120 | name = "proc-macro2" 121 | version = "1.0.19" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 125 | ] 126 | 127 | [[package]] 128 | name = "quote" 129 | version = "1.0.7" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | dependencies = [ 132 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 133 | ] 134 | 135 | [[package]] 136 | name = "ryu" 137 | version = "1.0.5" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | 140 | [[package]] 141 | name = "serde" 142 | version = "1.0.114" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | dependencies = [ 145 | "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", 146 | ] 147 | 148 | [[package]] 149 | name = "serde_derive" 150 | version = "1.0.114" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | dependencies = [ 153 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 155 | "syn 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", 156 | ] 157 | 158 | [[package]] 159 | name = "serde_json" 160 | version = "1.0.57" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | dependencies = [ 163 | "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", 166 | ] 167 | 168 | [[package]] 169 | name = "signal-hook" 170 | version = "0.1.10" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | dependencies = [ 173 | "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", 174 | "signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 175 | ] 176 | 177 | [[package]] 178 | name = "signal-hook-registry" 179 | version = "1.1.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | dependencies = [ 182 | "arc-swap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 183 | "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", 184 | ] 185 | 186 | [[package]] 187 | name = "strsim" 188 | version = "0.8.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | 191 | [[package]] 192 | name = "syn" 193 | version = "1.0.38" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | dependencies = [ 196 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 197 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 198 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 199 | ] 200 | 201 | [[package]] 202 | name = "textwrap" 203 | version = "0.11.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | dependencies = [ 206 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 207 | ] 208 | 209 | [[package]] 210 | name = "unicode-width" 211 | version = "0.1.5" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | 214 | [[package]] 215 | name = "unicode-xid" 216 | version = "0.2.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | 219 | [[package]] 220 | name = "vec_map" 221 | version = "0.8.1" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | 224 | [[package]] 225 | name = "void" 226 | version = "1.0.2" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | 229 | [[package]] 230 | name = "winapi" 231 | version = "0.3.7" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | dependencies = [ 234 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 235 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 236 | ] 237 | 238 | [[package]] 239 | name = "winapi-i686-pc-windows-gnu" 240 | version = "0.4.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | 243 | [[package]] 244 | name = "winapi-x86_64-pc-windows-gnu" 245 | version = "0.4.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | 248 | [metadata] 249 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 250 | "checksum arc-swap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e43c468bcaa343ddcad9e46806e066e39f62434898b20f5af21261da910d5c7" 251 | "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 252 | "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" 253 | "checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7" 254 | "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" 255 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 256 | "checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" 257 | "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 258 | "checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f" 259 | "checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 260 | "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 261 | "checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" 262 | "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" 263 | "checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" 264 | "checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 265 | "checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 266 | "checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 267 | "checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" 268 | "checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" 269 | "checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68" 270 | "checksum signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1797d48f38f91643908bb14e35e79928f9f4b3cefb2420a564dde0991b4358dc" 271 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 272 | "checksum syn 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" 273 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 274 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 275 | "checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 276 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 277 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 278 | "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" 279 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 280 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 281 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "caffeinate" 3 | version = "0.1.0" 4 | authors = ["Ryan Schmukler "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | clap = "2.33.0" 11 | crossbeam-channel = "0.3.9" 12 | signal-hook = {version = "0.1.10"} 13 | ctrlc = "3.1.3" 14 | serde = { version = "1.0.114", features = ["derive"] } 15 | serde_json = "1.0.57" 16 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.1.0 / 2019-08-17 3 | ================== 4 | 5 | * initial release 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ryan Schmukler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Caffeinate 2 | 3 | A command-line app bringing caffeinate functionality to [xidlehook](https://github.com/jD91mZM2/xidlehook). 4 | 5 | ## Features 6 | 7 | ### Triggers 8 | Triggers are used to monitor for when caffeinate should exit (and allow 9 | `xidlehook` to resume). 10 | 11 | - [x] Timer 12 | - [x] PID-based monitoring 13 | 14 | ### Quit Actions 15 | Quit actions are used in conjunction with triggers to perform a final action 16 | before caffeinate exits. 17 | 18 | 19 | ## Setup 20 | 21 | 22 | ```sh 23 | cargo install --git https://github.com/rschmukler/caffeinate 24 | ``` 25 | 26 | Start xidlehook with a socket argument: 27 | 28 | ```sh 29 | xidlehook --timer primary 60 "xset dpms force off" --socket "/tmp/xidlehook.sock" 30 | ``` 31 | 32 | 33 | ## Usage Examples 34 | 35 | ```sh 36 | # Running indefinitely (exit with Ctrl-C) 37 | caffeinate 38 | 39 | # Running for an hour 40 | caffeinate --timer 3600 41 | 42 | # Running until a process exits, then shutdown down the machine 43 | caffeinate --pid 1234 --quit=shutdown 44 | ``` 45 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | extern crate serde; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use std::io::{self, Write, LineWriter}; 6 | use std::os::unix::net::UnixStream; 7 | use std::path::Path; 8 | 9 | #[repr(u8)] 10 | pub enum Command { 11 | Disable = 0, 12 | Enable = 1, 13 | #[allow(dead_code)] 14 | TriggerNow = 2, 15 | } 16 | 17 | #[derive(Debug, Deserialize, Serialize)] 18 | #[serde(untagged)] 19 | enum Filter { 20 | All, 21 | } 22 | 23 | impl Default for Filter { 24 | fn default() -> Self { 25 | Self::All 26 | } 27 | } 28 | 29 | #[derive(Debug, Deserialize, Serialize)] 30 | #[serde(rename_all = "camelCase")] 31 | enum Action { 32 | Disable, 33 | Enable, 34 | Trigger, 35 | } 36 | 37 | #[derive(Debug, Deserialize, Serialize)] 38 | struct Control { 39 | #[serde(default)] 40 | pub timer: Filter, 41 | pub action: Action, 42 | } 43 | 44 | #[derive(Debug, Deserialize, Serialize)] 45 | #[serde(tag = "type", rename_all = "camelCase")] 46 | enum Message { 47 | Control(Control), 48 | } 49 | 50 | pub struct XIdleHookClient { 51 | stream: UnixStream, 52 | } 53 | 54 | impl XIdleHookClient { 55 | /// Initializes a new XIdleHookClient for a socket at the given path. 56 | pub fn new>(path: P) -> Result { 57 | let stream = UnixStream::connect(path)?; 58 | 59 | Ok(XIdleHookClient { stream }) 60 | } 61 | 62 | /// Sends the specified command to the XIdleHook socket 63 | pub fn send(&mut self, cmd: Command) -> Result<(), io::Error> { 64 | let packet = Message::Control(Control { 65 | timer: Filter::All, 66 | action: match cmd { 67 | Command::Disable => Action::Disable, 68 | Command::Enable => Action::Enable, 69 | Command::TriggerNow => Action::Trigger, 70 | }, 71 | }); 72 | 73 | let mut writer = LineWriter::new(&self.stream); 74 | 75 | serde_json::to_writer(&mut writer, &packet)?; 76 | writer.write_all(&[b'\n'])?; 77 | writer.flush()?; 78 | 79 | // xidlehook's new socket api sends a response back on successful recv. 80 | // this function could check the response ("Empty") before returning Ok. 81 | 82 | Ok(()) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{value_t_or_exit, App, Arg}; 2 | 3 | mod client; 4 | 5 | use client::{Command, XIdleHookClient}; 6 | use crossbeam_channel::{never, select}; 7 | use std::str::FromStr; 8 | use std::io; 9 | 10 | mod trigger; 11 | 12 | 13 | enum Error { 14 | InvalidArgument(String) 15 | } 16 | 17 | #[derive(PartialEq)] 18 | enum QuitAction { 19 | Nothing, 20 | Suspend, 21 | Shutdown, 22 | Restart 23 | } 24 | 25 | impl QuitAction { 26 | fn perform(self) -> Result<(), io::Error> { 27 | if self == Self::Nothing { 28 | return Ok(()) 29 | } 30 | 31 | let arg = 32 | match self { 33 | Self::Suspend => "suspend", 34 | Self::Restart => "reboot", 35 | Self::Shutdown => "poweroff", 36 | Self::Nothing => unreachable!() 37 | }; 38 | 39 | std::process::Command::new("systemctl") 40 | .arg(arg) 41 | .spawn() 42 | .map(|_| ()) 43 | } 44 | } 45 | 46 | impl FromStr for QuitAction { 47 | type Err = Error; 48 | 49 | fn from_str(s: &str) -> Result { 50 | match s { 51 | "nothing" => Ok(Self::Nothing), 52 | "suspend" => Ok(Self::Suspend), 53 | "shutdown" => Ok(Self::Shutdown), 54 | "restart" => Ok(Self::Restart), 55 | _ => Err(Error::InvalidArgument(s.to_owned())) 56 | } 57 | } 58 | } 59 | 60 | fn main() { 61 | let matches = App::new("caffeinate") 62 | .version("1.0") 63 | .about("Keeping xidlehook woke") 64 | .after_help("If multiple triggers are specified, caffeinate will exit after the first one is fired") 65 | .arg( 66 | Arg::with_name("socket") 67 | .short("s") 68 | .long("socket") 69 | .value_name("FILE") 70 | .help("Path to the xidlehook socket") 71 | .default_value("/tmp/xidlehook.sock"), 72 | ) 73 | .arg( 74 | Arg::with_name("timer") 75 | .short("t") 76 | .long("timer") 77 | .value_name("SECONDS") 78 | .help("Wait a specified number of seconds") 79 | .takes_value(true), 80 | ) 81 | .arg( 82 | Arg::with_name("pid") 83 | .short("p") 84 | .long("pid") 85 | .value_name("PROCESS_ID") 86 | .help("Wait for a process to quit") 87 | .takes_value(true), 88 | ) 89 | .arg( 90 | Arg::with_name("screen-off") 91 | .short("o") 92 | .long("screen-off") 93 | .takes_value(false) 94 | .help("Turns the screen off on start"), 95 | ) 96 | .arg( 97 | Arg::with_name("quit") 98 | .short("q") 99 | .long("quit") 100 | .value_name("SHUTDOWN") 101 | .default_value("nothing") 102 | .help("action to perform upon quit [shutdown|suspend|restart|nothing]"), 103 | ) 104 | .get_matches(); 105 | 106 | let socket = matches.value_of("socket").expect("no socket path provided"); 107 | let mut client = XIdleHookClient::new(socket).expect("Error connecting to xidlehook socket"); 108 | 109 | let timer_event = if matches.is_present("timer") { 110 | let secs = value_t_or_exit!(matches, "timer", u64); 111 | trigger::timer(secs) 112 | } else { 113 | never() 114 | }; 115 | 116 | let ctrl_c_event = trigger::ctrl_c().expect("Error wiring up ctrl-c listener"); 117 | let (pid_event, pid) = if matches.is_present("pid") { 118 | let pid = value_t_or_exit!(matches, "pid", u64); 119 | let pid_event = trigger::pid(pid).expect(&format!("Process with pid {:?} does not exist", pid)); 120 | (pid_event, Some(pid)) 121 | } else { 122 | (never(), None) 123 | }; 124 | 125 | let quit_action = value_t_or_exit!(matches, "quit", QuitAction); 126 | 127 | client 128 | .send(Command::Disable) 129 | .expect("error communicating with xidlehook"); 130 | 131 | if matches.is_present("screen-off") { 132 | std::process::Command::new("xset") 133 | .args(&["dpms","force","off"]) 134 | .spawn().map(|_| ()).expect("Error powering off display"); 135 | } 136 | 137 | let is_interrupt_quit = 138 | select! { 139 | recv(timer_event) -> _ => { 140 | println!("Times up! Goodbye"); 141 | false 142 | } 143 | recv(pid_event) -> _ => { 144 | println!("Process {:?} exited...", pid); 145 | false 146 | } 147 | recv(ctrl_c_event) -> _ => { 148 | println!("Shutting down..."); 149 | true 150 | } 151 | }; 152 | 153 | client 154 | .send(Command::Enable) 155 | .expect("error communicating with xidlehook"); 156 | 157 | if !is_interrupt_quit { 158 | quit_action.perform().expect("Error performing quit action"); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/trigger.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel::{Receiver, bounded, after}; 2 | use std::thread; 3 | use std::path::Path; 4 | use std::time::{Instant,Duration}; 5 | 6 | pub fn ctrl_c() -> Result, ctrlc::Error> { 7 | let (sender, receiver) = bounded(0); 8 | ctrlc::set_handler(move || { 9 | let _ = sender.send(()); 10 | })?; 11 | 12 | Ok(receiver) 13 | } 14 | 15 | 16 | pub fn pid(pid: u64) -> Option> { 17 | let (sender, receiver) = bounded(0); 18 | let path_str: String = format!("/proc/{:?}", pid); 19 | let path = Path::new(&path_str); 20 | if !path.exists() { 21 | return None 22 | } 23 | thread::spawn(move ||{ 24 | let path = Path::new(&path_str); 25 | loop { 26 | if !path.exists() { 27 | sender.send(()).unwrap(); 28 | break; 29 | } 30 | thread::sleep(Duration::from_millis(100)); 31 | } 32 | }); 33 | Some(receiver) 34 | } 35 | 36 | pub fn timer(secs: u64) -> Receiver { 37 | after(Duration::from_secs(secs)) 38 | } 39 | --------------------------------------------------------------------------------