#![warn(clippy::pedantic)]
#![warn(missing_docs)]
#![warn(clippy::cargo)]
#![windows_subsystem = "windows"]
use clap::Parser;
use std::{ops::Deref, process::ExitCode, thread::sleep, time::Duration};
use windows::{
core::{Result, HSTRING, PCWSTR},
Win32::{
Foundation::{HWND, LPARAM, LRESULT, WPARAM},
System::{
Console::{AttachConsole, ATTACH_PARENT_PROCESS},
LibraryLoader::GetModuleHandleW,
},
UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, MessageBoxW, RegisterClassW,
SendNotifyMessageW, CW_USEDEFAULT, MB_ICONERROR,
MB_ICONINFORMATION, MB_OK, SC_MONITORPOWER, WINDOW_EX_STYLE,
WM_SYSCOMMAND, WNDCLASSW, WS_OVERLAPPED,
},
},
};
const APPLICATION_NAME: &str = "monoff";
const DEFAULT_DELAY_MS: u16 = 100;
const OFF_MONITORPOWER: LPARAM = LPARAM(2);
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(
short,
long,
default_value_t = DEFAULT_DELAY_MS,
value_parser = clap::value_parser!(u16).range(0..)
)]
delay: u16,
}
fn turn_off_monitors() -> Result<()> {
let window_class_name = HSTRING::from(APPLICATION_NAME);
let instance = unsafe { GetModuleHandleW(None)? };
let window_class = WNDCLASSW {
hInstance: instance.into(),
lpszClassName: PCWSTR(window_class_name.as_wide().as_ptr()),
lpfnWndProc: Some(window_proc),
..Default::default()
};
unsafe { RegisterClassW(&window_class) };
let window = unsafe {
CreateWindowExW(
WINDOW_EX_STYLE::default(),
&window_class_name,
PCWSTR::null(),
WS_OVERLAPPED,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
None,
None,
instance,
None,
)
};
unsafe {
SendNotifyMessageW(
window,
WM_SYSCOMMAND,
WPARAM(SC_MONITORPOWER as usize),
OFF_MONITORPOWER,
)
}
}
extern "system" fn window_proc(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe { DefWindowProcW(window, message, wparam, lparam) }
}
enum ClapError<'err> {
ParseError(&'err clap::Error),
Informational(&'err clap::Error),
}
impl ClapError<'_> {
fn exit_code(&self) -> ExitCode {
match self {
Self::ParseError(_) => ExitCode::FAILURE,
Self::Informational(_) => ExitCode::SUCCESS,
}
}
fn show(&self, message_console: &MessageConsole) {
match message_console {
MessageConsole::Textual => {
self.print().unwrap();
}
MessageConsole::Graphical => {
message_console.show_message(
&self.to_string(),
matches!(self, ClapError::ParseError(_)),
);
}
}
}
}
impl Deref for ClapError<'_> {
type Target = clap::Error;
fn deref(&self) -> &Self::Target {
match self {
Self::ParseError(err) | Self::Informational(err) => err,
}
}
}
impl<'err> From<&'err clap::Error> for ClapError<'err> {
fn from(err: &'err clap::Error) -> Self {
if err.use_stderr() {
Self::ParseError(err)
} else {
Self::Informational(err)
}
}
}
enum MessageConsole {
Textual,
Graphical,
}
impl MessageConsole {
fn attach_to_available() -> MessageConsole {
let res = unsafe { AttachConsole(ATTACH_PARENT_PROCESS) };
match res {
Ok(()) => Self::Textual,
Err(_) => Self::Graphical,
}
}
fn show_message(&self, message: &str, is_error: bool) {
match self {
Self::Textual => {
if is_error {
eprintln!("{message}");
} else {
println!("{message}");
}
}
Self::Graphical => {
Self::show_message_box(message, APPLICATION_NAME, is_error);
}
}
}
fn show_message_box(text: &str, title: &str, error: bool) {
let messagebox_style = if error {
MB_ICONERROR | MB_OK
} else {
MB_ICONINFORMATION | MB_OK
};
unsafe {
let _ = MessageBoxW(
None,
&HSTRING::from(text),
&HSTRING::from(title),
messagebox_style,
);
}
}
}
#[cfg(target_os = "windows")]
fn main() -> ExitCode {
let message_console = MessageConsole::attach_to_available();
let args = match Args::try_parse() {
Ok(args) => args,
Err(err) => {
let clap_error = ClapError::from(&err);
clap_error.show(&message_console);
return clap_error.exit_code();
}
};
let delay_duration = Duration::from_millis(args.delay.into());
sleep(delay_duration);
match turn_off_monitors() {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
let error_message = format!("Error turning off monitors: {err}");
message_console.show_message(&error_message, true);
ExitCode::FAILURE
}
}
}
#[cfg(not(target_os = "windows"))]
fn main() {
eprintln!("This program is only intended to run on Windows.");
}