├── .gitignore ├── bin ├── tray.png ├── startup.png ├── systray.png ├── maximum_ss.png ├── minimum_ss.png ├── shortcut.png ├── atrofac-cli.exe ├── atrofac-gui.exe └── startup_folder.png ├── libgui ├── src │ ├── lib.rs │ ├── engine │ │ ├── mod.rs │ │ ├── default_config.yaml │ │ ├── configuration.rs │ │ └── engine.rs │ ├── systray │ │ ├── mod.rs │ │ ├── README.md │ │ └── win32.rs │ └── system │ │ ├── mod.rs │ │ └── implementation.rs └── Cargo.toml ├── gui ├── resources │ ├── icon.ico │ ├── icon_128.png │ └── icon.svg ├── Cargo.toml ├── build.rs └── src │ └── main.rs ├── library ├── src │ ├── lib.rs │ ├── err.rs │ ├── device_control.rs │ └── atkacpi.rs └── Cargo.toml ├── Cargo.toml ├── cli ├── Cargo.toml └── src │ ├── main.rs │ └── opts.rs ├── CHANGES.md ├── ADVANCED.md ├── README.md ├── LICENSE.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /bin/tray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/bin/tray.png -------------------------------------------------------------------------------- /bin/startup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/bin/startup.png -------------------------------------------------------------------------------- /bin/systray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/bin/systray.png -------------------------------------------------------------------------------- /bin/maximum_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/bin/maximum_ss.png -------------------------------------------------------------------------------- /bin/minimum_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/bin/minimum_ss.png -------------------------------------------------------------------------------- /bin/shortcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/bin/shortcut.png -------------------------------------------------------------------------------- /libgui/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod engine; 2 | pub mod system; 3 | pub(crate) mod systray; 4 | -------------------------------------------------------------------------------- /bin/atrofac-cli.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/bin/atrofac-cli.exe -------------------------------------------------------------------------------- /bin/atrofac-gui.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/bin/atrofac-gui.exe -------------------------------------------------------------------------------- /bin/startup_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/bin/startup_folder.png -------------------------------------------------------------------------------- /gui/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/gui/resources/icon.ico -------------------------------------------------------------------------------- /gui/resources/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronosun/atrofac/HEAD/gui/resources/icon_128.png -------------------------------------------------------------------------------- /libgui/src/engine/mod.rs: -------------------------------------------------------------------------------- 1 | mod configuration; 2 | mod engine; 3 | 4 | pub use {configuration::*, engine::*}; 5 | -------------------------------------------------------------------------------- /library/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod atkacpi; 2 | mod device_control; 3 | mod err; 4 | 5 | pub use {atkacpi::*, device_control::*, err::*}; 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "library", "cli", "gui", "libgui" 5 | ] 6 | 7 | [profile.release] 8 | opt-level = "z" -------------------------------------------------------------------------------- /gui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atrofac-gui" 3 | version = "0.1.0" 4 | authors = ["caelis "] 5 | edition = "2018" 6 | build = "build.rs" 7 | 8 | [dependencies] 9 | log = "0.4" 10 | atrofac-libgui = {path = "../libgui"} 11 | atrofac-library = {path = "../library"} 12 | 13 | [build-dependencies] 14 | winres = "0.1" -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atrofac-cli" 3 | version = "0.1.0" 4 | authors = ["caelis "] 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 | atrofac-library = {path = "../library"} 11 | structopt = "0.3" 12 | exitcode = "1.1" 13 | log = "0.4" 14 | env_logger = "0.7" 15 | 16 | -------------------------------------------------------------------------------- /gui/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | extern crate winres; 3 | 4 | // only build for windows 5 | #[cfg(target_os = "windows")] 6 | fn main() { 7 | if cfg!(target_os = "windows") { 8 | let mut res = winres::WindowsResource::new(); 9 | res.set_icon("resources/icon.ico"); 10 | res.compile().unwrap(); 11 | } 12 | } 13 | 14 | // nothing to do for other operating systems 15 | #[cfg(not(target_os = "windows"))] 16 | fn main() {} 17 | -------------------------------------------------------------------------------- /library/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atrofac-library" 3 | version = "0.1.0" 4 | authors = ["caelis "] 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 | log = "0.4" 11 | regex = "1" 12 | 13 | [target.'cfg(windows)'.dependencies] 14 | winapi = { version = "0.3", features = ["fileapi", "ioapiset", "minwinbase", "handleapi", "errhandlingapi"] } 15 | 16 | -------------------------------------------------------------------------------- /libgui/src/engine/default_config.yaml: -------------------------------------------------------------------------------- 1 | active_plan: "Silent (fanless)" 2 | plans: 3 | - name: "Silent (fanless)" 4 | plan: "silent" 5 | cpu_curve: "30c:0%,49c:0%,59c:0%,69c:0%,79c:31%,89c:49%,99c:56%,109c:56%" 6 | gpu_curve: "30c:0%,49c:0%,59c:0%,69c:0%,79c:34%,89c:51%,99c:61%,109c:61%" 7 | - name: "Silent (low-speed fan)" 8 | plan: "silent" 9 | cpu_curve: "30c:10%,49c:10%,59c:10%,69c:10%,79c:31%,89c:49%,99c:56%,109c:56%" 10 | gpu_curve: "30c:0%,49c:0%,59c:0%,69c:0%,79c:34%,89c:51%,99c:61%,109c:61%" 11 | - name: "Silent (default fan speed)" 12 | plan: "silent" 13 | - name: "Windows" 14 | plan: "windows" 15 | - name: "Performance" 16 | plan: "performance" 17 | - name: "Turbo" 18 | plan: "turbo" 19 | -------------------------------------------------------------------------------- /libgui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atrofac-libgui" 3 | version = "0.1.0" 4 | authors = ["caelis "] 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 | log = "0.4" 11 | atrofac-library = {path = "../library"} 12 | serde = {version = "1.0", features = ["derive", "rc"] } 13 | dirs = "2.0" 14 | serde_yaml = "0.8" 15 | indexmap = "1.3" 16 | msgbox = "0.4.0" 17 | flexi_logger = { version = "0.15", features = ["specfile", "ziplogs"] } 18 | 19 | # things required for systray 20 | [target.'cfg(windows)'.dependencies] 21 | libc="0.2" 22 | encoding="0.2.33" 23 | winapi = { version = "0.3", features = ["winuser", "minwinbase", "basetsd"] } 24 | 25 | -------------------------------------------------------------------------------- /library/src/err.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::borrow::Cow; 3 | use std::fmt::Display; 4 | use winapi::_core::fmt::Formatter; 5 | use winapi::_core::num::TryFromIntError; 6 | 7 | #[derive(Debug)] 8 | pub struct AfErr { 9 | msg: Cow<'static, str>, 10 | } 11 | 12 | impl From for AfErr { 13 | fn from(string: String) -> Self { 14 | Self { msg: string.into() } 15 | } 16 | } 17 | 18 | impl From<&'static str> for AfErr { 19 | fn from(string: &'static str) -> Self { 20 | Self { 21 | msg: Cow::Borrowed(string), 22 | } 23 | } 24 | } 25 | 26 | impl Display for AfErr { 27 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 28 | write!(f, "{}", self.msg) 29 | } 30 | } 31 | 32 | impl From for AfErr { 33 | fn from(_: TryFromIntError) -> Self { 34 | "Integer overflow/underflow (TryFromIntError).".into() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /libgui/src/systray/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::systray::win32::Window; 2 | use atrofac_library::AfErr; 3 | 4 | #[allow(non_snake_case, unused)] 5 | mod win32; 6 | 7 | // Systray Lib 8 | 9 | #[derive(Clone, Debug)] 10 | pub enum SystrayError { 11 | OsError(String), 12 | } 13 | 14 | pub enum SystrayAction { 15 | SelectItem, 16 | DisplayMenu, 17 | HideMenu, 18 | Timer, 19 | Quit, 20 | ApmResume, 21 | } 22 | 23 | pub struct SystrayEvent { 24 | pub action: SystrayAction, 25 | pub menu_index: u32, 26 | } 27 | 28 | impl std::fmt::Display for SystrayError { 29 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 30 | match self { 31 | &SystrayError::OsError(ref err_str) => write!(f, "OsError: {}", err_str), 32 | } 33 | } 34 | } 35 | 36 | impl Into for SystrayError { 37 | fn into(self) -> AfErr { 38 | AfErr::from(format!("Systray error: {}", self)) 39 | } 40 | } 41 | 42 | pub struct Application { 43 | pub window: Window, 44 | } 45 | 46 | impl Application { 47 | pub fn new() -> Result { 48 | match Window::new() { 49 | Ok(w) => Ok(Application { window: w }), 50 | Err(e) => Err(e), 51 | } 52 | } 53 | } 54 | 55 | type Callback = Box () + 'static>; 56 | 57 | fn make_callback(f: F) -> Callback 58 | where 59 | F: std::ops::Fn(&Window) -> () + 'static, 60 | { 61 | Box::new(f) as Callback 62 | } 63 | -------------------------------------------------------------------------------- /libgui/src/systray/README.md: -------------------------------------------------------------------------------- 1 | Copy from https://github.com/mrmekon/systray-rs with some modifications: 2 | 3 | * Added possibility to set a timer (so we don't waste another thread just for a timer). 4 | * Set tooltip -> &String -> &str 5 | * fix for `set_icon_from_buffer` 6 | 7 | ``` 8 | Copyright (c) 2016, Kyle Machulis 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions are met: 13 | 14 | * Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | 17 | * Redistributions in binary form must reproduce the above copyright notice, 18 | this list of conditions and the following disclaimer in the documentation 19 | and/or other materials provided with the distribution. 20 | 21 | * Neither the name of the project nor the names of its 22 | contributors may be used to endorse or promote products derived from 23 | this software without specific prior written permission. 24 | 25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 26 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 28 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 29 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 31 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 33 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | ``` -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 2020-07-07 2 | 3 | * Profile adjustments: 4 | * Profile *Silent (fanless)*: New values are `30c:0%,49c:0%,59c:0%,69c:0%,79c:31%,89c:49%,99c:56%,109c:56%` (cpu) / `30c:0%,49c:0%,59c:0%,69c:0%,79c:34%,89c:51%,99c:61%,109c:61%`. Note 30c vs. 39c!. **You need to manually adjust the configuration file or delete the configuration file (atrofac will then create a new configuration file with the new values).** 5 | * Logging: Timestamp & log fan curve. 6 | 7 | # 2020-07-06 8 | 9 | * atrofac now re-applies the plan after wakeup (from sleep & hibernation); configurable (activated by default). 10 | * Profile adjustments: 11 | * Profile *Silent (fanless)*: New values are `39c:0%,49c:0%,59c:0%,69c:0%,79c:31%,89c:49%,99c:56%,109c:56%` (cpu) / `39c:0%,49c:0%,59c:0%,69c:0%,79c:34%,89c:51%,99c:61%,109c:61%`. (note 39c vs. 30c, 49c vs 40c, ...). Fanless mode is now possible up to 78 degrees (before: 69 degrees). **You need to manually adjust the configuration file or delete the configuration file (atrofac will then create a new configuration file with the new values).** 12 | * Profile *Silent (low-speed fan)*: New values are `30c:10%,49c:10%,59c:10%,69c:10%,79c:31%,89c:49%,99c:56%,109c:56%` (cpu) / `30c:0%,49c:0%,59c:0%,69c:0%,79c:34%,89c:51%,99c:61%,109c:61%` (gpu). Reason: A bit less fan speed fluctuation (still fluctuates a bit, especially within the first minute). **You need to manually adjust the configuration file or delete the configuration file (atrofac will then create a new configuration file with the new values).** 13 | * Added logging; configurable (activated by default). 14 | * Reload the configuration before setting a new plan (this prevents overwriting of changes made by the user when the user has forgotten to apply the new configuration). 15 | 16 | # 2020-05-26 17 | 18 | * atrofac now no longer periodically re-applies the plan (due to increased power drain); configurable. -------------------------------------------------------------------------------- /libgui/src/system/mod.rs: -------------------------------------------------------------------------------- 1 | mod implementation; 2 | 3 | use crate::system::implementation::SystemImpl; 4 | use atrofac_library::AfErr; 5 | use std::borrow::Cow; 6 | use std::path::PathBuf; 7 | use std::time::Duration; 8 | 9 | pub fn new_system_interface() -> Result { 10 | SystemImpl::new() 11 | } 12 | 13 | pub trait SystemInterface { 14 | fn tray_clear(&mut self) -> Result<(), AfErr>; 15 | fn tray_add(&mut self, item: MenuItem) -> Result<(), AfErr>; 16 | fn tray_tooltip(&mut self, text: &str) -> Result<(), AfErr>; 17 | fn tray_icon(&mut self, buf: &[u8], width: u32, height: u32) -> Result<(), AfErr>; 18 | 19 | fn show_err_message(&mut self, title: &str, text: &str) -> Result<(), AfErr>; 20 | fn set_timer(&mut self, duration: Duration) -> Result<(), AfErr>; 21 | fn remove_timer(&mut self) -> Result<(), AfErr>; 22 | 23 | /// Opens the (system) editor to edit the given file. 24 | fn edit(&self, file: &PathBuf) -> Result<(), AfErr>; 25 | 26 | /// Gets next event (blocks until there's a new event). Returns None if there 27 | /// are no more events. 28 | fn receive_event(&self) -> Result, AfErr>; 29 | 30 | /// quits the "system" -> will yield `None` in `receive_event`. 31 | fn quit(&self) -> Result<(), AfErr>; 32 | } 33 | 34 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 35 | pub struct MenuItemIdx(u32); 36 | 37 | impl MenuItemIdx { 38 | pub const fn new(id: u32) -> Self { 39 | Self(id) 40 | } 41 | 42 | pub fn id(&self) -> u32 { 43 | self.0 44 | } 45 | } 46 | 47 | pub enum MenuItem<'a> { 48 | Separator, 49 | String(StringMenuItem<'a>), 50 | } 51 | 52 | pub struct StringMenuItem<'a> { 53 | pub text: Cow<'a, str>, 54 | pub state: MenuItemState, 55 | } 56 | 57 | pub enum SystemEvent { 58 | OnTimer, 59 | OnTray(MenuItemIdx), 60 | OnApmResume, 61 | } 62 | 63 | pub enum MenuItemState { 64 | Default, 65 | Checked, 66 | } 67 | -------------------------------------------------------------------------------- /libgui/src/engine/configuration.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::rc::Rc; 3 | 4 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 5 | pub struct Configuration { 6 | pub active_plan: Option, 7 | pub plans: Vec, 8 | /// default value is "false". 9 | pub disable_logging: Option, 10 | /// log specification. Default value is "info" (other meaningful values are "debug"). 11 | pub log_spec: Option, 12 | } 13 | 14 | impl Default for Configuration { 15 | fn default() -> Self { 16 | Configuration { 17 | active_plan: None, 18 | plans: Default::default(), 19 | disable_logging: None, 20 | log_spec: None, 21 | } 22 | } 23 | } 24 | 25 | #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] 26 | pub struct PlanName(Rc); 27 | 28 | impl PlanName { 29 | pub fn as_str(&self) -> &str { 30 | &self.0 31 | } 32 | } 33 | 34 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 35 | pub struct Plan { 36 | pub name: PlanName, 37 | pub plan: PowerPlan, 38 | pub refresh_interval_sec: Option, 39 | pub refresh_on_apm_resume_automatic: Option, 40 | pub cpu_curve: Option, 41 | pub gpu_curve: Option, 42 | } 43 | 44 | #[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] 45 | pub enum PowerPlan { 46 | #[serde(rename = "windows")] 47 | Windows, 48 | #[serde(rename = "silent")] 49 | Silent, 50 | #[serde(rename = "performance")] 51 | Performance, 52 | #[serde(rename = "turbo")] 53 | Turbo, 54 | } 55 | 56 | impl Into for PowerPlan { 57 | fn into(self) -> atrofac_library::PowerPlan { 58 | match self { 59 | PowerPlan::Windows => atrofac_library::PowerPlan::PerformanceWindows, 60 | PowerPlan::Silent => atrofac_library::PowerPlan::Silent, 61 | PowerPlan::Performance => atrofac_library::PowerPlan::PerformanceWindows, 62 | PowerPlan::Turbo => atrofac_library::PowerPlan::TurboManual, 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | mod opts; 2 | 3 | use crate::opts::Opts; 4 | use atrofac_library::{AfErr, AtkAcpi, FanCurveDevice, FanCurveTable, FanCurveTableBuilder}; 5 | use env_logger::Env; 6 | use log::info; 7 | use log::warn; 8 | use structopt::StructOpt; 9 | 10 | fn convert_to_curve(device: FanCurveDevice, string: &str) -> Result { 11 | if string.trim().len() == 0 { 12 | // the minimum table 13 | Ok(FanCurveTableBuilder::from_string( 14 | device, 15 | "0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%", 16 | ) 17 | .unwrap() 18 | .auto_fix_build()) 19 | } else { 20 | let builder_from_string = FanCurveTableBuilder::from_string(device, string)?; 21 | let is_valid = builder_from_string.is_valid(); 22 | let table = builder_from_string.auto_fix_build(); 23 | if !is_valid { 24 | warn!("Fan curve for {:?} might damage your device and has been auto-adjusted to the minimum safe values: {}.", device, table.to_string()); 25 | } 26 | Ok(table) 27 | } 28 | } 29 | 30 | fn perform(opt: Opts) -> Result<(), AfErr> { 31 | match opt { 32 | Opts::Plan(power_plan) => { 33 | let mut atk = AtkAcpi::new()?; 34 | atk.set_power_plan(power_plan.into())?; 35 | Ok(()) 36 | } 37 | Opts::Fan(fan) => { 38 | let cpu = convert_to_curve(FanCurveDevice::Cpu, &fan.cpu)?; 39 | let gpu = convert_to_curve(FanCurveDevice::Gpu, &fan.gpu)?; 40 | let mut atk = AtkAcpi::new()?; 41 | atk.set_power_plan(fan.plan.into())?; 42 | atk.set_fan_curve(&cpu)?; 43 | atk.set_fan_curve(&gpu)?; 44 | Ok(()) 45 | } 46 | } 47 | } 48 | 49 | fn main() { 50 | env_logger::from_env(Env::default().default_filter_or("info")).init(); 51 | 52 | let opt = Opts::from_args(); 53 | if let Err(error) = perform(opt) { 54 | warn!("Unable to perform operation: {:?}", error); 55 | std::process::exit(exitcode::CONFIG); 56 | } else { 57 | info!("Success."); 58 | std::process::exit(exitcode::OK); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ADVANCED.md: -------------------------------------------------------------------------------- 1 | ## Building 2 | 3 | Checkout and use a current rust version. Should build out of the box. Windows only. 4 | 5 | ## Usage: `atrofac-cli` 6 | 7 | ### Change power plan 8 | 9 | Note: This DOES NOT adjust the fan curve. It uses the default fan curve defined by Asus. 10 | 11 | ```shell 12 | atrofac-cli plan 13 | ``` 14 | 15 | Examples: 16 | 17 | ```shell 18 | atrofac-cli plan silent 19 | atrofac-cli plan performance 20 | atrofac-cli plan turbo 21 | atrofac-cli plan windows 22 | ``` 23 | 24 | ### Change fan curve 25 | 26 | This adjusts the fan curve and sets a power plan. 27 | 28 | ```shell 29 | atrofac-cli fan --plan --cpu --gpu 30 | ``` 31 | 32 | The `FAN_CURVE` is basically the same you can do graphically in the Armoury Crate GUI. It's a list (8 entries) of temperature and fan percentage. Assume you want to set a fan curve that looks like this: 33 | 34 | | Entry | Temperature | Fan Percentage | 35 | | --- |:---:| ---:| 36 | | 0 | 30 | 0% | 37 | | 1 | 40 | 5% | 38 | | 2 | 50 | 10% | 39 | | 3 | 60 | 20% | 40 | | 4 | 70 | 35% | 41 | | 5 | 80 | 55% | 42 | | 6 | 90 | 65% | 43 | | 7 | 100 | 65% | 44 | 45 | ... in this case the `FAN_CURVE` string would look like this: `30c:0%,40c:5%,50c:10%,60c:20%,70c:35%,80c:55%,90c:65%,100c:65%`. 46 | 47 | #### Examples 48 | 49 | Set the silent power profile and set the fan curves (CPU & GPU) to a minimum: 50 | 51 | ```shell 52 | atrofac-cli fan 53 | ``` 54 | 55 | Set the windows power plan and set the fan curves: 56 | 57 | ```shell 58 | atrofac-cli fan --plan windows --cpu 30c:0%,40c:5%,50c:10%,60c:20%,70c:35%,80c:55%,90c:65%,100c:65% --gpu 30c:0%,40c:5%,50c:10%,60c:20%,70c:35%,80c:55%,90c:65%,100c:65% 59 | ``` 60 | 61 | #### Limits 62 | 63 | autrofac will automatically adjust dangerous fan curves (**NO GUARANTEE THAT THIS REALLY WORKS**). These are the limits: 64 | 65 | | Entry | Temperature | Fan Percentage (CPU) | Fan Percentage (GPU) | 66 | | --- |:---:| ---:| ---:| 67 | | 0 | 30..39 | 0% or higher | 0% or higher | 68 | | 1 | 40..49 | 0% or higher | 0% or higher | 69 | | 2 | 50..59 | 0% or higher | 0% or higher | 70 | | 3 | 60..69 | 0% or higher | 0% or higher | 71 | | 4 | 70..79 | 31% or higher | 34% or higher | 72 | | 5 | 80..89 | 49% or higher | 51% or higher | 73 | | 6 | 90..99 | 56% or higher | 61% or higher | 74 | | 6 | 100..109 | 56% or higher | 61% or higher | 75 | 76 | So it's possible to operate the Zephyrus G14 fanless as long as the temperatures of the GPU & CPU are below 69 degrees celsius. 77 | 78 | ## GUI 79 | 80 | There's no GUI yet... but you can crate a shortcut, like this: 81 | 82 | ![Shortcut](bin/shortcut.png) 83 | 84 | ## Technical details 85 | 86 | It uses the `DeviceIoControl` function (windows API) on the `\\.\ATKACPI`-file to change the power plan and set the fan curve (like the Armoury Crate does). -------------------------------------------------------------------------------- /cli/src/opts.rs: -------------------------------------------------------------------------------- 1 | use structopt::clap::arg_enum; 2 | use structopt::clap::AppSettings; 3 | use structopt::StructOpt; 4 | 5 | #[derive(StructOpt, Debug)] 6 | #[structopt(setting = AppSettings::InferSubcommands)] 7 | #[structopt(rename_all = "kebab-case")] 8 | pub enum Opts { 9 | /// Sets the power plan and uses the default fan curve. 10 | #[structopt(aliases = &["plan", "set-plan"])] 11 | Plan(PowerPlan), 12 | /// Sets a custom fan curve with a power plan. 13 | #[structopt(aliases = &["fan", "set-fan"])] 14 | Fan(Fan), 15 | } 16 | 17 | #[derive(StructOpt, Debug)] 18 | #[structopt(setting = AppSettings::InferSubcommands)] 19 | pub enum PowerPlan { 20 | /// Windows power plan. 21 | #[structopt(aliases = &["win", "windows"])] 22 | Windows, 23 | /// Silent power plan (with default fan curve). 24 | #[structopt(aliases = &["silent"])] 25 | Silent, 26 | /// Performance power plan. 27 | #[structopt(aliases = &["performance"])] 28 | Performance, 29 | /// Turbo power plan. 30 | #[structopt(aliases = &["turbo"])] 31 | Turbo, 32 | } 33 | 34 | impl Into for PowerPlan { 35 | fn into(self) -> atrofac_library::PowerPlan { 36 | match self { 37 | PowerPlan::Windows => atrofac_library::PowerPlan::PerformanceWindows, 38 | PowerPlan::Silent => atrofac_library::PowerPlan::Silent, 39 | PowerPlan::Performance => atrofac_library::PowerPlan::PerformanceWindows, 40 | PowerPlan::Turbo => atrofac_library::PowerPlan::TurboManual, 41 | } 42 | } 43 | } 44 | 45 | arg_enum! { 46 | #[derive(Debug)] 47 | #[allow(non_camel_case_types)] 48 | pub enum PowerPlan2 { 49 | windows, 50 | silent, 51 | performance, 52 | turbo 53 | } 54 | } 55 | 56 | impl Into for PowerPlan2 { 57 | fn into(self) -> atrofac_library::PowerPlan { 58 | match self { 59 | PowerPlan2::windows => atrofac_library::PowerPlan::PerformanceWindows, 60 | PowerPlan2::silent => atrofac_library::PowerPlan::Silent, 61 | PowerPlan2::performance => atrofac_library::PowerPlan::PerformanceWindows, 62 | PowerPlan2::turbo => atrofac_library::PowerPlan::TurboManual, 63 | } 64 | } 65 | } 66 | 67 | #[derive(StructOpt, Debug)] 68 | #[structopt(setting = AppSettings::InferSubcommands)] 69 | pub struct Fan { 70 | /// The power plan to set with the custom fan curve. 71 | #[structopt(possible_values = &PowerPlan2::variants(), long = "plan", default_value = "silent")] 72 | pub plan: PowerPlan2, 73 | /// The CPU fan curve. If not given, will set the fan speed to minimum. Must be a list of 74 | /// comma separated entries (8 entries). Each entry looks like this: 75 | /// c:%. Example: 0c:0%,40c:0%,50c:0%,60c:0%,70c:34%,80c:51%,90c:61%,100c:61%. 76 | #[structopt(long = "cpu", default_value = "")] 77 | pub cpu: String, 78 | /// The GPU fan curve. If not given, will set the fan speed to minimum. Must be a list of 79 | /// comma separated entries (8 entries). Each entry looks like this: 80 | /// c:%. Example: 0c:0%,40c:0%,50c:0%,60c:0%,70c:34%,80c:51%,90c:61%,100c:61%. 81 | #[structopt(long = "gpu", default_value = "")] 82 | pub gpu: String, 83 | } 84 | -------------------------------------------------------------------------------- /gui/resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 42 | 51 | 54 | 58 | 59 | 60 | 62 | 63 | 65 | 66 | 68 | 69 | 71 | 72 | 74 | 75 | 77 | 78 | 80 | 81 | 83 | 84 | 86 | 87 | 89 | 90 | 92 | 93 | 95 | 96 | 98 | 99 | 101 | 102 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /library/src/device_control.rs: -------------------------------------------------------------------------------- 1 | use crate::err::AfErr; 2 | use log::warn; 3 | use std::convert::TryFrom; 4 | use std::ffi::OsStr; 5 | use std::iter::once; 6 | use std::os::windows::ffi::OsStrExt; 7 | use std::ptr::null_mut; 8 | use winapi::ctypes::c_void; 9 | use winapi::um::errhandlingapi::GetLastError; 10 | use winapi::um::fileapi::CreateFileW; 11 | use winapi::um::handleapi::CloseHandle; 12 | use winapi::um::ioapiset::DeviceIoControl; 13 | use winapi::um::winnt::HANDLE; 14 | 15 | pub struct DeviceControl { 16 | handle: HANDLE, 17 | } 18 | 19 | impl DeviceControl { 20 | pub fn new(file: &str) -> Result { 21 | let wide_file: Vec = OsStr::new(file).encode_wide().chain(once(0)).collect(); 22 | let file_handle = unsafe { 23 | CreateFileW( 24 | wide_file.as_ptr(), 25 | 0xc0000000, 26 | 3, 27 | null_mut(), 28 | 3, 29 | 0, 30 | null_mut(), 31 | ) 32 | }; 33 | if file_handle != null_mut() { 34 | Ok(Self { 35 | handle: file_handle, 36 | }) 37 | } else { 38 | Err(format!("Unable to open device file '{}'.", file).into()) 39 | } 40 | } 41 | 42 | pub fn control( 43 | &mut self, 44 | control_code: u32, 45 | in_buffer: &mut [u8], 46 | out_buffer: &mut [u8], 47 | ) -> Result { 48 | let in_buffer_size = u32::try_from(in_buffer.len())?; 49 | let out_buffer_size = u32::try_from(out_buffer.len())?; 50 | let mut out_buffer_written: u32 = 0; 51 | let out_buffer_written_ref: &mut u32 = &mut out_buffer_written; 52 | 53 | let in_buffer_c_void: *mut c_void = in_buffer as *mut _ as *mut c_void; 54 | let out_buffer_c_void: *mut c_void = out_buffer as *mut _ as *mut c_void; 55 | let success = unsafe { 56 | DeviceIoControl( 57 | self.handle, 58 | control_code, 59 | in_buffer_c_void, 60 | in_buffer_size, 61 | out_buffer_c_void, 62 | out_buffer_size, 63 | out_buffer_written_ref, 64 | null_mut(), 65 | ) 66 | }; 67 | if success != 0 { 68 | Ok(ControlResult { out_buffer_written }) 69 | } else { 70 | let last_error = unsafe { GetLastError() }; 71 | Err(format!( 72 | "Unable to write command {} to file (DeviceIoControl). \ 73 | Last error code: {}, \ 74 | In-Buffer {:x?}.", 75 | control_code, last_error, in_buffer 76 | ) 77 | .into()) 78 | } 79 | } 80 | 81 | fn dispose(&mut self) -> Result<(), AfErr> { 82 | let success = unsafe { CloseHandle(self.handle) }; 83 | if success != 0 { 84 | Ok(()) 85 | } else { 86 | Err("Unable to close file".into()) 87 | } 88 | } 89 | } 90 | 91 | impl Drop for DeviceControl { 92 | fn drop(&mut self) { 93 | if let Err(err) = self.dispose() { 94 | warn!("Unable to close file: {:?}", err) 95 | } 96 | } 97 | } 98 | 99 | pub struct ControlResult { 100 | out_buffer_written: u32, 101 | } 102 | 103 | impl ControlResult { 104 | pub fn out_buffer_written(&self) -> u32 { 105 | self.out_buffer_written 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /libgui/src/system/implementation.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::path::PathBuf; 3 | use std::process::Command; 4 | use std::time::Duration; 5 | 6 | use msgbox::IconType; 7 | 8 | use atrofac_library::AfErr; 9 | 10 | use crate::system::{MenuItem, MenuItemIdx, MenuItemState, SystemEvent, SystemInterface}; 11 | use crate::systray::{Application, SystrayAction, SystrayError}; 12 | 13 | pub struct SystemImpl { 14 | app: Application, 15 | } 16 | 17 | impl SystemImpl { 18 | pub fn new() -> Result { 19 | let app = map_err(Application::new())?; 20 | Ok(Self { app }) 21 | } 22 | } 23 | 24 | impl SystemInterface for SystemImpl { 25 | fn tray_clear(&mut self) -> Result<(), AfErr> { 26 | map_err(self.app.window.clear_menu())?; 27 | Ok(()) 28 | } 29 | 30 | fn tray_add(&mut self, item: MenuItem) -> Result<(), AfErr> { 31 | match item { 32 | MenuItem::Separator => { 33 | map_err(self.app.window.add_menu_separator())?; 34 | } 35 | MenuItem::String(string_menu_item) => { 36 | let index = map_err(self.app.window.add_menu_item( 37 | &string_menu_item.text, 38 | false, 39 | |_| {}, 40 | ))?; 41 | match string_menu_item.state { 42 | MenuItemState::Default => {} 43 | MenuItemState::Checked => { 44 | map_err(self.app.window.select_menu_item(index))?; 45 | } 46 | } 47 | } 48 | } 49 | Ok(()) 50 | } 51 | 52 | fn tray_tooltip(&mut self, text: &str) -> Result<(), AfErr> { 53 | map_err(self.app.window.set_tooltip(text)) 54 | } 55 | 56 | fn tray_icon(&mut self, buf: &[u8], width: u32, height: u32) -> Result<(), AfErr> { 57 | map_err(self.app.window.set_icon_from_buffer(buf, width, height)) 58 | } 59 | 60 | fn show_err_message(&mut self, title: &str, text: &str) -> Result<(), AfErr> { 61 | msgbox::create(title, text, IconType::Error); 62 | Ok(()) 63 | } 64 | 65 | fn set_timer(&mut self, duration: Duration) -> Result<(), AfErr> { 66 | map_err( 67 | self.app 68 | .window 69 | .set_timer(u32::try_from(duration.as_millis())?), 70 | )?; 71 | Ok(()) 72 | } 73 | 74 | fn remove_timer(&mut self) -> Result<(), AfErr> { 75 | map_err(self.app.window.remove_timer()) 76 | } 77 | 78 | fn edit(&self, file: &PathBuf) -> Result<(), AfErr> { 79 | Command::new("notepad.exe") 80 | .args(&[file.as_os_str()]) 81 | .output() 82 | .map_err(|err| AfErr::from(format!("Unable to start editor: {}.", err)))?; 83 | Ok(()) 84 | } 85 | 86 | fn receive_event(&self) -> Result, AfErr> { 87 | loop { 88 | let systray_event = 89 | self.app.window.rx.recv().map_err(|err| { 90 | AfErr::from(format!("Unable to get data from channel: {}.", err)) 91 | })?; 92 | 93 | match systray_event.action { 94 | SystrayAction::SelectItem => { 95 | return Ok(Some(SystemEvent::OnTray(MenuItemIdx::new( 96 | systray_event.menu_index, 97 | )))); 98 | } 99 | SystrayAction::DisplayMenu => { 100 | // not interested in this 101 | } 102 | SystrayAction::HideMenu => { 103 | // not interested in this 104 | } 105 | SystrayAction::Timer => { 106 | return Ok(Some(SystemEvent::OnTimer)); 107 | } 108 | SystrayAction::Quit => { 109 | return Ok(None); 110 | } 111 | SystrayAction::ApmResume => return Ok(Some(SystemEvent::OnApmResume)), 112 | }; 113 | } 114 | } 115 | 116 | fn quit(&self) -> Result<(), AfErr> { 117 | self.app.window.quit(); 118 | Ok(()) 119 | } 120 | } 121 | 122 | fn map_err(result: Result) -> Result { 123 | result.map_err(|err| { 124 | let af_err: AfErr = err.into(); 125 | af_err 126 | }) 127 | } 128 | -------------------------------------------------------------------------------- /gui/src/main.rs: -------------------------------------------------------------------------------- 1 | #![windows_subsystem = "windows"] 2 | 3 | use atrofac_libgui::engine::Engine; 4 | use atrofac_libgui::system::{ 5 | new_system_interface, MenuItem, MenuItemIdx, MenuItemState, StringMenuItem, SystemEvent, 6 | SystemInterface, 7 | }; 8 | use atrofac_library::AfErr; 9 | use log::info; 10 | use std::borrow::Cow; 11 | use std::convert::TryFrom; 12 | use std::time::Duration; 13 | 14 | const MENU_ITEM_RELOAD_CONFIG_OFFSET: usize = 1; 15 | const MENU_ITEM_EDIT_CONFIG_OFFSET: usize = 2; 16 | const MENU_ITEM_EDIT_EXIT_OFFSET: usize = 3; 17 | 18 | fn apply(engine: &mut Engine, system: &mut impl SystemInterface) -> Result<(), AfErr> { 19 | engine.apply()?; 20 | 21 | // set the timer 22 | if let Some(active_plan) = engine.active_plan() { 23 | if let Some(refresh_interval_sec) = active_plan.refresh_interval_sec { 24 | system.set_timer(Duration::from_secs(refresh_interval_sec as u64))?; 25 | } else { 26 | system.remove_timer()?; 27 | } 28 | } 29 | Ok(()) 30 | } 31 | 32 | fn on_apm_resume_automatic(engine: &mut Engine) -> Result<(), AfErr> { 33 | // Check whether we need to do something (stored in the configuration) 34 | if let Some(active_plan) = engine.active_plan() { 35 | // default value is "true" 36 | let refresh_on_resume = active_plan.refresh_on_apm_resume_automatic.unwrap_or(true); 37 | if refresh_on_resume { 38 | info!("Going to re-apply the plan due to wakeup (PBT_APMRESUMEAUTOMATIC)."); 39 | engine.apply()?; 40 | } 41 | } 42 | Ok(()) 43 | } 44 | 45 | fn load_tray(engine: &Engine, system: &mut impl SystemInterface) -> Result<(), AfErr> { 46 | system.tray_clear()?; 47 | 48 | let active_plan = engine.active_plan(); 49 | for (_, plan_name) in engine.available_plans() { 50 | let active = if let Some(active_plan) = active_plan { 51 | active_plan.name == plan_name 52 | } else { 53 | false 54 | }; 55 | system.tray_add(MenuItem::String(StringMenuItem { 56 | text: Cow::Borrowed(plan_name.as_str()), 57 | state: if active { 58 | MenuItemState::Checked 59 | } else { 60 | MenuItemState::Default 61 | }, 62 | }))?; 63 | } 64 | system.tray_add(MenuItem::Separator)?; 65 | system.tray_add(MenuItem::String(StringMenuItem { 66 | text: "Reload configuration".into(), 67 | state: MenuItemState::Default, 68 | }))?; 69 | system.tray_add(MenuItem::String(StringMenuItem { 70 | text: "Edit configuration".into(), 71 | state: MenuItemState::Default, 72 | }))?; 73 | system.tray_add(MenuItem::String(StringMenuItem { 74 | text: "Quit application".into(), 75 | state: MenuItemState::Default, 76 | }))?; 77 | Ok(()) 78 | } 79 | 80 | fn on_tray( 81 | menu_item_id: MenuItemIdx, 82 | engine: &mut Engine, 83 | system: &mut impl SystemInterface, 84 | ) -> Result<(), AfErr> { 85 | let index_usize = usize::try_from(menu_item_id.id())?; 86 | let number_of_plans = engine.number_of_plans(); 87 | if index_usize >= number_of_plans { 88 | // not a plan 89 | let offset = index_usize - number_of_plans; 90 | match offset { 91 | MENU_ITEM_RELOAD_CONFIG_OFFSET => { 92 | engine.load_configuration()?; 93 | load_tray(engine, system)?; 94 | apply(engine, system)?; 95 | info!("Plan applied due to configuration file reload."); 96 | Ok(()) 97 | } 98 | MENU_ITEM_EDIT_CONFIG_OFFSET => { 99 | let config_file = engine.config_file(); 100 | system.edit(config_file)?; 101 | Ok(()) 102 | } 103 | MENU_ITEM_EDIT_EXIT_OFFSET => { 104 | info!("Quitting atrofac (user selected quit from menu)."); 105 | system.quit()?; 106 | Ok(()) 107 | } 108 | _ => Err(AfErr::from(format!("Unknown menu item offset {}.", offset))), 109 | } 110 | } else { 111 | // it's a plan 112 | // first re-load the configuration (in case user has edited the file; prevents overwriting the file). 113 | engine.load_configuration()?; 114 | if let Some(plan_name) = engine.plan_by_index(menu_item_id.id() as usize).cloned() { 115 | info!("User selected plan {} in system tray.", plan_name.as_str()); 116 | engine.set_active_plan(plan_name); 117 | // when the plan has been changed, save the configuration 118 | engine.save_configuration()?; 119 | apply(engine, system)?; 120 | // reload tray 121 | load_tray(engine, system)?; 122 | Ok(()) 123 | } else { 124 | Err(AfErr::from(format!( 125 | "Plan #{} not found.", 126 | menu_item_id.id() 127 | ))) 128 | } 129 | } 130 | } 131 | 132 | fn run_main_with_error( 133 | engine: &mut Engine, 134 | system: &mut impl SystemInterface, 135 | ) -> Result<(), AfErr> { 136 | engine.load_configuration()?; 137 | apply(engine, system)?; 138 | info!("Plan initially applied while application startup."); 139 | load_tray(engine, system)?; 140 | system.tray_tooltip("Control fan curve and power profile for Asus Zephyrus ROG G14.")?; 141 | system.tray_icon(include_bytes!("../resources/icon.ico"), 64, 64)?; 142 | 143 | // loop 144 | loop { 145 | let event = system.receive_event()?; 146 | if let Some(event) = event { 147 | match event { 148 | SystemEvent::OnTimer => { 149 | apply(engine, system)?; 150 | info!( 151 | "Plan successfully re-applied due to timer event ('refresh_interval_sec')." 152 | ); 153 | } 154 | SystemEvent::OnTray(menu_item_id) => { 155 | on_tray(menu_item_id, engine, system)?; 156 | } 157 | SystemEvent::OnApmResume => { 158 | on_apm_resume_automatic(engine)?; 159 | } 160 | } 161 | } else { 162 | // finish 163 | return Ok(()); 164 | } 165 | } 166 | } 167 | 168 | fn run_main(engine: &mut Engine, system: &mut impl SystemInterface) { 169 | if let Err(err) = run_main_with_error(engine, system) { 170 | system 171 | .show_err_message("Error", &format!("{}", err)) 172 | .expect("Unable to display error message"); 173 | } 174 | } 175 | 176 | fn main() { 177 | let mut system = new_system_interface().expect("Unable to create system interface"); 178 | let mut engine = Engine::new().expect("Unable to create engine."); 179 | run_main(&mut engine, &mut system); 180 | } 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atrofac 2 | 3 | A GUI, a library and a command line application to control the power plan, and the fan curve (CPU & GPU) of Asus Zephyrus G14 devices (might also work with other devices that use the Armoury Crate Service). Fanless mode is possible as long as the GPU & CPU temperatures are not too hot (even on battery). 4 | 5 | ## WARING / DISCLAIMER 6 | 7 | **USE THIS AT YOUR OWN RISK. MANUALLY ADJUSTING FAN CURVES CAN BE DANGEROUS (MIGHT BREAK YOUR DEVICE). FRANKLY I HAVE NO IDEA WHAT I'M DOING SINCE THERE'S NO DOCUMENTATION FROM ASUS ON HOW TO MANUALLY ADJUST THE FAN CURVES. ATROFAC TRIES TO PREVENT YOU FROM SETTING A DANGEROUS FAN CURVE (THE SAME WAY ARMOURY CRATE DOES).** 8 | 9 | ## State / Changes 10 | 11 | Has been in use for more than a month (end of may 2020 - early july 2020) and works with some minor flaws (see reddit for details and read this documentation). This is my G14 model: firmware `GA401IU.212`, Ryzen 7 4800HS, GeForce GTX 1660 Ti. According to reddit it seems to work for other users too (and even other notebook models): [https://www.reddit.com/r/ZephyrusG14/comments/go16hp/fanless_completely_silent_on_battery_manual_fan/](https://www.reddit.com/r/ZephyrusG14/comments/go16hp/fanless_completely_silent_on_battery_manual_fan/). 12 | 13 | Changes : [CHANGES.md](CHANGES.md) 14 | 15 | ## Getting started (end user documentation) 16 | 17 | You most likely want to use the atrofac GUI (if not, see here for the command line version: [ADVANCED.md](ADVANCED.md)). It's a simple system tray application that runs in the background. 18 | 19 | ![System tray](bin/systray.png) 20 | 21 | ### Step 1: Download 22 | 23 | Download the binary: [atrofac-gui.exe](bin/atrofac-gui.exe). 24 | 25 | ### Step 2: Autostart 26 | 27 | You usually want atrofac to be started when your computer starts. You can also skip this step and start the application manually. 28 | 29 | Press Windows-Key + R. This opens "Run": 30 | 31 | ![Run](bin/startup.png) 32 | 33 | Enter `shell:startup`. Now move the "autrofac-gui" to the Start-up folder. Done. 34 | 35 | ![Run](bin/startup_folder.png) 36 | 37 | ### Step 3: Start the app 38 | 39 | Start the app and you should see a new icon in the system tray; the app is running. 40 | 41 | ![Screenshot](bin/tray.png) 42 | 43 | ### Step 4 (optional): Reduce power drain 44 | 45 | To make sure the G14 stays fanless, you have to make sure the device does not get too hot / drains too much power. There's a good article, read it and apply the suggestions: [https://blog.remjx.com/how-to-fix-thermal-battery-life-issues-asus-zephyrus-g14-g15-2020/](https://blog.remjx.com/how-to-fix-thermal-battery-life-issues-asus-zephyrus-g14-g15-2020/). Things I did: 46 | 47 | * Updated the Bios and all drivers. 48 | * "Disable CPU Boost on default power profile": I set "Processor performance boost mode" to "Disabled" on battery and to "Efficient Enabled" when plugged in. 49 | * Removed "GameFirst". 50 | * Set "Switchable Dynamic Graphics" to "Force power-saving graphics" on battery. (in Windows "Edit power plan"). 51 | * Disabled all windows background apps (it should be enough if you just disable those that utilize the NVidia-GPU). (see "Windows Settings > Background Apps"). 52 | 53 | This is the result: 54 | 55 | About 4 watts idling with minimum brightness: 56 | 57 | ![Run](bin/minimum_ss.png) 58 | 59 | About 7.6 watts idling with maximum brightness: 60 | 61 | ![Run](bin/maximum_ss.png) 62 | 63 | Note: This are the values when idling... As soon as you do something (even just opening a web browser), values will increase. If your values are within +/- 20% everything is ok. 64 | 65 | ### Step 5 (optional): Remove Armoury Crate & Service 66 | 67 | This step might be a bit controversial. I removed those two apps: 68 | 69 | * Armoury Crate 70 | * Armoury Crate Service 71 | 72 | Why? Armoury Crate overwrites the settings made by atrofac on certain occasions (when waking up, after a restart, when plugging in, on unplug). To make sure this does not happen, I just uninstalled Armoury Crate & Armoury Crate Service. I suggest doing that too - you can re-install those apps later if this does not work for you. 73 | 74 | #### Alternative 1 75 | 76 | Keep Armoury Crate and manually re-apply the desired plan in atrofac every time Armoury Crate overwrites the settings made by atrofac. 77 | 78 | #### Alternative 2 79 | 80 | Set `refresh_interval_sec` to a value (about 30-120 seconds). See below for more details. **I wouldn't advise it**: Why? Every time atrofac (and also Armoury Crate) applies a power plan, there's a short peak in power consumption (up to 25 watts). I can't really tell the impact on battery life - the 25 watts drain could just be for a millisecond - but it's long enough to be visible in [BatteryBar](http://batterybarpro.com/). (technical detail: unfortunately I have not yet figured out how to read out the current power plan - if this was possible I could apply the power plan only when something has changed). 81 | 82 | ### Optional: Change settings 83 | 84 | atrofac comes preconfigured with 6 different profiles ("Silent (fanless)", "Silent (low-speed fan)", ...). If you want to see what those profiles do - or change them, click on "Edit configuration"; the configuration is just a yaml file. After saving the file you have to click "Reload configuration" to apply the changes. If you break the configuration file, the app won't start up anymore. Don't worry: Just delete the broken configuration file and atrofac will create a new one. The file can be found here (note: the folder `AppData` is hidden by default): 85 | 86 | `` 87 | C:\Users\\AppData\Roaming\atrofac_gui_config.yaml 88 | `` 89 | 90 | An entry for a plan looks like this: 91 | 92 | ```yaml 93 | - name: Silent (Fanless) 94 | plan: silent 95 | cpu_curve: "30c:0%,49c:0%,59c:0%,69c:0%,79c:31%,89c:49%,99c:56%,109c:56%" 96 | gpu_curve: "30c:0%,49c:0%,59c:0%,69c:0%,79c:34%,89c:51%,99c:61%,109c:61%" 97 | ``` 98 | 99 | * `name`: The name, obviously. 100 | * `plan`: One of `silent`, `windows`, `performance`, `turbo`. 101 | * `cpu_curve`: CPU fan curve, see [ADVANCED.md](ADVANCED.md). 102 | * `gpu_curve`: GPU fan curve, see [ADVANCED.md](ADVANCED.md). 103 | * `refresh_interval_sec` (optional): Armoury crate will overwrite the changes made by atrofac eventually (usually when waking up from sleep; when going from AC to DC or vice versa). So atrofac will periodically apply the settings every n seconds. It has been renamed from `update_interval_sec` to make sure it's gone when somebody updates the app and keeps the old configuration. Why: see "end user documentation". 104 | * `refresh_on_apm_resume_automatic` (optional; default value is `true`): If this is true, atrofac will re-apply the plan after resuming from sleep or hibernation. You usually want this to be `true`. (technical detail: see Win32 API `PBT_APMRESUMEAUTOMATIC`). 105 | 106 | You can also omit `cpu_curve` and `gpu_curve` in that case the default fan curves (as defined by Asus) are used. 107 | 108 | ## Binaries 109 | 110 | There's no CI yet, but there are prebuilt binaries: 111 | 112 | * GUI: [atrofac-gui.exe](bin/atrofac-gui.exe) 113 | * CLI-tool: [atrofac-cli.exe](bin/atrofac-cli.exe) 114 | 115 | ## Advanced 116 | 117 | See [ADVANCED.md](ADVANCED.md) if you need more information such as: 118 | 119 | * The command line version of this tool. 120 | * Building. 121 | * Technical information. 122 | * Fan-curve details. 123 | 124 | ### Logs 125 | 126 | Log files are written to `C:\Users\\AppData\Local\atrofac_logs`. These files are deleted automatically after a few days. There are two options in the config file (top level section) related to logging: 127 | 128 | * `disable_logging`: Set this to `true` to disable logging (it's `false` by default). 129 | * `log_spec`: A string; log specification. By default it's `info`; See [https://docs.rs/flexi_logger/0.15.7/flexi_logger/struct.LogSpecification.html](https://docs.rs/flexi_logger/0.15.7/flexi_logger/struct.LogSpecification.html) for details. 130 | 131 | -------------------------------------------------------------------------------- /libgui/src/engine/engine.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::{BufReader, BufWriter, Write}; 3 | use std::path::PathBuf; 4 | 5 | use dirs::{config_dir, data_local_dir}; 6 | use indexmap::map::IndexMap; 7 | use log::info; 8 | use log::warn; 9 | 10 | use atrofac_library::{AfErr, AtkAcpi, FanCurveDevice, FanCurveTable, FanCurveTableBuilder}; 11 | 12 | use crate::engine::configuration::{Configuration, Plan, PlanName}; 13 | use flexi_logger::{Age, Cleanup, Criterion, Logger, Naming, detailed_format}; 14 | use std::fs; 15 | 16 | const DEFAULT_LOG_LEVEL: &str = "info"; 17 | const CONFIG_FILE_NAME: &str = "atrofac_gui_config.yaml"; 18 | const ATROFAC_LOG_DIR: &str = "atrofac_logs"; 19 | 20 | pub struct Engine { 21 | configuration: Configuration, 22 | plans: IndexMap, 23 | config_file: PathBuf, 24 | } 25 | 26 | impl Engine { 27 | pub fn new() -> Result { 28 | let config_file = Self::require_config_file_path_buf()?; 29 | let engine = Self { 30 | configuration: Default::default(), 31 | plans: Default::default(), 32 | config_file, 33 | }; 34 | engine.start_logging()?; 35 | Ok(engine) 36 | } 37 | 38 | pub fn load_configuration(&mut self) -> Result<(), AfErr> { 39 | let path_buf = self.config_file(); 40 | if !path_buf.exists() { 41 | let configuration = serde_yaml::from_str(include_str!("default_config.yaml")) 42 | .map_err(|err| AfErr::from(format!("Configuration file is invalid: {}.", err)))?; 43 | self.set_configuration(configuration); 44 | // save the configuration right now (so the user has a template to edit). 45 | self.save_configuration()?; 46 | info!("Created a new configuration file (since there was none)."); 47 | Ok(()) 48 | } else { 49 | let file = File::open(path_buf.as_path()).map_err(|err| { 50 | AfErr::from(format!( 51 | "Unable to open config file {}: {}.", 52 | path_buf.as_path().display(), 53 | err 54 | )) 55 | })?; 56 | let buffered_reader = BufReader::new(file); 57 | let configuration = serde_yaml::from_reader(buffered_reader) 58 | .map_err(|err| AfErr::from(format!("Configuration file is invalid: {}.", err)))?; 59 | self.set_configuration(configuration); 60 | info!("Configuration file successfully (re-)loaded."); 61 | Ok(()) 62 | } 63 | } 64 | 65 | pub fn save_configuration(&self) -> Result<(), AfErr> { 66 | let path_buf = self.config_file(); 67 | let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(path_buf.as_path()).map_err(|err| { 68 | AfErr::from(format!("Unable to open config file {} for writing; are you currently editing the file?: {}.", path_buf.as_path().display(), err)) 69 | })?; 70 | { 71 | let mut buffered_writer = BufWriter::new(&mut file); 72 | serde_yaml::to_writer(&mut buffered_writer, &self.configuration).map_err(|err| { 73 | AfErr::from(format!( 74 | "Unable to serialize configuration (save configuration): {}.", 75 | err 76 | )) 77 | })?; 78 | buffered_writer 79 | .flush() 80 | .map_err(|err| AfErr::from(format!("Unable to flush buffer: {}", err)))?; 81 | } 82 | file.flush() 83 | .map_err(|err| AfErr::from(format!("Unable to flush file: {}", err)))?; 84 | info!("Configuration file successfully saved (modified configuration file)."); 85 | Ok(()) 86 | } 87 | 88 | pub fn active_plan(&self) -> Option<&Plan> { 89 | if let Some(active_plan) = &self.configuration.active_plan { 90 | self.plans.get(active_plan) 91 | } else { 92 | None 93 | } 94 | } 95 | 96 | pub fn set_active_plan(&mut self, plan: PlanName) { 97 | self.configuration.active_plan = Some(plan); 98 | } 99 | 100 | pub fn available_plans<'a>(&'a self) -> impl Iterator + 'a { 101 | self.configuration 102 | .plans 103 | .iter() 104 | .enumerate() 105 | .map(|(index, plan)| (index, plan.name.clone())) 106 | } 107 | 108 | pub fn plan_by_index(&self, index: usize) -> Option<&PlanName> { 109 | self.plans.get_index(index).map(|(key, _)| key) 110 | } 111 | 112 | pub fn number_of_plans(&self) -> usize { 113 | self.configuration.plans.len() 114 | } 115 | 116 | pub fn apply(&mut self) -> Result { 117 | if let Some(active_plan) = self.active_plan() { 118 | let mut atk = AtkAcpi::new()?; 119 | if let (Some(cpu), Some(gpu)) = (&active_plan.cpu_curve, &active_plan.gpu_curve) { 120 | // plan and fan curve 121 | let cpu = convert_to_curve(FanCurveDevice::Cpu, cpu)?; 122 | let gpu = convert_to_curve(FanCurveDevice::Gpu, gpu)?; 123 | atk.set_power_plan(active_plan.plan.into())?; 124 | atk.set_fan_curve(&cpu)?; 125 | atk.set_fan_curve(&gpu)?; 126 | info!( 127 | "Power plan updated with custom fan curve: {:?}; CPU {}; GPU {}.", 128 | active_plan.plan, 129 | cpu.to_string(), 130 | gpu.to_string(), 131 | ); 132 | } else { 133 | // plan only 134 | atk.set_power_plan(active_plan.plan.into())?; 135 | info!( 136 | "Power plan updated (no custom fan curve): {:?}.", 137 | active_plan.plan 138 | ); 139 | } 140 | Ok(ApplyInfo::Ok) 141 | } else { 142 | warn!("No active plan found (cannot apply plan)."); 143 | Ok(ApplyInfo::NoPlan) 144 | } 145 | } 146 | 147 | pub fn config_file(&self) -> &PathBuf { 148 | &self.config_file 149 | } 150 | 151 | fn require_config_file_path_buf() -> Result { 152 | if let Some(config_file) = Self::config_file_path_buf() { 153 | Ok(config_file) 154 | } else { 155 | Err(AfErr::from( 156 | "Unable to determine configuration directory for current user.", 157 | )) 158 | } 159 | } 160 | 161 | fn config_file_path_buf() -> Option { 162 | if let Some(mut config_dir) = config_dir() { 163 | config_dir.push(CONFIG_FILE_NAME); 164 | Some(config_dir) 165 | } else { 166 | None 167 | } 168 | } 169 | 170 | fn set_configuration(&mut self, configuration: Configuration) { 171 | self.configuration = configuration; 172 | self.update_plan_map(); 173 | } 174 | 175 | fn update_plan_map(&mut self) { 176 | self.plans.clear(); 177 | for plan in &self.configuration.plans { 178 | self.plans.insert(plan.name.clone(), plan.clone()); 179 | } 180 | } 181 | 182 | fn start_logging(&self) -> Result<(), AfErr> { 183 | let disable_logging = self.configuration.disable_logging.unwrap_or(false); 184 | if !disable_logging { 185 | let log_spec: &str = if let Some(log_spec) = &self.configuration.log_spec { 186 | &log_spec 187 | } else { 188 | DEFAULT_LOG_LEVEL 189 | }; 190 | Logger::with_env_or_str(log_spec) 191 | .log_to_file() 192 | .directory(self.log_directory()?) 193 | .rotate( 194 | Criterion::Age(Age::Day), 195 | Naming::Timestamps, 196 | Cleanup::KeepLogFiles(7), 197 | ) 198 | // no background thread. This would just waste resources; for atrofac it's no problem if cleanup blocks. 199 | .cleanup_in_background_thread(false) 200 | .format(detailed_format) 201 | .start() 202 | .map_err(|log_err| AfErr::from(format!("Unable to start logger: {}", log_err)))?; 203 | info!("atrofac started."); 204 | Ok(()) 205 | } else { 206 | Ok(()) 207 | } 208 | } 209 | 210 | fn log_directory(&self) -> Result { 211 | let mut log_dir = data_local_dir().ok_or_else(|| { 212 | AfErr::from("Unable to determine data local dir (used to store log files).") 213 | })?; 214 | log_dir.push(ATROFAC_LOG_DIR); 215 | fs::create_dir_all(&log_dir).map_err(|error| { 216 | AfErr::from(format!( 217 | "Unable to create log directory '{:?}'; \ 218 | error: {}.", 219 | &log_dir, error 220 | )) 221 | })?; 222 | Ok(log_dir) 223 | } 224 | } 225 | 226 | pub enum ApplyInfo { 227 | Ok, 228 | NoPlan, 229 | } 230 | 231 | fn convert_to_curve(device: FanCurveDevice, string: &str) -> Result { 232 | let builder_from_string = FanCurveTableBuilder::from_string(device, string)?; 233 | let is_valid = builder_from_string.is_valid(); 234 | let table = builder_from_string.auto_fix_build(); 235 | if !is_valid { 236 | warn!( 237 | "Fan curve for {:?} might damage your device and has been auto-adjusted to \ 238 | the minimum safe values: {}.", 239 | device, 240 | table.to_string() 241 | ); 242 | } 243 | Ok(table) 244 | } 245 | -------------------------------------------------------------------------------- /library/src/atkacpi.rs: -------------------------------------------------------------------------------- 1 | use crate::{AfErr, DeviceControl}; 2 | use regex::Regex; 3 | use std::convert::TryFrom; 4 | use std::ops::Index; 5 | 6 | const FILE_NAME: &'static str = "\\\\.\\ATKACPI"; 7 | const CONTROL_CODE: u32 = 2237452; 8 | const POWER_PLAN_TEMPLATE: [u8; 16] = [ 9 | 0x44, 0x45, 0x56, 0x53, 0x08, 0x00, 0x00, 0x00, 0x75, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 10 | ]; 11 | const POWER_PLAN_INDEX: usize = 12; 12 | const SET_FAN_CURVE_TEMPLATE: [u8; 28] = [ 13 | 0x44, 0x45, 0x56, 0x53, 0x14, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x11, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 14 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 15 | ]; 16 | const SET_FAN_CURVE_DEVICE_INDEX: usize = 8; 17 | 18 | pub struct AtkAcpi { 19 | device_control: DeviceControl, 20 | } 21 | 22 | impl AtkAcpi { 23 | pub fn new() -> Result { 24 | Ok(Self { 25 | device_control: DeviceControl::new(FILE_NAME)?, 26 | }) 27 | } 28 | 29 | pub fn set_power_plan(&mut self, power_plan: PowerPlan) -> Result<(), AfErr> { 30 | let mut in_buffer: [u8; 16] = POWER_PLAN_TEMPLATE; 31 | in_buffer[POWER_PLAN_INDEX] = power_plan.to_byte(); 32 | self.control(&mut in_buffer) 33 | } 34 | 35 | pub fn set_fan_curve(&mut self, table: &FanCurveTable) -> Result<(), AfErr> { 36 | let mut in_buffer: [u8; 28] = SET_FAN_CURVE_TEMPLATE; 37 | in_buffer[SET_FAN_CURVE_DEVICE_INDEX] = table.device.to_byte(); 38 | in_buffer[12..].copy_from_slice(&table.table); 39 | self.control(&mut in_buffer) 40 | } 41 | 42 | fn control(&mut self, in_buffer: &mut [u8]) -> Result<(), AfErr> { 43 | let mut out_buffer: [u8; 1024] = [0; 1024]; 44 | self.device_control 45 | .control(CONTROL_CODE, in_buffer, &mut out_buffer)?; 46 | Ok(()) 47 | } 48 | } 49 | 50 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] 51 | pub enum PowerPlan { 52 | PerformanceWindows, 53 | TurboManual, 54 | Silent, 55 | } 56 | 57 | impl PowerPlan { 58 | pub(crate) fn to_byte(&self) -> u8 { 59 | match self { 60 | PowerPlan::PerformanceWindows => 0x00, 61 | PowerPlan::TurboManual => 0x01, 62 | PowerPlan::Silent => 0x02, 63 | } 64 | } 65 | } 66 | 67 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] 68 | pub enum FanCurveDevice { 69 | Cpu, 70 | Gpu, 71 | } 72 | 73 | impl FanCurveDevice { 74 | pub(crate) fn to_byte(&self) -> u8 { 75 | match self { 76 | FanCurveDevice::Cpu => 0x24, 77 | FanCurveDevice::Gpu => 0x25, 78 | } 79 | } 80 | } 81 | 82 | pub struct FanCurveTable { 83 | device: FanCurveDevice, 84 | table: [u8; 16], 85 | } 86 | 87 | impl FanCurveTable { 88 | pub fn entry(&self, index: TableIndex) -> TableEntry { 89 | let degrees = self.table[index.0 as usize]; 90 | let fan_percent = self.table[index.0 as usize + 8]; 91 | TableEntry { 92 | degrees, 93 | fan_percent, 94 | } 95 | } 96 | 97 | fn set(&mut self, index: TableIndex, entry: TableEntry) { 98 | self.table[index.0 as usize] = entry.degrees; 99 | self.table[index.0 as usize + 8] = entry.fan_percent; 100 | } 101 | 102 | pub fn is_valid(&self) -> bool { 103 | let mut last_percentage: u8 = 0; 104 | for index in TableIndex::iterator() { 105 | let entry = self.entry(index); 106 | let degrees = entry.degrees(); 107 | let degrees_are_ok = degrees >= index.min_degrees_inclusive() 108 | && degrees <= index.max_degrees_inclusive(); 109 | if !degrees_are_ok { 110 | return false; 111 | } 112 | let percentage = entry.fan_percent(); 113 | let fan_percentage_ok = percentage >= index.min_percentage_inclusive(self.device) 114 | && percentage >= last_percentage; 115 | last_percentage = percentage; 116 | if !fan_percentage_ok { 117 | return false; 118 | } 119 | } 120 | true 121 | } 122 | 123 | pub fn to_string(&self) -> String { 124 | let mut string = String::with_capacity(100); 125 | for (index, table_index) in TableIndex::iterator().enumerate() { 126 | let entry = self.entry(table_index); 127 | string.push_str(&format!("{}c:{}%", entry.degrees(), entry.fan_percent())); 128 | if index != TableIndex::max_ordinal() as usize { 129 | string.push_str(","); 130 | } 131 | } 132 | string 133 | } 134 | 135 | fn auto_fix(&mut self) { 136 | let mut last_percentage: u8 = 0; 137 | for index in TableIndex::iterator() { 138 | let entry = self.entry(index); 139 | 140 | // fix degrees 141 | let mut degrees = entry.degrees(); 142 | if degrees > index.max_degrees_inclusive() { 143 | degrees = index.max_degrees_inclusive(); 144 | } 145 | if degrees < index.min_degrees_inclusive() { 146 | degrees = index.min_degrees_inclusive(); 147 | } 148 | 149 | // fix percentage 150 | let mut percentage = entry.fan_percent(); 151 | if percentage < index.min_percentage_inclusive(self.device) { 152 | percentage = index.min_percentage_inclusive(self.device); 153 | } 154 | if percentage < last_percentage { 155 | percentage = last_percentage; 156 | } 157 | last_percentage = percentage; 158 | 159 | // write back 160 | let new_entry = TableEntry::new(degrees, percentage); 161 | self.set(index, new_entry); 162 | } 163 | } 164 | } 165 | 166 | pub struct FanCurveTableBuilder { 167 | table: FanCurveTable, 168 | } 169 | 170 | impl FanCurveTableBuilder { 171 | pub fn new(device: FanCurveDevice) -> Self { 172 | Self { 173 | table: FanCurveTable { 174 | device, 175 | table: [0; 16], 176 | }, 177 | } 178 | } 179 | 180 | pub fn set(&mut self, index: TableIndex, entry: TableEntry) { 181 | self.table.set(index, entry) 182 | } 183 | 184 | pub fn is_valid(&self) -> bool { 185 | self.table.is_valid() 186 | } 187 | 188 | pub fn auto_fix(&mut self) { 189 | self.table.auto_fix() 190 | } 191 | 192 | pub fn auto_fix_build(self) -> FanCurveTable { 193 | let mut table = self.table; 194 | table.auto_fix(); 195 | table 196 | } 197 | 198 | pub fn from_string( 199 | device: FanCurveDevice, 200 | string: &str, 201 | ) -> Result { 202 | let regex = Regex::new(r"\s*(\d{1,3})c:(\d{1,3})%\s*").unwrap(); 203 | let mut builder = FanCurveTableBuilder::new(device); 204 | 205 | for (index, pattern) in string.split(",").enumerate() { 206 | if index > TableIndex::max_ordinal() as usize { 207 | return Err(format!( 208 | "Too many entries for fan curve table, cannot have more \ 209 | than {} entries.", 210 | TableIndex::max_ordinal() + 1 211 | ) 212 | .into()); 213 | } 214 | let mut captures = regex.captures_iter(pattern); 215 | if let Some(captures) = captures.next() { 216 | let table_index = TableIndex::from_ordinal(u8::try_from(index)?); 217 | if let (Ok(degrees), Ok(percentage), Some(table_index)) = ( 218 | captures.index(1).parse::(), 219 | captures.index(2).parse::(), 220 | table_index, 221 | ) { 222 | builder.set(table_index, TableEntry::new(degrees, percentage)); 223 | continue; 224 | } 225 | } 226 | 227 | return Err(format!( 228 | "Unable to parse '{}': It must look like this: \ 229 | c:%, examples: 35c:45% or 55c:75% (while degrees must be \ 230 | <=255 and percent within 0-100).", 231 | pattern 232 | ) 233 | .into()); 234 | } 235 | Ok(builder) 236 | } 237 | } 238 | 239 | pub struct TableEntry { 240 | degrees: u8, 241 | fan_percent: u8, 242 | } 243 | 244 | impl TableEntry { 245 | pub fn new(degrees: u8, fan_percent: u8) -> Self { 246 | Self { 247 | degrees, 248 | fan_percent, 249 | } 250 | } 251 | 252 | pub fn degrees(&self) -> u8 { 253 | self.degrees 254 | } 255 | 256 | pub fn fan_percent(&self) -> u8 { 257 | self.fan_percent 258 | } 259 | } 260 | 261 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] 262 | pub struct TableIndex(u8); 263 | 264 | impl TableIndex { 265 | pub const fn max_ordinal() -> u8 { 266 | 7 267 | } 268 | 269 | pub fn iterator() -> impl Iterator { 270 | (0..Self::max_ordinal() + 1).map(|ordinal| TableIndex(ordinal)) 271 | } 272 | 273 | pub fn from_ordinal(ordinal: u8) -> Option { 274 | if ordinal <= Self::max_ordinal() { 275 | Some(TableIndex(ordinal)) 276 | } else { 277 | None 278 | } 279 | } 280 | 281 | pub fn min_degrees_inclusive(&self) -> u8 { 282 | (self.0 * 10) + 30 283 | } 284 | 285 | pub fn max_degrees_inclusive(&self) -> u8 { 286 | (self.0 * 10) + 39 287 | } 288 | 289 | pub fn min_percentage_inclusive(&self, device: FanCurveDevice) -> u8 { 290 | match self.0 { 291 | 0 | 1 | 2 | 3 => 0, 292 | 4 => match device { 293 | FanCurveDevice::Cpu => 31, 294 | FanCurveDevice::Gpu => 34, 295 | }, 296 | 5 => match device { 297 | FanCurveDevice::Cpu => 49, 298 | FanCurveDevice::Gpu => 51, 299 | }, 300 | 6 | 7 => match device { 301 | FanCurveDevice::Cpu => 56, 302 | FanCurveDevice::Gpu => 61, 303 | }, 304 | _ => panic!("No such table index"), 305 | } 306 | } 307 | } 308 | 309 | #[cfg(test)] 310 | mod tests { 311 | use crate::{FanCurveDevice, FanCurveTableBuilder}; 312 | 313 | #[test] 314 | pub fn minimum_cpu_table_temp_given() { 315 | let minimum_table_string = "39c:0%,49c:0%,59c:0%,69c:0%,79c:31%,89c:49%,99c:56%,109c:56%"; 316 | let table = 317 | FanCurveTableBuilder::from_string(FanCurveDevice::Cpu, minimum_table_string).unwrap(); 318 | assert_eq!(true, table.is_valid()); 319 | // auto-fix should do nothing 320 | let table = table.auto_fix_build(); 321 | // should return the same 322 | assert_eq!(table.to_string(), minimum_table_string); 323 | } 324 | 325 | #[test] 326 | pub fn minimum_gpu_table_temp_given() { 327 | let minimum_table_string = "39c:0%,49c:0%,59c:0%,69c:0%,79c:34%,89c:51%,99c:61%,109c:61%"; 328 | let table = 329 | FanCurveTableBuilder::from_string(FanCurveDevice::Gpu, minimum_table_string).unwrap(); 330 | assert_eq!(true, table.is_valid()); 331 | // auto-fix should do nothing 332 | let table = table.auto_fix_build(); 333 | // should return the same 334 | assert_eq!(table.to_string(), minimum_table_string); 335 | } 336 | 337 | #[test] 338 | pub fn minimum_cpu_table() { 339 | let table_string = "150c:0%,150c:0%,150c:0%,150c:0%,150c:0%,150c:0%,150c:0%,150c:0%"; 340 | let minimum_allowed = "39c:0%,49c:0%,59c:0%,69c:0%,79c:31%,89c:49%,99c:56%,109c:56%"; 341 | let table = FanCurveTableBuilder::from_string(FanCurveDevice::Cpu, table_string).unwrap(); 342 | assert_eq!(false, table.is_valid()); 343 | // should fix the table to minimum values 344 | let table = table.auto_fix_build(); 345 | // should return the same 346 | assert_eq!(table.to_string(), minimum_allowed); 347 | } 348 | 349 | #[test] 350 | pub fn minimum_gpu_table() { 351 | let table_string = "150c:0%,150c:0%,150c:0%,150c:0%,150c:0%,150c:0%,150c:0%,150c:0%"; 352 | let minimum_allowed = "39c:0%,49c:0%,59c:0%,69c:0%,79c:34%,89c:51%,99c:61%,109c:61%"; 353 | let table = FanCurveTableBuilder::from_string(FanCurveDevice::Gpu, table_string).unwrap(); 354 | assert_eq!(false, table.is_valid()); 355 | // should fix the table to minimum values 356 | let table = table.auto_fix_build(); 357 | // should return the same 358 | assert_eq!(table.to_string(), minimum_allowed); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Dual licensed, MIT or Apache 2.0. 2 | 3 | ``` 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | ``` 28 | 29 | ``` 30 | Apache License 31 | Version 2.0, January 2004 32 | http://www.apache.org/licenses/ 33 | 34 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 35 | 36 | 1. Definitions. 37 | 38 | "License" shall mean the terms and conditions for use, reproduction, 39 | and distribution as defined by Sections 1 through 9 of this document. 40 | 41 | "Licensor" shall mean the copyright owner or entity authorized by 42 | the copyright owner that is granting the License. 43 | 44 | "Legal Entity" shall mean the union of the acting entity and all 45 | other entities that control, are controlled by, or are under common 46 | control with that entity. For the purposes of this definition, 47 | "control" means (i) the power, direct or indirect, to cause the 48 | direction or management of such entity, whether by contract or 49 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 50 | outstanding shares, or (iii) beneficial ownership of such entity. 51 | 52 | "You" (or "Your") shall mean an individual or Legal Entity 53 | exercising permissions granted by this License. 54 | 55 | "Source" form shall mean the preferred form for making modifications, 56 | including but not limited to software source code, documentation 57 | source, and configuration files. 58 | 59 | "Object" form shall mean any form resulting from mechanical 60 | transformation or translation of a Source form, including but 61 | not limited to compiled object code, generated documentation, 62 | and conversions to other media types. 63 | 64 | "Work" shall mean the work of authorship, whether in Source or 65 | Object form, made available under the License, as indicated by a 66 | copyright notice that is included in or attached to the work 67 | (an example is provided in the Appendix below). 68 | 69 | "Derivative Works" shall mean any work, whether in Source or Object 70 | form, that is based on (or derived from) the Work and for which the 71 | editorial revisions, annotations, elaborations, or other modifications 72 | represent, as a whole, an original work of authorship. For the purposes 73 | of this License, Derivative Works shall not include works that remain 74 | separable from, or merely link (or bind by name) to the interfaces of, 75 | the Work and Derivative Works thereof. 76 | 77 | "Contribution" shall mean any work of authorship, including 78 | the original version of the Work and any modifications or additions 79 | to that Work or Derivative Works thereof, that is intentionally 80 | submitted to Licensor for inclusion in the Work by the copyright owner 81 | or by an individual or Legal Entity authorized to submit on behalf of 82 | the copyright owner. For the purposes of this definition, "submitted" 83 | means any form of electronic, verbal, or written communication sent 84 | to the Licensor or its representatives, including but not limited to 85 | communication on electronic mailing lists, source code control systems, 86 | and issue tracking systems that are managed by, or on behalf of, the 87 | Licensor for the purpose of discussing and improving the Work, but 88 | excluding communication that is conspicuously marked or otherwise 89 | designated in writing by the copyright owner as "Not a Contribution." 90 | 91 | "Contributor" shall mean Licensor and any individual or Legal Entity 92 | on behalf of whom a Contribution has been received by Licensor and 93 | subsequently incorporated within the Work. 94 | 95 | 2. Grant of Copyright License. Subject to the terms and conditions of 96 | this License, each Contributor hereby grants to You a perpetual, 97 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 98 | copyright license to reproduce, prepare Derivative Works of, 99 | publicly display, publicly perform, sublicense, and distribute the 100 | Work and such Derivative Works in Source or Object form. 101 | 102 | 3. Grant of Patent License. Subject to the terms and conditions of 103 | this License, each Contributor hereby grants to You a perpetual, 104 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 105 | (except as stated in this section) patent license to make, have made, 106 | use, offer to sell, sell, import, and otherwise transfer the Work, 107 | where such license applies only to those patent claims licensable 108 | by such Contributor that are necessarily infringed by their 109 | Contribution(s) alone or by combination of their Contribution(s) 110 | with the Work to which such Contribution(s) was submitted. If You 111 | institute patent litigation against any entity (including a 112 | cross-claim or counterclaim in a lawsuit) alleging that the Work 113 | or a Contribution incorporated within the Work constitutes direct 114 | or contributory patent infringement, then any patent licenses 115 | granted to You under this License for that Work shall terminate 116 | as of the date such litigation is filed. 117 | 118 | 4. Redistribution. You may reproduce and distribute copies of the 119 | Work or Derivative Works thereof in any medium, with or without 120 | modifications, and in Source or Object form, provided that You 121 | meet the following conditions: 122 | 123 | (a) You must give any other recipients of the Work or 124 | Derivative Works a copy of this License; and 125 | 126 | (b) You must cause any modified files to carry prominent notices 127 | stating that You changed the files; and 128 | 129 | (c) You must retain, in the Source form of any Derivative Works 130 | that You distribute, all copyright, patent, trademark, and 131 | attribution notices from the Source form of the Work, 132 | excluding those notices that do not pertain to any part of 133 | the Derivative Works; and 134 | 135 | (d) If the Work includes a "NOTICE" text file as part of its 136 | distribution, then any Derivative Works that You distribute must 137 | include a readable copy of the attribution notices contained 138 | within such NOTICE file, excluding those notices that do not 139 | pertain to any part of the Derivative Works, in at least one 140 | of the following places: within a NOTICE text file distributed 141 | as part of the Derivative Works; within the Source form or 142 | documentation, if provided along with the Derivative Works; or, 143 | within a display generated by the Derivative Works, if and 144 | wherever such third-party notices normally appear. The contents 145 | of the NOTICE file are for informational purposes only and 146 | do not modify the License. You may add Your own attribution 147 | notices within Derivative Works that You distribute, alongside 148 | or as an addendum to the NOTICE text from the Work, provided 149 | that such additional attribution notices cannot be construed 150 | as modifying the License. 151 | 152 | You may add Your own copyright statement to Your modifications and 153 | may provide additional or different license terms and conditions 154 | for use, reproduction, or distribution of Your modifications, or 155 | for any such Derivative Works as a whole, provided Your use, 156 | reproduction, and distribution of the Work otherwise complies with 157 | the conditions stated in this License. 158 | 159 | 5. Submission of Contributions. Unless You explicitly state otherwise, 160 | any Contribution intentionally submitted for inclusion in the Work 161 | by You to the Licensor shall be under the terms and conditions of 162 | this License, without any additional terms or conditions. 163 | Notwithstanding the above, nothing herein shall supersede or modify 164 | the terms of any separate license agreement you may have executed 165 | with Licensor regarding such Contributions. 166 | 167 | 6. Trademarks. This License does not grant permission to use the trade 168 | names, trademarks, service marks, or product names of the Licensor, 169 | except as required for reasonable and customary use in describing the 170 | origin of the Work and reproducing the content of the NOTICE file. 171 | 172 | 7. Disclaimer of Warranty. Unless required by applicable law or 173 | agreed to in writing, Licensor provides the Work (and each 174 | Contributor provides its Contributions) on an "AS IS" BASIS, 175 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 176 | implied, including, without limitation, any warranties or conditions 177 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 178 | PARTICULAR PURPOSE. You are solely responsible for determining the 179 | appropriateness of using or redistributing the Work and assume any 180 | risks associated with Your exercise of permissions under this License. 181 | 182 | 8. Limitation of Liability. In no event and under no legal theory, 183 | whether in tort (including negligence), contract, or otherwise, 184 | unless required by applicable law (such as deliberate and grossly 185 | negligent acts) or agreed to in writing, shall any Contributor be 186 | liable to You for damages, including any direct, indirect, special, 187 | incidental, or consequential damages of any character arising as a 188 | result of this License or out of the use or inability to use the 189 | Work (including but not limited to damages for loss of goodwill, 190 | work stoppage, computer failure or malfunction, or any and all 191 | other commercial damages or losses), even if such Contributor 192 | has been advised of the possibility of such damages. 193 | 194 | 9. Accepting Warranty or Additional Liability. While redistributing 195 | the Work or Derivative Works thereof, You may choose to offer, 196 | and charge a fee for, acceptance of support, warranty, indemnity, 197 | or other liability obligations and/or rights consistent with this 198 | License. However, in accepting such obligations, You may act only 199 | on Your own behalf and on Your sole responsibility, not on behalf 200 | of any other Contributor, and only if You agree to indemnify, 201 | defend, and hold each Contributor harmless for any liability 202 | incurred by, or claims asserted against, such Contributor by reason 203 | of your accepting any such warranty or additional liability. 204 | 205 | END OF TERMS AND CONDITIONS 206 | ``` 207 | 208 | systray-rs is BSD licensed. 209 | 210 | ``` 211 | Copyright (c) 2016, Kyle Machulis 212 | All rights reserved. 213 | 214 | Redistribution and use in source and binary forms, with or without 215 | modification, are permitted provided that the following conditions are met: 216 | 217 | * Redistributions of source code must retain the above copyright notice, this 218 | list of conditions and the following disclaimer. 219 | 220 | * Redistributions in binary form must reproduce the above copyright notice, 221 | this list of conditions and the following disclaimer in the documentation 222 | and/or other materials provided with the distribution. 223 | 224 | * Neither the name of the project nor the names of its 225 | contributors may be used to endorse or promote products derived from 226 | this software without specific prior written permission. 227 | 228 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 229 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 230 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 231 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 232 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 233 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 234 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 235 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 236 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 237 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 238 | ``` -------------------------------------------------------------------------------- /libgui/src/systray/win32.rs: -------------------------------------------------------------------------------- 1 | use crate::systray::{make_callback, Callback, SystrayAction, SystrayError, SystrayEvent}; 2 | use encoding::all::UTF_16LE; 3 | use encoding::{EncoderTrap, Encoding}; 4 | use log::debug; 5 | use std; 6 | use std::cell::{Cell, RefCell}; 7 | use std::collections::HashMap; 8 | use std::ffi::OsStr; 9 | use std::os::windows::ffi::OsStrExt; 10 | use std::ptr::{null, null_mut}; 11 | use std::slice; 12 | use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError}; 13 | use std::thread; 14 | use winapi; 15 | use winapi::ctypes::c_int; 16 | use winapi::ctypes::c_ulong; 17 | use winapi::ctypes::c_ushort; 18 | use winapi::shared::basetsd::{UINT_PTR, ULONG_PTR}; 19 | use winapi::shared::guiddef::GUID; 20 | use winapi::shared::minwindef::{BOOL, UINT}; 21 | use winapi::shared::minwindef::{DWORD, HINSTANCE, LPARAM, LRESULT, PBYTE, TRUE, WPARAM}; 22 | use winapi::shared::windef::POINT; 23 | use winapi::shared::windef::RECT; 24 | use winapi::shared::windef::{HBITMAP, HBRUSH, HICON, HMENU, HWND}; 25 | use winapi::um::errhandlingapi::GetLastError; 26 | use winapi::um::libloaderapi::GetModuleHandleA; 27 | use winapi::um::winnt::CHAR; 28 | use winapi::um::winnt::LPCWSTR; 29 | use winapi::um::winnt::WCHAR; 30 | use winapi::um::winuser::MSG; 31 | use winapi::um::winuser::{ 32 | CheckMenuItem, CreateIconFromResourceEx, CreatePopupMenu, CreateWindowExW, DefWindowProcW, 33 | DeleteMenu, DispatchMessageW, EnableMenuItem, GetCursorPos, GetMessageW, InsertMenuItemW, 34 | KillTimer, LoadCursorW, LoadIconW, LoadImageW, LookupIconIdFromDirectoryEx, PostMessageW, 35 | PostQuitMessage, RegisterClassW, SetForegroundWindow, SetTimer, TranslateMessage, 36 | CW_USEDEFAULT, IDI_APPLICATION, IMAGE_ICON, LR_DEFAULTCOLOR, LR_LOADFROMFILE, WM_DESTROY, 37 | WM_LBUTTONUP, WM_MENUCOMMAND, WM_POWERBROADCAST, WM_QUIT, WM_RBUTTONUP, WM_TIMER, 38 | WM_UNINITMENUPOPUP, WM_USER, WNDCLASSW, WS_OVERLAPPEDWINDOW, 39 | }; 40 | use winapi::um::winuser::{LPMENUITEMINFOA, LPMENUITEMINFOW, MENUITEMINFOW}; 41 | 42 | const TIMER1: UINT_PTR = 323523; 43 | 44 | // Until winapi hits 0.3 on crates.io, add these so we can publish a crate. 45 | macro_rules! UNION { 46 | ($base:ident, $field:ident, $variant:ident, $variantmut:ident, $fieldtype:ty) => { 47 | impl $base { 48 | #[inline] 49 | pub unsafe fn $variant(&self) -> &$fieldtype { 50 | ::std::mem::transmute(&self.$field) 51 | } 52 | #[inline] 53 | pub unsafe fn $variantmut(&mut self) -> &mut $fieldtype { 54 | ::std::mem::transmute(&mut self.$field) 55 | } 56 | } 57 | }; 58 | } 59 | 60 | macro_rules! STRUCT { 61 | {$(#[$attrs:meta])* nodebug struct $name:ident { $($field:ident: $ftype:ty,)+ }} => { 62 | #[repr(C)] $(#[$attrs])* 63 | pub struct $name { 64 | $(pub $field: $ftype,)+ 65 | } 66 | impl Copy for $name {} 67 | impl Clone for $name { fn clone(&self) -> $name { *self } } 68 | }; 69 | {$(#[$attrs:meta])* struct $name:ident { $($field:ident: $ftype:ty,)+ }} => { 70 | #[repr(C)] $(#[$attrs])* 71 | pub struct $name { 72 | $(pub $field: $ftype,)+ 73 | } 74 | impl Copy for $name {} 75 | impl Clone for $name { fn clone(&self) -> $name { *self } } 76 | }; 77 | } 78 | 79 | extern "system" { 80 | pub fn GetMenuInfo(hMenu: HMENU, lpcmi: LPMENUINFO) -> BOOL; 81 | pub fn GetMenuItemCount(hMenu: HMENU) -> c_int; 82 | pub fn GetMenuItemID(hMenu: HMENU, nPos: c_int) -> UINT; 83 | pub fn GetMenuItemInfoA( 84 | hMenu: HMENU, 85 | uItem: UINT, 86 | fByPosition: BOOL, 87 | lpmii: LPMENUITEMINFOA, 88 | ) -> BOOL; 89 | pub fn GetMenuItemInfoW( 90 | hMenu: HMENU, 91 | uItem: UINT, 92 | fByPosition: BOOL, 93 | lpmii: LPMENUITEMINFOW, 94 | ) -> BOOL; 95 | pub fn SetMenuInfo(hMenu: HMENU, lpcmi: LPCMENUINFO) -> BOOL; 96 | pub fn TrackPopupMenu( 97 | hMenu: HMENU, 98 | uFlags: UINT, 99 | x: c_int, 100 | y: c_int, 101 | nReserved: c_int, 102 | hWnd: HWND, 103 | prcRect: *const RECT, 104 | ); 105 | pub fn TrackPopupMenuEx( 106 | hMenu: HMENU, 107 | fuFlags: UINT, 108 | x: c_int, 109 | y: c_int, 110 | hWnd: HWND, 111 | lptpm: LPTPMPARAMS, 112 | ); 113 | pub fn Shell_NotifyIconA(dwMessage: DWORD, lpData: PNOTIFYICONDATAA) -> BOOL; 114 | pub fn Shell_NotifyIconW(dwMessage: DWORD, lpData: PNOTIFYICONDATAW) -> BOOL; 115 | } 116 | 117 | pub const NIM_ADD: DWORD = 0x00000000; 118 | pub const NIM_MODIFY: DWORD = 0x00000001; 119 | pub const NIM_DELETE: DWORD = 0x00000002; 120 | pub const NIM_SETFOCUS: DWORD = 0x00000003; 121 | pub const NIM_SETVERSION: DWORD = 0x00000004; 122 | pub const NIF_MESSAGE: UINT = 0x00000001; 123 | pub const NIF_ICON: UINT = 0x00000002; 124 | pub const NIF_TIP: UINT = 0x00000004; 125 | pub const NIF_STATE: UINT = 0x00000008; 126 | pub const NIF_INFO: UINT = 0x00000010; 127 | pub const NIF_GUID: UINT = 0x00000020; 128 | pub const NIF_REALTIME: UINT = 0x00000040; 129 | pub const NIF_SHOWTIP: UINT = 0x00000080; 130 | pub const NOTIFYICON_VERSION: UINT = 3; 131 | pub const NOTIFYICON_VERSION_4: UINT = 4; 132 | 133 | pub const MF_BYCOMMAND: UINT = 0x00000000; 134 | pub const MF_BYPOSITION: UINT = 0x00000400; 135 | pub const MF_UNCHECKED: UINT = 0x00000000; 136 | pub const MF_CHECKED: UINT = 0x00000008; 137 | pub const MF_ENABLED: UINT = 0x00000000; 138 | pub const MF_GRAYED: UINT = 0x00000001; 139 | pub const MF_DISABLED: UINT = 0x00000002; 140 | 141 | STRUCT! {nodebug struct NOTIFYICONDATAA { 142 | cbSize: DWORD, 143 | hWnd: HWND, 144 | uID: UINT, 145 | uFlags: UINT, 146 | uCallbackMessage: UINT, 147 | hIcon: HICON, 148 | szTip: [CHAR; 128], 149 | dwState: DWORD, 150 | dwStateMask: DWORD, 151 | szInfo: [CHAR; 256], 152 | uTimeout: UINT, 153 | szInfoTitle: [CHAR; 64], 154 | dwInfoFlags: DWORD, 155 | guidItem: GUID, 156 | hBalloonIcon: HICON, 157 | }} 158 | UNION!(NOTIFYICONDATAA, uTimeout, uTimeout, uTimeout_mut, UINT); 159 | UNION!(NOTIFYICONDATAA, uTimeout, uVersion, uVersion_mut, UINT); 160 | pub type PNOTIFYICONDATAA = *mut NOTIFYICONDATAA; 161 | 162 | STRUCT! {nodebug struct NOTIFYICONDATAW { 163 | cbSize: DWORD, 164 | hWnd: HWND, 165 | uID: UINT, 166 | uFlags: UINT, 167 | uCallbackMessage: UINT, 168 | hIcon: HICON, 169 | szTip: [WCHAR; 128], 170 | dwState: DWORD, 171 | dwStateMask: DWORD, 172 | szInfo: [WCHAR; 256], 173 | uTimeout: UINT, 174 | szInfoTitle: [WCHAR; 64], 175 | dwInfoFlags: DWORD, 176 | guidItem: GUID, 177 | hBalloonIcon: HICON, 178 | }} 179 | UNION!(NOTIFYICONDATAW, uTimeout, uTimeout, uTimeout_mut, UINT); 180 | UNION!(NOTIFYICONDATAW, uTimeout, uVersion, uVersion_mut, UINT); // used with NIM_SETVERSION, values 0, 3 and 4 181 | 182 | pub type PNOTIFYICONDATAW = *mut NOTIFYICONDATAW; 183 | pub const MIIM_BITMAP: UINT = 0x00000080; 184 | pub const MIIM_CHECKMARKS: UINT = 0x00000008; 185 | pub const MIIM_DATA: UINT = 0x00000020; 186 | pub const MIIM_FTYPE: UINT = 0x00000100; 187 | pub const MIIM_ID: UINT = 0x00000002; 188 | pub const MIIM_STATE: UINT = 0x00000001; 189 | pub const MIIM_STRING: UINT = 0x00000040; 190 | pub const MIIM_SUBMENU: UINT = 0x00000004; 191 | pub const MIIM_TYPE: UINT = 0x00000010; 192 | 193 | pub const MFT_BITMAP: UINT = 0x00000004; 194 | pub const MFT_MENUBARBREAK: UINT = 0x00000020; 195 | pub const MFT_MENUBREAK: UINT = 0x00000040; 196 | pub const MFT_OWNERDRAW: UINT = 0x00000100; 197 | pub const MFT_RADIOCHECK: UINT = 0x00000200; 198 | pub const MFT_RIGHTJUSTIFY: UINT = 0x00004000; 199 | pub const MFT_RIGHTORDER: UINT = 0x00002000; 200 | pub const MFT_SEPARATOR: UINT = 0x00000800; 201 | pub const MFT_STRING: UINT = 0x00000000; 202 | 203 | pub const MFS_CHECKED: UINT = 0x00000008; 204 | pub const MFS_DEFAULT: UINT = 0x00001000; 205 | pub const MFS_DISABLED: UINT = 0x00000003; 206 | pub const MFS_ENABLED: UINT = 0x00000000; 207 | pub const MFS_GRAYED: UINT = 0x00000003; 208 | pub const MFS_HILITE: UINT = 0x00000080; 209 | pub const MFS_UNCHECKED: UINT = 0x00000000; 210 | pub const MFS_UNHILITE: UINT = 0x00000000; 211 | 212 | //pub const HBMMENU_CALLBACK: HBITMAP = -1 as HBITMAP; 213 | pub const HBMMENU_MBAR_CLOSE: HBITMAP = 5 as HBITMAP; 214 | pub const HBMMENU_MBAR_CLOSE_D: HBITMAP = 6 as HBITMAP; 215 | pub const HBMMENU_MBAR_MINIMIZE: HBITMAP = 3 as HBITMAP; 216 | pub const HBMMENU_MBAR_MINIMIZE_D: HBITMAP = 7 as HBITMAP; 217 | pub const HBMMENU_MBAR_RESTORE: HBITMAP = 2 as HBITMAP; 218 | pub const HBMMENU_POPUP_CLOSE: HBITMAP = 8 as HBITMAP; 219 | pub const HBMMENU_POPUP_MAXIMIZE: HBITMAP = 10 as HBITMAP; 220 | pub const HBMMENU_POPUP_MINIMIZE: HBITMAP = 11 as HBITMAP; 221 | pub const HBMMENU_POPUP_RESTORE: HBITMAP = 9 as HBITMAP; 222 | pub const HBMMENU_SYSTEM: HBITMAP = 1 as HBITMAP; 223 | 224 | pub const MIM_MAXHEIGHT: UINT = 0x00000001; 225 | pub const MIM_BACKGROUND: UINT = 0x00000002; 226 | pub const MIM_HELPID: UINT = 0x00000004; 227 | pub const MIM_MENUDATA: UINT = 0x00000008; 228 | pub const MIM_STYLE: UINT = 0x00000010; 229 | pub const MIM_APPLYTOSUBMENUS: UINT = 0x80000000; 230 | 231 | pub const MNS_CHECKORBMP: UINT = 0x04000000; 232 | pub const MNS_NOTIFYBYPOS: UINT = 0x08000000; 233 | pub const MNS_AUTODISMISS: UINT = 0x10000000; 234 | pub const MNS_DRAGDROP: UINT = 0x20000000; 235 | pub const MNS_MODELESS: UINT = 0x40000000; 236 | pub const MNS_NOCHECK: UINT = 0x80000000; 237 | 238 | STRUCT! {struct MENUINFO { 239 | cbSize: DWORD, 240 | fMask: DWORD, 241 | dwStyle: DWORD, 242 | cyMax: UINT, 243 | hbrBack: HBRUSH, 244 | dwContextHelpID: DWORD, 245 | dwMenuData: ULONG_PTR, 246 | }} 247 | pub type LPMENUINFO = *mut MENUINFO; 248 | pub type LPCMENUINFO = *const MENUINFO; 249 | 250 | pub const TPM_LEFTALIGN: UINT = 0x0000; 251 | pub const TPM_CENTERALIGN: UINT = 0x0004; 252 | pub const TPM_RIGHTALIGN: UINT = 0x0008; 253 | pub const TPM_TOPALIGN: UINT = 0x0000; 254 | pub const TPM_VCENTERALIGN: UINT = 0x0010; 255 | pub const TPM_BOTTOMALIGN: UINT = 0x0020; 256 | pub const TPM_NONOTIFY: UINT = 0x0080; 257 | pub const TPM_RETURNCMD: UINT = 0x0100; 258 | pub const TPM_LEFTBUTTON: UINT = 0x0000; 259 | pub const TPM_RIGHTBUTTON: UINT = 0x0002; 260 | pub const TPM_HORNEGANIMATION: UINT = 0x0800; 261 | pub const TPM_HORPOSANIMATION: UINT = 0x0400; 262 | pub const TPM_NOANIMATION: UINT = 0x4000; 263 | pub const TPM_VERNEGANIMATION: UINT = 0x2000; 264 | pub const TPM_VERPOSANIMATION: UINT = 0x1000; 265 | 266 | STRUCT! {struct TPMPARAMS { 267 | cbSize: UINT, 268 | rcExclude: RECT, 269 | }} 270 | 271 | pub type LPTPMPARAMS = *const TPMPARAMS; 272 | 273 | pub enum MenuEnableFlag { 274 | Enabled, 275 | Disabled, 276 | Grayed, 277 | } 278 | 279 | fn to_wstring(str: &str) -> Vec { 280 | OsStr::new(str) 281 | .encode_wide() 282 | .chain(Some(0).into_iter()) 283 | .collect::>() 284 | } 285 | 286 | // Got this idea from glutin. Yay open source! Boo stupid winproc! Even more boo 287 | // doing SetLongPtr tho. 288 | thread_local!(static WININFO_STASH: RefCell> = RefCell::new(None)); 289 | 290 | #[derive(Clone)] 291 | struct WindowInfo { 292 | pub hwnd: HWND, 293 | pub hinstance: HINSTANCE, 294 | pub hmenu: HMENU, 295 | } 296 | 297 | unsafe impl Send for WindowInfo {} 298 | unsafe impl Sync for WindowInfo {} 299 | 300 | #[derive(Clone)] 301 | struct WindowsLoopData { 302 | pub info: WindowInfo, 303 | pub tx: Sender, 304 | } 305 | 306 | unsafe fn get_win_os_error(msg: &str) -> SystrayError { 307 | SystrayError::OsError(format!("{}: {}", &msg, GetLastError())) 308 | } 309 | 310 | unsafe extern "system" fn window_proc( 311 | h_wnd: HWND, 312 | msg: UINT, 313 | w_param: WPARAM, 314 | l_param: LPARAM, 315 | ) -> LRESULT { 316 | if msg == WM_MENUCOMMAND { 317 | WININFO_STASH.with(|stash| { 318 | let stash = stash.borrow(); 319 | let stash = stash.as_ref(); 320 | if let Some(stash) = stash { 321 | let menu_id = GetMenuItemID(stash.info.hmenu, w_param as i32) as i32; 322 | if menu_id != -1 { 323 | stash 324 | .tx 325 | .send(SystrayEvent { 326 | action: SystrayAction::SelectItem, 327 | menu_index: menu_id as u32, 328 | }) 329 | .ok(); 330 | } 331 | } 332 | }); 333 | } 334 | 335 | if msg == WM_TIMER { 336 | if w_param == TIMER1 { 337 | WININFO_STASH.with(|stash| { 338 | let stash = stash.borrow(); 339 | let stash = stash.as_ref(); 340 | if let Some(stash) = stash { 341 | stash 342 | .tx 343 | .send(SystrayEvent { 344 | action: SystrayAction::Timer, 345 | menu_index: 0, 346 | }) 347 | .ok(); 348 | } 349 | }); 350 | } 351 | } 352 | 353 | if msg == WM_POWERBROADCAST { 354 | if w_param == winapi::um::winuser::PBT_APMRESUMEAUTOMATIC { 355 | WININFO_STASH.with(|stash| { 356 | let stash = stash.borrow(); 357 | let stash = stash.as_ref(); 358 | if let Some(stash) = stash { 359 | stash 360 | .tx 361 | .send(SystrayEvent { 362 | action: SystrayAction::ApmResume, 363 | menu_index: 0, 364 | }) 365 | .ok(); 366 | } 367 | }); 368 | } 369 | } 370 | 371 | if msg == WM_UNINITMENUPOPUP { 372 | WININFO_STASH.with(|stash| { 373 | let stash = stash.borrow(); 374 | let stash = stash.as_ref(); 375 | if let Some(stash) = stash { 376 | stash 377 | .tx 378 | .send(SystrayEvent { 379 | action: SystrayAction::HideMenu, 380 | menu_index: 0, 381 | }) 382 | .ok(); 383 | } 384 | }); 385 | } 386 | 387 | if msg == WM_USER + 1 { 388 | if l_param as UINT == WM_LBUTTONUP || l_param as UINT == WM_RBUTTONUP { 389 | let mut p = POINT { x: 0, y: 0 }; 390 | if GetCursorPos(&mut p as *mut POINT) == 0 { 391 | return 1; 392 | } 393 | SetForegroundWindow(h_wnd); 394 | WININFO_STASH.with(|stash| { 395 | let stash = stash.borrow(); 396 | let stash = stash.as_ref(); 397 | if let Some(stash) = stash { 398 | stash 399 | .tx 400 | .send(SystrayEvent { 401 | action: SystrayAction::DisplayMenu, 402 | menu_index: 0, 403 | }) 404 | .ok(); 405 | TrackPopupMenu( 406 | stash.info.hmenu, 407 | 0, 408 | p.x, 409 | p.y, 410 | (TPM_BOTTOMALIGN | TPM_LEFTALIGN) as i32, 411 | h_wnd, 412 | std::ptr::null_mut(), 413 | ); 414 | } 415 | }); 416 | } 417 | } 418 | if msg == WM_DESTROY { 419 | WININFO_STASH.with(|stash| { 420 | let stash = stash.borrow(); 421 | let stash = stash.as_ref(); 422 | if let Some(stash) = stash { 423 | stash 424 | .tx 425 | .send(SystrayEvent { 426 | action: SystrayAction::Quit, 427 | menu_index: 0, 428 | }) 429 | .ok(); 430 | } 431 | }); 432 | PostQuitMessage(0); 433 | } 434 | return DefWindowProcW(h_wnd, msg, w_param, l_param); 435 | } 436 | 437 | fn get_nid_struct(hwnd: &HWND) -> NOTIFYICONDATAW { 438 | NOTIFYICONDATAW { 439 | cbSize: std::mem::size_of::() as DWORD, 440 | hWnd: *hwnd, 441 | uID: 0x1 as UINT, 442 | uFlags: 0 as UINT, 443 | uCallbackMessage: 0 as UINT, 444 | hIcon: 0 as HICON, 445 | szTip: [0 as u16; 128], 446 | dwState: 0 as DWORD, 447 | dwStateMask: 0 as DWORD, 448 | szInfo: [0 as u16; 256], 449 | uTimeout: 0 as UINT, 450 | szInfoTitle: [0 as u16; 64], 451 | dwInfoFlags: 0 as UINT, 452 | guidItem: GUID { 453 | Data1: 0 as c_ulong, 454 | Data2: 0 as c_ushort, 455 | Data3: 0 as c_ushort, 456 | Data4: [0; 8], 457 | }, 458 | hBalloonIcon: 0 as HICON, 459 | } 460 | } 461 | 462 | fn get_menu_item_struct() -> MENUITEMINFOW { 463 | MENUITEMINFOW { 464 | cbSize: std::mem::size_of::() as UINT, 465 | fMask: 0 as UINT, 466 | fType: 0 as UINT, 467 | fState: 0 as UINT, 468 | wID: 0 as UINT, 469 | hSubMenu: 0 as HMENU, 470 | hbmpChecked: 0 as HBITMAP, 471 | hbmpUnchecked: 0 as HBITMAP, 472 | dwItemData: 0 as ULONG_PTR, 473 | dwTypeData: std::ptr::null_mut(), 474 | cch: 0 as u32, 475 | hbmpItem: 0 as HBITMAP, 476 | } 477 | } 478 | 479 | unsafe fn init_window() -> Result { 480 | let class_name = to_wstring("my_window"); 481 | let hinstance: HINSTANCE = GetModuleHandleA(std::ptr::null_mut()); 482 | let wnd = WNDCLASSW { 483 | style: 0, 484 | lpfnWndProc: Some(window_proc), 485 | cbClsExtra: 0, 486 | cbWndExtra: 0, 487 | hInstance: 0 as HINSTANCE, 488 | hIcon: LoadIconW(0 as HINSTANCE, IDI_APPLICATION), 489 | hCursor: LoadCursorW(0 as HINSTANCE, IDI_APPLICATION), 490 | hbrBackground: 16 as HBRUSH, 491 | lpszMenuName: 0 as LPCWSTR, 492 | lpszClassName: class_name.as_ptr(), 493 | }; 494 | if RegisterClassW(&wnd) == 0 { 495 | return Err(get_win_os_error("Error creating window class")); 496 | } 497 | let hwnd = CreateWindowExW( 498 | 0, 499 | class_name.as_ptr(), 500 | to_wstring("rust_systray_window").as_ptr(), 501 | WS_OVERLAPPEDWINDOW, 502 | CW_USEDEFAULT, 503 | 0, 504 | CW_USEDEFAULT, 505 | 0, 506 | 0 as HWND, 507 | 0 as HMENU, 508 | 0 as HINSTANCE, 509 | std::ptr::null_mut(), 510 | ); 511 | if hwnd == std::ptr::null_mut() { 512 | return Err(get_win_os_error("Error creating window")); 513 | } 514 | let mut nid = get_nid_struct(&hwnd); 515 | nid.uID = 0x1; 516 | nid.uFlags = NIF_MESSAGE; 517 | nid.uCallbackMessage = WM_USER + 1; 518 | if Shell_NotifyIconW(NIM_ADD, &mut nid as *mut NOTIFYICONDATAW) == 0 { 519 | return Err(get_win_os_error("Error adding menu icon")); 520 | } 521 | // Setup menu 522 | let hmenu = CreatePopupMenu(); 523 | let m = MENUINFO { 524 | cbSize: std::mem::size_of::() as DWORD, 525 | fMask: MIM_APPLYTOSUBMENUS | MIM_STYLE, 526 | dwStyle: MNS_NOTIFYBYPOS, 527 | cyMax: 0 as UINT, 528 | hbrBack: 0 as HBRUSH, 529 | dwContextHelpID: 0 as DWORD, 530 | dwMenuData: 0 as ULONG_PTR, 531 | }; 532 | if SetMenuInfo(hmenu, &m as *const MENUINFO) == 0 { 533 | return Err(get_win_os_error("Error setting up menu")); 534 | } 535 | 536 | Ok(WindowInfo { 537 | hwnd: hwnd, 538 | hmenu: hmenu, 539 | hinstance: hinstance, 540 | }) 541 | } 542 | 543 | unsafe fn run_loop() { 544 | debug!("Running windows loop"); 545 | // Run message loop 546 | let mut msg = MSG { 547 | hwnd: 0 as HWND, 548 | message: 0 as UINT, 549 | wParam: 0 as WPARAM, 550 | lParam: 0 as LPARAM, 551 | time: 0 as DWORD, 552 | pt: POINT { x: 0, y: 0 }, 553 | }; 554 | loop { 555 | GetMessageW(&mut msg, 0 as HWND, 0, 0); 556 | if msg.message == WM_QUIT { 557 | break; 558 | } 559 | TranslateMessage(&mut msg); 560 | DispatchMessageW(&mut msg); 561 | } 562 | debug!("Leaving windows run loop"); 563 | } 564 | 565 | pub struct Window { 566 | info: WindowInfo, 567 | windows_loop: Option>, 568 | menu_idx: Cell, 569 | callback: RefCell>, 570 | pub rx: Receiver, 571 | menu_displayed: Cell, 572 | } 573 | 574 | impl Window { 575 | pub fn new() -> Result { 576 | let (tx, rx) = channel(); 577 | let (event_tx, event_rx) = channel(); 578 | let windows_loop = thread::spawn(move || { 579 | unsafe { 580 | let i = init_window(); 581 | let k; 582 | match i { 583 | Ok(j) => { 584 | tx.send(Ok(j.clone())).ok(); 585 | k = j; 586 | } 587 | Err(e) => { 588 | // If creation didn't work, return out of the thread. 589 | tx.send(Err(e)).ok(); 590 | return; 591 | } 592 | }; 593 | WININFO_STASH.with(|stash| { 594 | let data = WindowsLoopData { 595 | info: k, 596 | tx: event_tx, 597 | }; 598 | (*stash.borrow_mut()) = Some(data); 599 | }); 600 | run_loop(); 601 | } 602 | }); 603 | let info = match rx.recv().unwrap() { 604 | Ok(i) => i, 605 | Err(e) => { 606 | return Err(e); 607 | } 608 | }; 609 | let w = Window { 610 | info: info, 611 | windows_loop: Some(windows_loop), 612 | rx: event_rx, 613 | menu_idx: Cell::new(0), 614 | callback: RefCell::new(HashMap::new()), 615 | menu_displayed: Cell::new(false), 616 | }; 617 | Ok(w) 618 | } 619 | 620 | pub fn quit(&self) { 621 | unsafe { 622 | PostMessageW(self.info.hwnd, WM_DESTROY, 0 as WPARAM, 0 as LPARAM); 623 | } 624 | } 625 | 626 | pub fn set_timer(&mut self, milliseconds: u32) -> Result<(), SystrayError> { 627 | unsafe { SetTimer(self.info.hwnd, TIMER1, milliseconds, None) }; 628 | Ok(()) 629 | } 630 | 631 | pub fn remove_timer(&mut self) -> Result<(), SystrayError> { 632 | unsafe { KillTimer(self.info.hwnd, TIMER1) }; 633 | Ok(()) 634 | } 635 | 636 | pub fn set_tooltip(&self, tooltip: &str) -> Result<(), SystrayError> { 637 | // Add Tooltip 638 | debug!("Setting tooltip to {}", tooltip); 639 | let mut nid = get_nid_struct(&self.info.hwnd); 640 | // Gross way to convert String to UTF-16 [i16; 128] 641 | // TODO: Clean up conversion, test for length so we don't panic at runtime 642 | let mut v: Vec = UTF_16LE.encode(tooltip, EncoderTrap::Strict).unwrap(); 643 | v.push(0); 644 | v.push(0); // NUL-terminate 645 | let utf16: &[u16] = unsafe { slice::from_raw_parts(v.as_ptr() as *const _, v.len() / 2) }; 646 | for i in 0..std::cmp::min(utf16.len(), 128) { 647 | nid.szTip[i] = utf16[i]; 648 | } 649 | nid.szTip[127] = 0; // NUL-terminate 650 | nid.uFlags = NIF_TIP; 651 | unsafe { 652 | if Shell_NotifyIconW(NIM_MODIFY, &mut nid as *mut NOTIFYICONDATAW) == 0 { 653 | return Err(get_win_os_error("Error setting tooltip")); 654 | } 655 | } 656 | Ok(()) 657 | } 658 | 659 | pub fn select_menu_item(&self, item: u32) -> Result { 660 | unsafe { 661 | if CheckMenuItem(self.info.hmenu, item, MF_BYPOSITION | MF_CHECKED) as i32 == -1 { 662 | return Err(get_win_os_error("Menu item does not exist (cannot check)")); 663 | } 664 | } 665 | Ok(item) 666 | } 667 | 668 | pub fn enable_menu_item(&self, item: u32, enable: MenuEnableFlag) -> Result { 669 | let flags = MF_BYPOSITION 670 | | match enable { 671 | MenuEnableFlag::Enabled => MF_ENABLED, 672 | MenuEnableFlag::Disabled => MF_DISABLED, 673 | MenuEnableFlag::Grayed => MF_GRAYED, 674 | }; 675 | unsafe { 676 | if EnableMenuItem(self.info.hmenu, item, flags) == 0 { 677 | return Err(get_win_os_error("Error enabling menu item")); 678 | } 679 | } 680 | Ok(item) 681 | } 682 | 683 | pub fn unselect_menu_item(&self, item: u32) -> Result { 684 | unsafe { 685 | if CheckMenuItem(self.info.hmenu, item, MF_BYPOSITION | MF_UNCHECKED) == 0 { 686 | return Err(get_win_os_error("Error unchecking menu item")); 687 | } 688 | } 689 | Ok(item) 690 | } 691 | 692 | fn add_menu_entry(&self, item_name: &str, checked: bool) -> Result { 693 | let mut st = to_wstring(item_name); 694 | let idx = self.menu_idx.get(); 695 | self.menu_idx.set(idx + 1); 696 | let mut item = get_menu_item_struct(); 697 | item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_STATE | MIIM_CHECKMARKS; 698 | if checked { 699 | item.fState = MFS_CHECKED; 700 | } 701 | item.fType = MFT_STRING; 702 | item.wID = idx; 703 | item.dwTypeData = st.as_mut_ptr(); 704 | item.cch = (item_name.len() * 2) as u32; 705 | unsafe { 706 | if InsertMenuItemW(self.info.hmenu, idx, 1, &item as *const MENUITEMINFOW) == 0 { 707 | return Err(get_win_os_error("Error inserting menu item")); 708 | } 709 | } 710 | Ok(idx) 711 | } 712 | 713 | pub fn add_menu_separator(&self) -> Result { 714 | let idx = self.menu_idx.get(); 715 | self.menu_idx.set(idx + 1); 716 | let mut item = get_menu_item_struct(); 717 | item.fMask = MIIM_FTYPE; 718 | item.fType = MFT_SEPARATOR; 719 | item.wID = idx; 720 | unsafe { 721 | if InsertMenuItemW(self.info.hmenu, idx, 1, &item as *const MENUITEMINFOW) == 0 { 722 | return Err(get_win_os_error("Error inserting separator")); 723 | } 724 | } 725 | Ok(idx) 726 | } 727 | 728 | pub fn add_menu_item( 729 | &self, 730 | item_name: &str, 731 | checked: bool, 732 | f: F, 733 | ) -> Result 734 | where 735 | F: std::ops::Fn(&Window) -> () + 'static, 736 | { 737 | let idx = match self.add_menu_entry(item_name, checked) { 738 | Ok(i) => i, 739 | Err(e) => { 740 | return Err(e); 741 | } 742 | }; 743 | let mut m = self.callback.borrow_mut(); 744 | m.insert(idx, make_callback(f)); 745 | Ok(idx) 746 | } 747 | 748 | pub fn clear_menu(&self) -> Result<(), SystrayError> { 749 | let mut idx = self.menu_idx.get(); 750 | unsafe { 751 | while idx > 0 { 752 | if DeleteMenu(self.info.hmenu, idx - 1, MF_BYPOSITION) == 0 { 753 | return Err(get_win_os_error("Error clearing menu")); 754 | } 755 | idx = idx - 1; 756 | } 757 | self.menu_idx.set(0); 758 | } 759 | Ok(()) 760 | } 761 | 762 | fn set_icon(&self, icon: HICON) -> Result<(), SystrayError> { 763 | unsafe { 764 | let mut nid = get_nid_struct(&self.info.hwnd); 765 | nid.uFlags = NIF_ICON; 766 | nid.hIcon = icon; 767 | if Shell_NotifyIconW(NIM_MODIFY, &mut nid as *mut NOTIFYICONDATAW) == 0 { 768 | return Err(get_win_os_error("Error setting icon")); 769 | } 770 | } 771 | Ok(()) 772 | } 773 | 774 | pub fn set_icon_from_resource(&self, resource_name: &String) -> Result<(), SystrayError> { 775 | let icon; 776 | unsafe { 777 | icon = LoadImageW( 778 | self.info.hinstance, 779 | to_wstring(&resource_name).as_ptr(), 780 | IMAGE_ICON, 781 | 64, 782 | 64, 783 | 0, 784 | ) as HICON; 785 | if icon == std::ptr::null_mut() as HICON { 786 | return Err(get_win_os_error("Error setting icon from resource")); 787 | } 788 | } 789 | self.set_icon(icon) 790 | } 791 | 792 | pub fn set_icon_from_file(&self, icon_file: &String) -> Result<(), SystrayError> { 793 | let wstr_icon_file = to_wstring(&icon_file); 794 | let hicon; 795 | unsafe { 796 | hicon = LoadImageW( 797 | std::ptr::null_mut() as HINSTANCE, 798 | wstr_icon_file.as_ptr(), 799 | IMAGE_ICON, 800 | 64, 801 | 64, 802 | LR_LOADFROMFILE, 803 | ) as HICON; 804 | if hicon == std::ptr::null_mut() as HICON { 805 | return Err(get_win_os_error("Error setting icon from file")); 806 | } 807 | } 808 | self.set_icon(hicon) 809 | } 810 | 811 | pub fn set_icon_from_buffer( 812 | &self, 813 | buffer: &[u8], 814 | width: u32, 815 | height: u32, 816 | ) -> Result<(), SystrayError> { 817 | let offset = unsafe { 818 | LookupIconIdFromDirectoryEx( 819 | buffer.as_ptr() as PBYTE, 820 | TRUE, 821 | width as i32, 822 | height as i32, 823 | LR_DEFAULTCOLOR, 824 | ) 825 | }; 826 | 827 | if offset != 0 { 828 | let icon_data = &buffer[offset as usize..]; 829 | let hicon = unsafe { 830 | CreateIconFromResourceEx( 831 | icon_data.as_ptr() as PBYTE, 832 | buffer.len() as u32 - offset as u32, 833 | TRUE, 834 | 0x30000, 835 | width as i32, 836 | height as i32, 837 | LR_DEFAULTCOLOR, 838 | ) 839 | }; 840 | 841 | if hicon == std::ptr::null_mut() as HICON { 842 | return Err(unsafe { get_win_os_error("Cannot load icon from the buffer") }); 843 | } 844 | 845 | self.set_icon(hicon) 846 | } else { 847 | Err(unsafe { get_win_os_error("Error setting icon from buffer") }) 848 | } 849 | } 850 | 851 | pub fn menu_displayed(&self) -> bool { 852 | self.menu_displayed.get() 853 | } 854 | 855 | pub fn shutdown(&self) -> Result<(), SystrayError> { 856 | unsafe { 857 | let mut nid = get_nid_struct(&self.info.hwnd); 858 | nid.uFlags = NIF_ICON; 859 | if Shell_NotifyIconW(NIM_DELETE, &mut nid as *mut NOTIFYICONDATAW) == 0 { 860 | return Err(get_win_os_error("Error deleting icon from menu")); 861 | } 862 | } 863 | Ok(()) 864 | } 865 | } 866 | 867 | impl Drop for Window { 868 | fn drop(&mut self) { 869 | self.shutdown().ok(); 870 | } 871 | } 872 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler" 5 | version = "0.2.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ccc9a9dd069569f212bc4330af9f17c4afb5e8ce185e83dbb14f1349dda18b10" 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "0.7.10" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" 14 | dependencies = [ 15 | "memchr", 16 | ] 17 | 18 | [[package]] 19 | name = "ansi_term" 20 | version = "0.11.0" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 23 | dependencies = [ 24 | "winapi 0.3.8", 25 | ] 26 | 27 | [[package]] 28 | name = "arrayref" 29 | version = "0.3.6" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 32 | 33 | [[package]] 34 | name = "arrayvec" 35 | version = "0.5.1" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 38 | 39 | [[package]] 40 | name = "atk-sys" 41 | version = "0.6.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "f8dc233521f7bffd3042c31082ea71bd08820abf44bac938fb36591e20f76f39" 44 | dependencies = [ 45 | "bitflags", 46 | "glib-sys", 47 | "gobject-sys", 48 | "libc", 49 | "pkg-config", 50 | ] 51 | 52 | [[package]] 53 | name = "atrofac-cli" 54 | version = "0.1.0" 55 | dependencies = [ 56 | "atrofac-library", 57 | "env_logger", 58 | "exitcode", 59 | "log", 60 | "structopt", 61 | ] 62 | 63 | [[package]] 64 | name = "atrofac-gui" 65 | version = "0.1.0" 66 | dependencies = [ 67 | "atrofac-libgui", 68 | "atrofac-library", 69 | "log", 70 | "winres", 71 | ] 72 | 73 | [[package]] 74 | name = "atrofac-libgui" 75 | version = "0.1.0" 76 | dependencies = [ 77 | "atrofac-library", 78 | "dirs", 79 | "encoding", 80 | "flexi_logger", 81 | "indexmap", 82 | "libc", 83 | "log", 84 | "msgbox", 85 | "serde", 86 | "serde_yaml", 87 | "winapi 0.3.8", 88 | ] 89 | 90 | [[package]] 91 | name = "atrofac-library" 92 | version = "0.1.0" 93 | dependencies = [ 94 | "log", 95 | "regex", 96 | "winapi 0.3.8", 97 | ] 98 | 99 | [[package]] 100 | name = "atty" 101 | version = "0.2.14" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 104 | dependencies = [ 105 | "hermit-abi", 106 | "libc", 107 | "winapi 0.3.8", 108 | ] 109 | 110 | [[package]] 111 | name = "autocfg" 112 | version = "1.0.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 115 | 116 | [[package]] 117 | name = "base64" 118 | version = "0.11.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 121 | 122 | [[package]] 123 | name = "bitflags" 124 | version = "1.2.1" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 127 | 128 | [[package]] 129 | name = "blake2b_simd" 130 | version = "0.5.10" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 133 | dependencies = [ 134 | "arrayref", 135 | "arrayvec", 136 | "constant_time_eq", 137 | ] 138 | 139 | [[package]] 140 | name = "block" 141 | version = "0.1.6" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 144 | 145 | [[package]] 146 | name = "c_vec" 147 | version = "1.3.3" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "f8a318911dce53b5f1ca6539c44f5342c632269f0fa7ea3e35f32458c27a7c30" 150 | 151 | [[package]] 152 | name = "cairo-rs" 153 | version = "0.4.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "a110f269c2fd382df5fe8bd46dfa5f1b83608aa717fecb6e7a28c08c202f0e13" 156 | dependencies = [ 157 | "c_vec", 158 | "cairo-sys-rs", 159 | "glib", 160 | "glib-sys", 161 | "libc", 162 | ] 163 | 164 | [[package]] 165 | name = "cairo-sys-rs" 166 | version = "0.6.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "0395175ecba60accac076a02c31d143b9dcd9d5eb5316d7163a3273803b765c7" 169 | dependencies = [ 170 | "libc", 171 | "pkg-config", 172 | "winapi 0.3.8", 173 | ] 174 | 175 | [[package]] 176 | name = "cc" 177 | version = "1.0.54" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" 180 | 181 | [[package]] 182 | name = "cfg-if" 183 | version = "0.1.10" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 186 | 187 | [[package]] 188 | name = "chrono" 189 | version = "0.4.13" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6" 192 | dependencies = [ 193 | "num-integer", 194 | "num-traits", 195 | "time", 196 | ] 197 | 198 | [[package]] 199 | name = "clap" 200 | version = "2.33.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" 203 | dependencies = [ 204 | "ansi_term", 205 | "atty", 206 | "bitflags", 207 | "strsim", 208 | "textwrap", 209 | "unicode-width", 210 | "vec_map", 211 | ] 212 | 213 | [[package]] 214 | name = "cocoa" 215 | version = "0.19.1" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "f29f7768b2d1be17b96158e3285951d366b40211320fb30826a76cb7a0da6400" 218 | dependencies = [ 219 | "bitflags", 220 | "block", 221 | "core-foundation", 222 | "core-graphics", 223 | "foreign-types", 224 | "libc", 225 | "objc", 226 | ] 227 | 228 | [[package]] 229 | name = "constant_time_eq" 230 | version = "0.1.5" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 233 | 234 | [[package]] 235 | name = "core-foundation" 236 | version = "0.6.4" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" 239 | dependencies = [ 240 | "core-foundation-sys", 241 | "libc", 242 | ] 243 | 244 | [[package]] 245 | name = "core-foundation-sys" 246 | version = "0.6.2" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 249 | 250 | [[package]] 251 | name = "core-graphics" 252 | version = "0.17.3" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" 255 | dependencies = [ 256 | "bitflags", 257 | "core-foundation", 258 | "foreign-types", 259 | "libc", 260 | ] 261 | 262 | [[package]] 263 | name = "crc32fast" 264 | version = "1.2.0" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 267 | dependencies = [ 268 | "cfg-if", 269 | ] 270 | 271 | [[package]] 272 | name = "crossbeam-utils" 273 | version = "0.7.2" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 276 | dependencies = [ 277 | "autocfg", 278 | "cfg-if", 279 | "lazy_static", 280 | ] 281 | 282 | [[package]] 283 | name = "dirs" 284 | version = "2.0.2" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 287 | dependencies = [ 288 | "cfg-if", 289 | "dirs-sys", 290 | ] 291 | 292 | [[package]] 293 | name = "dirs-sys" 294 | version = "0.3.4" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" 297 | dependencies = [ 298 | "cfg-if", 299 | "libc", 300 | "redox_users", 301 | "winapi 0.3.8", 302 | ] 303 | 304 | [[package]] 305 | name = "dtoa" 306 | version = "0.4.5" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" 309 | 310 | [[package]] 311 | name = "encoding" 312 | version = "0.2.33" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" 315 | dependencies = [ 316 | "encoding-index-japanese", 317 | "encoding-index-korean", 318 | "encoding-index-simpchinese", 319 | "encoding-index-singlebyte", 320 | "encoding-index-tradchinese", 321 | ] 322 | 323 | [[package]] 324 | name = "encoding-index-japanese" 325 | version = "1.20141219.5" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" 328 | dependencies = [ 329 | "encoding_index_tests", 330 | ] 331 | 332 | [[package]] 333 | name = "encoding-index-korean" 334 | version = "1.20141219.5" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" 337 | dependencies = [ 338 | "encoding_index_tests", 339 | ] 340 | 341 | [[package]] 342 | name = "encoding-index-simpchinese" 343 | version = "1.20141219.5" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" 346 | dependencies = [ 347 | "encoding_index_tests", 348 | ] 349 | 350 | [[package]] 351 | name = "encoding-index-singlebyte" 352 | version = "1.20141219.5" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" 355 | dependencies = [ 356 | "encoding_index_tests", 357 | ] 358 | 359 | [[package]] 360 | name = "encoding-index-tradchinese" 361 | version = "1.20141219.5" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" 364 | dependencies = [ 365 | "encoding_index_tests", 366 | ] 367 | 368 | [[package]] 369 | name = "encoding_index_tests" 370 | version = "0.1.4" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" 373 | 374 | [[package]] 375 | name = "env_logger" 376 | version = "0.7.1" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 379 | dependencies = [ 380 | "atty", 381 | "humantime", 382 | "log", 383 | "regex", 384 | "termcolor", 385 | ] 386 | 387 | [[package]] 388 | name = "exitcode" 389 | version = "1.1.2" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" 392 | 393 | [[package]] 394 | name = "filetime" 395 | version = "0.2.10" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695" 398 | dependencies = [ 399 | "cfg-if", 400 | "libc", 401 | "redox_syscall", 402 | "winapi 0.3.8", 403 | ] 404 | 405 | [[package]] 406 | name = "flate2" 407 | version = "1.0.16" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "68c90b0fc46cf89d227cc78b40e494ff81287a92dd07631e5af0d06fe3cf885e" 410 | dependencies = [ 411 | "cfg-if", 412 | "crc32fast", 413 | "libc", 414 | "miniz_oxide", 415 | ] 416 | 417 | [[package]] 418 | name = "flexi_logger" 419 | version = "0.15.7" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "33897654c23a50cebab45e18356f69fb771c9949a6928344fb1f01ffccc7c5f3" 422 | dependencies = [ 423 | "chrono", 424 | "flate2", 425 | "glob", 426 | "log", 427 | "notify", 428 | "regex", 429 | "serde", 430 | "serde_derive", 431 | "thiserror", 432 | "toml", 433 | "yansi", 434 | ] 435 | 436 | [[package]] 437 | name = "foreign-types" 438 | version = "0.3.2" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 441 | dependencies = [ 442 | "foreign-types-shared", 443 | ] 444 | 445 | [[package]] 446 | name = "foreign-types-shared" 447 | version = "0.1.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 450 | 451 | [[package]] 452 | name = "fsevent" 453 | version = "0.4.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" 456 | dependencies = [ 457 | "bitflags", 458 | "fsevent-sys", 459 | ] 460 | 461 | [[package]] 462 | name = "fsevent-sys" 463 | version = "2.0.1" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" 466 | dependencies = [ 467 | "libc", 468 | ] 469 | 470 | [[package]] 471 | name = "fuchsia-zircon" 472 | version = "0.3.3" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 475 | dependencies = [ 476 | "bitflags", 477 | "fuchsia-zircon-sys", 478 | ] 479 | 480 | [[package]] 481 | name = "fuchsia-zircon-sys" 482 | version = "0.3.3" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 485 | 486 | [[package]] 487 | name = "gdk" 488 | version = "0.8.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "dd30051ff3d908ff2fc7e5776ffe1c699821e043809f294c3a61004f11d6c3a9" 491 | dependencies = [ 492 | "bitflags", 493 | "cairo-rs", 494 | "cairo-sys-rs", 495 | "gdk-pixbuf", 496 | "gdk-sys", 497 | "gio", 498 | "glib", 499 | "glib-sys", 500 | "gobject-sys", 501 | "libc", 502 | "pango", 503 | ] 504 | 505 | [[package]] 506 | name = "gdk-pixbuf" 507 | version = "0.4.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "c2d2199eba47ebcb9977ce28179649bdd59305ef465c4e6f9b65aaa41c24e6b5" 510 | dependencies = [ 511 | "gdk-pixbuf-sys", 512 | "gio", 513 | "gio-sys", 514 | "glib", 515 | "glib-sys", 516 | "gobject-sys", 517 | "libc", 518 | ] 519 | 520 | [[package]] 521 | name = "gdk-pixbuf-sys" 522 | version = "0.6.0" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "df6a3b73e04fafc07f5ebc083f1096a773412e627828e1103a55e921f81187d8" 525 | dependencies = [ 526 | "bitflags", 527 | "gio-sys", 528 | "glib-sys", 529 | "gobject-sys", 530 | "libc", 531 | "pkg-config", 532 | ] 533 | 534 | [[package]] 535 | name = "gdk-sys" 536 | version = "0.6.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "3162ff940526ddff71bf1f630facee6b5e05d282d125ba0c4c803842819b80c3" 539 | dependencies = [ 540 | "bitflags", 541 | "cairo-sys-rs", 542 | "gdk-pixbuf-sys", 543 | "gio-sys", 544 | "glib-sys", 545 | "gobject-sys", 546 | "libc", 547 | "pango-sys", 548 | "pkg-config", 549 | ] 550 | 551 | [[package]] 552 | name = "getrandom" 553 | version = "0.1.14" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 556 | dependencies = [ 557 | "cfg-if", 558 | "libc", 559 | "wasi", 560 | ] 561 | 562 | [[package]] 563 | name = "gio" 564 | version = "0.4.1" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "2db9fad8f1b0d4c7338a210a6cbdf081dcc1a3c223718c698c4f313f6c288acb" 567 | dependencies = [ 568 | "bitflags", 569 | "gio-sys", 570 | "glib", 571 | "glib-sys", 572 | "gobject-sys", 573 | "lazy_static", 574 | "libc", 575 | ] 576 | 577 | [[package]] 578 | name = "gio-sys" 579 | version = "0.6.0" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "2a57872499171d279f8577ce83837da4cae62b08dd32892236ed67ab7ea61030" 582 | dependencies = [ 583 | "bitflags", 584 | "glib-sys", 585 | "gobject-sys", 586 | "libc", 587 | "pkg-config", 588 | ] 589 | 590 | [[package]] 591 | name = "glib" 592 | version = "0.5.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "5e0be1b1432e227bcd1a9b28db9dc1474a7e7fd4227e08e16f35304f32d09b61" 595 | dependencies = [ 596 | "bitflags", 597 | "glib-sys", 598 | "gobject-sys", 599 | "lazy_static", 600 | "libc", 601 | ] 602 | 603 | [[package]] 604 | name = "glib-sys" 605 | version = "0.6.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "615bef979b5838526aee99241afc80cfb2e34a8735d4bcb8ec6072598c18a408" 608 | dependencies = [ 609 | "bitflags", 610 | "libc", 611 | "pkg-config", 612 | ] 613 | 614 | [[package]] 615 | name = "glob" 616 | version = "0.3.0" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 619 | 620 | [[package]] 621 | name = "gobject-sys" 622 | version = "0.6.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "70409d6405db8b1591602fcd0cbe8af52cd9976dd39194442b4c149ba343f86d" 625 | dependencies = [ 626 | "bitflags", 627 | "glib-sys", 628 | "libc", 629 | "pkg-config", 630 | ] 631 | 632 | [[package]] 633 | name = "gtk" 634 | version = "0.4.1" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "d695d6be4110618a97c19cd068e8a00e53e33b87e3c65cdc5397667498b1bc24" 637 | dependencies = [ 638 | "bitflags", 639 | "cairo-rs", 640 | "cairo-sys-rs", 641 | "cc", 642 | "gdk", 643 | "gdk-pixbuf", 644 | "gdk-pixbuf-sys", 645 | "gdk-sys", 646 | "gio", 647 | "gio-sys", 648 | "glib", 649 | "glib-sys", 650 | "gobject-sys", 651 | "gtk-sys", 652 | "lazy_static", 653 | "libc", 654 | "pango", 655 | ] 656 | 657 | [[package]] 658 | name = "gtk-sys" 659 | version = "0.6.0" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "3d9554cf5b3a85a13fb39258c65b04b262989c1d7a758f8f555b77a478621a91" 662 | dependencies = [ 663 | "atk-sys", 664 | "bitflags", 665 | "cairo-sys-rs", 666 | "gdk-pixbuf-sys", 667 | "gdk-sys", 668 | "gio-sys", 669 | "glib-sys", 670 | "gobject-sys", 671 | "libc", 672 | "pango-sys", 673 | "pkg-config", 674 | ] 675 | 676 | [[package]] 677 | name = "heck" 678 | version = "0.3.1" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 681 | dependencies = [ 682 | "unicode-segmentation", 683 | ] 684 | 685 | [[package]] 686 | name = "hermit-abi" 687 | version = "0.1.13" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" 690 | dependencies = [ 691 | "libc", 692 | ] 693 | 694 | [[package]] 695 | name = "humantime" 696 | version = "1.3.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 699 | dependencies = [ 700 | "quick-error", 701 | ] 702 | 703 | [[package]] 704 | name = "indexmap" 705 | version = "1.3.2" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" 708 | dependencies = [ 709 | "autocfg", 710 | ] 711 | 712 | [[package]] 713 | name = "inotify" 714 | version = "0.7.1" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" 717 | dependencies = [ 718 | "bitflags", 719 | "inotify-sys", 720 | "libc", 721 | ] 722 | 723 | [[package]] 724 | name = "inotify-sys" 725 | version = "0.1.3" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" 728 | dependencies = [ 729 | "libc", 730 | ] 731 | 732 | [[package]] 733 | name = "iovec" 734 | version = "0.1.4" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 737 | dependencies = [ 738 | "libc", 739 | ] 740 | 741 | [[package]] 742 | name = "kernel32-sys" 743 | version = "0.2.2" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 746 | dependencies = [ 747 | "winapi 0.2.8", 748 | "winapi-build", 749 | ] 750 | 751 | [[package]] 752 | name = "lazy_static" 753 | version = "1.4.0" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 756 | 757 | [[package]] 758 | name = "lazycell" 759 | version = "1.2.1" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" 762 | 763 | [[package]] 764 | name = "libc" 765 | version = "0.2.70" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" 768 | 769 | [[package]] 770 | name = "linked-hash-map" 771 | version = "0.5.3" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" 774 | 775 | [[package]] 776 | name = "log" 777 | version = "0.4.8" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 780 | dependencies = [ 781 | "cfg-if", 782 | ] 783 | 784 | [[package]] 785 | name = "malloc_buf" 786 | version = "0.0.6" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 789 | dependencies = [ 790 | "libc", 791 | ] 792 | 793 | [[package]] 794 | name = "memchr" 795 | version = "2.3.3" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 798 | 799 | [[package]] 800 | name = "miniz_oxide" 801 | version = "0.4.0" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" 804 | dependencies = [ 805 | "adler", 806 | ] 807 | 808 | [[package]] 809 | name = "mio" 810 | version = "0.6.22" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 813 | dependencies = [ 814 | "cfg-if", 815 | "fuchsia-zircon", 816 | "fuchsia-zircon-sys", 817 | "iovec", 818 | "kernel32-sys", 819 | "libc", 820 | "log", 821 | "miow", 822 | "net2", 823 | "slab", 824 | "winapi 0.2.8", 825 | ] 826 | 827 | [[package]] 828 | name = "mio-extras" 829 | version = "2.0.6" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" 832 | dependencies = [ 833 | "lazycell", 834 | "log", 835 | "mio", 836 | "slab", 837 | ] 838 | 839 | [[package]] 840 | name = "miow" 841 | version = "0.2.1" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 844 | dependencies = [ 845 | "kernel32-sys", 846 | "net2", 847 | "winapi 0.2.8", 848 | "ws2_32-sys", 849 | ] 850 | 851 | [[package]] 852 | name = "msgbox" 853 | version = "0.4.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "82cb63d8d7be875323a43d9ab525c28ce2d65bff89648d1aedd9962e00dede00" 856 | dependencies = [ 857 | "cocoa", 858 | "gtk", 859 | "objc", 860 | "winapi 0.3.8", 861 | ] 862 | 863 | [[package]] 864 | name = "net2" 865 | version = "0.2.34" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 868 | dependencies = [ 869 | "cfg-if", 870 | "libc", 871 | "winapi 0.3.8", 872 | ] 873 | 874 | [[package]] 875 | name = "notify" 876 | version = "4.0.15" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" 879 | dependencies = [ 880 | "bitflags", 881 | "filetime", 882 | "fsevent", 883 | "fsevent-sys", 884 | "inotify", 885 | "libc", 886 | "mio", 887 | "mio-extras", 888 | "walkdir", 889 | "winapi 0.3.8", 890 | ] 891 | 892 | [[package]] 893 | name = "num-integer" 894 | version = "0.1.43" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 897 | dependencies = [ 898 | "autocfg", 899 | "num-traits", 900 | ] 901 | 902 | [[package]] 903 | name = "num-traits" 904 | version = "0.2.12" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 907 | dependencies = [ 908 | "autocfg", 909 | ] 910 | 911 | [[package]] 912 | name = "objc" 913 | version = "0.2.7" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 916 | dependencies = [ 917 | "malloc_buf", 918 | ] 919 | 920 | [[package]] 921 | name = "pango" 922 | version = "0.4.0" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "45374801e224373c3c0393cd48073c81093494c8735721e81d1dbaa4096b2767" 925 | dependencies = [ 926 | "bitflags", 927 | "glib", 928 | "glib-sys", 929 | "gobject-sys", 930 | "libc", 931 | "pango-sys", 932 | ] 933 | 934 | [[package]] 935 | name = "pango-sys" 936 | version = "0.6.0" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "94039b3921a4af4058a3e4335e5d15099101f298a92f5afc40bab3a3027594a1" 939 | dependencies = [ 940 | "bitflags", 941 | "glib-sys", 942 | "gobject-sys", 943 | "libc", 944 | "pkg-config", 945 | ] 946 | 947 | [[package]] 948 | name = "pkg-config" 949 | version = "0.3.17" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 952 | 953 | [[package]] 954 | name = "proc-macro-error" 955 | version = "1.0.2" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678" 958 | dependencies = [ 959 | "proc-macro-error-attr", 960 | "proc-macro2", 961 | "quote", 962 | "syn", 963 | "version_check", 964 | ] 965 | 966 | [[package]] 967 | name = "proc-macro-error-attr" 968 | version = "1.0.2" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" 971 | dependencies = [ 972 | "proc-macro2", 973 | "quote", 974 | "syn", 975 | "syn-mid", 976 | "version_check", 977 | ] 978 | 979 | [[package]] 980 | name = "proc-macro2" 981 | version = "1.0.13" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" 984 | dependencies = [ 985 | "unicode-xid", 986 | ] 987 | 988 | [[package]] 989 | name = "quick-error" 990 | version = "1.2.3" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 993 | 994 | [[package]] 995 | name = "quote" 996 | version = "1.0.6" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" 999 | dependencies = [ 1000 | "proc-macro2", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "redox_syscall" 1005 | version = "0.1.56" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 1008 | 1009 | [[package]] 1010 | name = "redox_users" 1011 | version = "0.3.4" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" 1014 | dependencies = [ 1015 | "getrandom", 1016 | "redox_syscall", 1017 | "rust-argon2", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "regex" 1022 | version = "1.3.7" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" 1025 | dependencies = [ 1026 | "aho-corasick", 1027 | "memchr", 1028 | "regex-syntax", 1029 | "thread_local", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "regex-syntax" 1034 | version = "0.6.17" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" 1037 | 1038 | [[package]] 1039 | name = "rust-argon2" 1040 | version = "0.7.0" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" 1043 | dependencies = [ 1044 | "base64", 1045 | "blake2b_simd", 1046 | "constant_time_eq", 1047 | "crossbeam-utils", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "same-file" 1052 | version = "1.0.6" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1055 | dependencies = [ 1056 | "winapi-util", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "serde" 1061 | version = "1.0.110" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" 1064 | dependencies = [ 1065 | "serde_derive", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "serde_derive" 1070 | version = "1.0.110" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" 1073 | dependencies = [ 1074 | "proc-macro2", 1075 | "quote", 1076 | "syn", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "serde_yaml" 1081 | version = "0.8.12" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "16c7a592a1ec97c9c1c68d75b6e537dcbf60c7618e038e7841e00af1d9ccf0c4" 1084 | dependencies = [ 1085 | "dtoa", 1086 | "linked-hash-map", 1087 | "serde", 1088 | "yaml-rust", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "slab" 1093 | version = "0.4.2" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1096 | 1097 | [[package]] 1098 | name = "strsim" 1099 | version = "0.8.0" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1102 | 1103 | [[package]] 1104 | name = "structopt" 1105 | version = "0.3.14" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" 1108 | dependencies = [ 1109 | "clap", 1110 | "lazy_static", 1111 | "structopt-derive", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "structopt-derive" 1116 | version = "0.4.7" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" 1119 | dependencies = [ 1120 | "heck", 1121 | "proc-macro-error", 1122 | "proc-macro2", 1123 | "quote", 1124 | "syn", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "syn" 1129 | version = "1.0.23" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" 1132 | dependencies = [ 1133 | "proc-macro2", 1134 | "quote", 1135 | "unicode-xid", 1136 | ] 1137 | 1138 | [[package]] 1139 | name = "syn-mid" 1140 | version = "0.5.0" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" 1143 | dependencies = [ 1144 | "proc-macro2", 1145 | "quote", 1146 | "syn", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "termcolor" 1151 | version = "1.1.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 1154 | dependencies = [ 1155 | "winapi-util", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "textwrap" 1160 | version = "0.11.0" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1163 | dependencies = [ 1164 | "unicode-width", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "thiserror" 1169 | version = "1.0.20" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" 1172 | dependencies = [ 1173 | "thiserror-impl", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "thiserror-impl" 1178 | version = "1.0.20" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" 1181 | dependencies = [ 1182 | "proc-macro2", 1183 | "quote", 1184 | "syn", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "thread_local" 1189 | version = "1.0.1" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1192 | dependencies = [ 1193 | "lazy_static", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "time" 1198 | version = "0.1.43" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1201 | dependencies = [ 1202 | "libc", 1203 | "winapi 0.3.8", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "toml" 1208 | version = "0.5.6" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" 1211 | dependencies = [ 1212 | "serde", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "unicode-segmentation" 1217 | version = "1.6.0" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 1220 | 1221 | [[package]] 1222 | name = "unicode-width" 1223 | version = "0.1.7" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 1226 | 1227 | [[package]] 1228 | name = "unicode-xid" 1229 | version = "0.2.0" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 1232 | 1233 | [[package]] 1234 | name = "vec_map" 1235 | version = "0.8.2" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1238 | 1239 | [[package]] 1240 | name = "version_check" 1241 | version = "0.9.1" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" 1244 | 1245 | [[package]] 1246 | name = "walkdir" 1247 | version = "2.3.1" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1250 | dependencies = [ 1251 | "same-file", 1252 | "winapi 0.3.8", 1253 | "winapi-util", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "wasi" 1258 | version = "0.9.0+wasi-snapshot-preview1" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1261 | 1262 | [[package]] 1263 | name = "winapi" 1264 | version = "0.2.8" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1267 | 1268 | [[package]] 1269 | name = "winapi" 1270 | version = "0.3.8" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 1273 | dependencies = [ 1274 | "winapi-i686-pc-windows-gnu", 1275 | "winapi-x86_64-pc-windows-gnu", 1276 | ] 1277 | 1278 | [[package]] 1279 | name = "winapi-build" 1280 | version = "0.1.1" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1283 | 1284 | [[package]] 1285 | name = "winapi-i686-pc-windows-gnu" 1286 | version = "0.4.0" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1289 | 1290 | [[package]] 1291 | name = "winapi-util" 1292 | version = "0.1.5" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1295 | dependencies = [ 1296 | "winapi 0.3.8", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "winapi-x86_64-pc-windows-gnu" 1301 | version = "0.4.0" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1304 | 1305 | [[package]] 1306 | name = "winres" 1307 | version = "0.1.11" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "ff4fb510bbfe5b8992ff15f77a2e6fe6cf062878f0eda00c0f44963a807ca5dc" 1310 | dependencies = [ 1311 | "toml", 1312 | ] 1313 | 1314 | [[package]] 1315 | name = "ws2_32-sys" 1316 | version = "0.2.1" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1319 | dependencies = [ 1320 | "winapi 0.2.8", 1321 | "winapi-build", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "yaml-rust" 1326 | version = "0.4.3" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" 1329 | dependencies = [ 1330 | "linked-hash-map", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "yansi" 1335 | version = "0.5.0" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" 1338 | --------------------------------------------------------------------------------