Initial commit

This commit is contained in:
2024-04-30 04:49:26 -05:00
commit bb27195eb4
9 changed files with 1797 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

14
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"cSpell.words": [
"APMQUERYSUSPEND",
"appender",
"HWND",
"LPARAM",
"LRESULT",
"POWERBROADCAST",
"simplelog",
"USEDEFAULT",
"wndproc",
"WPARAM"
]
}

1302
Cargo.lock generated Normal file
View File

File diff suppressed because it is too large Load Diff

57
Cargo.toml Normal file
View File

@@ -0,0 +1,57 @@
[package]
name = "smart-rgb"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"
chrono = "0.4.38"
log = "0.4.21"
log4rs = "1.3.0"
openrgb = "0.1.2"
tokio = { version = "1.25.0", features = ["rt", "macros", "rt-multi-thread"] }
tokio-stream = "0.1.12"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.2.16", features = ["env-filter"]}
windows-service = "0.7.0"
[dependencies.windows]
version = "0.56"
features = [
"Win32_System_LibraryLoader",
"Win32_Foundation",
"Win32_UI_WindowsAndMessaging",
"Win32_System_Power",
"Win32_System_RemoteDesktop",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_Graphics_Gdi",
"Win32_System_Threading",
"Win32_System_SystemInformation",
"Win32_System_Com",
"Win32_Security",
"Win32_System_TaskScheduler",
"Win32_System_Ole",
"Win32_System_Variant",
"Win32_System_Console",
"Win32_Security",
"UI"
]
[dependencies.windows-sys]
version = "0.52.0"
features = [
"Win32_Foundation",
"Win32_Security",
"Win32_System_Console",
"Win32_System",
"Win32_System_IO",
"Win32_Storage_FileSystem",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
"Win32_UI",
"Win32",
"Win32_Graphics_Gdi",
]

14
README.md Normal file
View File

@@ -0,0 +1,14 @@
# smart-rgb
A personal script solution to ensure my computer's RGB lights go off when I need them to be.
- Off
- After idling during the day for more than 3 hours.
- After idling at night (past 11PM) for more than 25 minutes.
- Immediately once put into 'sleep' mode.
- On
- Upon unlock.
This script is intended to be cross-platform for **Windows 10** and **Ubuntu 22.04**.
It uses OpenRGB's server to enable/disable LEDs locally.

32
STORY.md Normal file
View File

@@ -0,0 +1,32 @@
# story
I like to write a short story of a repository's development sometimes.
## Sketching and Testing
Right now, I have a rough sketch of what my software is going to need, and what it's going to do.
- I might use this Tokio framework for idling and receiving notices while processing events. It seems like overkill,
but if I plan to use an automatic configuration reader, than it'd be useful.
- I am unsure at this time how to process and log events.
- Currently, creating a Windows service might be fast, but it also seems to make development slow.
- Would supporting a separate stage Windows process be difficult? It seems like creating a service is just a bunch of boilerplate for running a process.
- The process shows up in Task Manager like normal anyways.
- I'm unsure right now how to properly abstract the sleep/lock/idle states in both Windows and Linux. This might be really hard and annoying. Uggg.
Resources:
- [Minosse - Windows Service in Rust for setting Process Affinities automatically](https://github.com/artumino/minosse/tree/master)
- windows_service [Crates.io](https://crates.io/crates/windows-service/) [Docs](https://docs.rs/windows-service/latest/windows_service/)
- [windows_rs/samples](https://github.com/microsoft/windows-rs/tree/master/crates/samples)
- [Service Control Handler Function](https://learn.microsoft.com/en-us/windows/win32/services/service-control-handler-function?redirectedfrom=MSDN)
- [Event Logging using Rust in Windows](https://www.reddit.com/r/rust/comments/15cq9qp/event_logging_using_rust_in_windows/)
- daemonize [Crates.io](https://docs.rs/daemonize/latest/daemonize/)
- [SO - How to Programatically Detect When the OS Windows Is Waking Up or Going to Sleep](https://stackoverflow.com/questions/4693689/how-to-programmatically-detect-when-the-os-windows-is-waking-up-or-going-to-sl)
- [Tauri Discussion - Power Monitor api or plugin like in electron](https://github.com/tauri-apps/tauri/issues/8968)
- [Using WinAPI in Rust to Manage Windows Services](https://friendlyuser.github.io/posts/tech/rust/Using_WinAPI_in_Rust_to_Manage_Windows_Services/)
## Service vs Window
So initial studies were pretty confusing as most of the solutions for detecting changes in lock screen or sleep mode used `Wndproc`, which is a callback installed when creating a Window; a solution that I really wasn't interested in.
On top of being complex as hell, it required hiding the window manually using special `SW_HIDE` magic, and it sounded wrong. Also, I don't think services should have windows at all.

196
src/main.rs Normal file
View File

@@ -0,0 +1,196 @@
use std::ffi::OsString;
use windows_service::{
service::{ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType, ServiceType},
service_manager::{ServiceManager, ServiceManagerAccess},
service_control_handler::{self, ServiceControlHandlerResult},
};
use std::time::Duration;
use tokio::{runtime::Runtime, sync::mpsc};
use tokio::sync::mpsc::UnboundedReceiver;
use windows_service::{define_windows_service, service_dispatcher};
const SERVICE_NAME: &str = "Easy RGB - Background Scheduler";
const SERVICE_DESCRIPTION: &str = "Service to apply rules to background processes";
define_windows_service!(ffi_service_main, service_main);
pub(crate) async fn rule_applier(
rule_file_path: &str,
shutdown_recv: &mut UnboundedReceiver<()>,
) -> anyhow::Result<()> {
// let wmi_con = WMIConnection::new(COMLibrary::new()?)?;
// Apply rules to all running processes
// let running_process: Vec<WinProcess> = wmi_con.async_query().await?;
// running_process.into_iter().for_each(|process| {
// let process_info: ProcessInfo = process.into();
// rule_set.apply(&process_info)
// });
tokio::select! {
// Apply rules to new processes
// output = monitor_new_processes(&rule_set, &wmi_con) => output,
// Or wait for shutdown signal
_ = shutdown_recv.recv() => {
println!("Shutting down process monitor");
Ok(())
}
}
}
#[cfg(windows)]
fn service_main(_: Vec<OsString>) {
use windows_service::service::{ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus};
let rt = Runtime::new().unwrap();
let (shutdown_send, mut shutdown_recv) = mpsc::unbounded_channel();
let event_handler = move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
ServiceControl::Stop => {
shutdown_send.send(()).unwrap();
ServiceControlHandlerResult::NoError
}
_ => ServiceControlHandlerResult::NotImplemented,
}
};
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler).unwrap();
status_handle
.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})
.unwrap();
let args = std::env::args().collect::<Vec<_>>();
let rules_path = args.get(2).map(|s| s.as_str()).unwrap_or("rules.json");
let error_code = if rt
.block_on(rule_applier(rules_path, &mut shutdown_recv))
.is_err()
{
1
} else {
0
};
status_handle
.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(error_code),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})
.unwrap();
}
#[cfg(windows)]
fn main() -> anyhow::Result<(), windows_service::Error> {
use log::{debug, info, LevelFilter};
use log4rs::{append::{console::ConsoleAppender, file::FileAppender}, config::{Appender, Logger, Root}, encode::pattern::PatternEncoder, Config};
let stdout = ConsoleAppender::builder().build();
let log_file_path = std::env::current_exe().unwrap().with_file_name("service.log");
let requests = FileAppender::builder()
.encoder(Box::new(PatternEncoder::default()))
.build(log_file_path)
.unwrap();
let config = Config::builder()
.appender(Appender::builder().build("stdout", Box::new(stdout)))
.appender(Appender::builder().build("requests", Box::new(requests)))
.logger(Logger::builder().build("app::backend::db", LevelFilter::Debug))
.logger(Logger::builder()
.appender("requests")
.additive(false)
.build("app::requests", LevelFilter::Info))
.build(Root::builder().appender("stdout").appender("requests").build(LevelFilter::Trace))
.unwrap();
let handle = log4rs::init_config(config).unwrap();
let args = std::env::args().collect::<Vec<_>>();
let command = args.get(1);
debug!("Service control executed with args: {:?}", args);
if let Some(command) = command {
match command.as_str() {
"install" => {
install_service(args.get(2).map(|s| s.as_str()))?;
info!("Service installed");
return Ok(());
}
"uninstall" => {
uninstall_service()?;
info!("Service uninstalled");
return Ok(());
}
"run" => {
info!("Running service (nil)");
}
_ => {
info!("Unknown command");
return Ok(());
}
}
}
println!("Starting service");
match service_dispatcher::start(SERVICE_NAME, ffi_service_main) {
Ok(_) => {}
Err(e) => {
println!("Error starting service: {:?}", e);
}
}
Ok(())
}
#[cfg(windows)]
fn install_service(rules_path: Option<&str>) -> windows_service::Result<()> {
let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
let service_binary_path = ::std::env::current_exe().unwrap();
let service_info = ServiceInfo {
name: SERVICE_NAME.into(),
display_name: SERVICE_NAME.into(),
service_type: ServiceType::OWN_PROCESS,
start_type: ServiceStartType::AutoStart,
error_control: ServiceErrorControl::Normal,
executable_path: service_binary_path,
launch_arguments: vec!["run".into()],
dependencies: vec![],
account_name: None, // run as System
account_password: None,
};
let service = service_manager.create_service(&service_info, ServiceAccess::CHANGE_CONFIG)?;
service.set_description(SERVICE_DESCRIPTION)?;
Ok(())
}
#[cfg(windows)]
fn uninstall_service() -> windows_service::Result<()> {
let manager_access = ServiceManagerAccess::CONNECT;
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
let service = service_manager.open_service(SERVICE_NAME, ServiceAccess::DELETE)?;
service.delete()?;
Ok(())
}

88
src/main.rs.bad Normal file
View File

@@ -0,0 +1,88 @@
#![windows_subsystem = "windows"]
use windows_sys::Win32::{
Foundation::{HWND, LRESULT},
Graphics::Gdi::{BeginPaint, EndPaint, FillRect, COLOR_WINDOW, HBRUSH, PAINTSTRUCT},
UI::WindowsAndMessaging::{
CreateWindowExA, DefWindowProcA, DispatchMessageA, GetMessageA, PostQuitMessage,
RegisterClassA, ShowWindow, TranslateMessage, CW_USEDEFAULT, MSG, SW_NORMAL, WM_DESTROY,
WM_PAINT, WNDCLASSA, WS_OVERLAPPED,
},
};
// use windows::Win32::{
// Foundation::{HWND, LRESULT},
// Graphics::Gdi::{BeginPaint, EndPaint, FillRect, COLOR_WINDOW, PAINTSTRUCT},
// UI::WindowsAndMessaging::{
// CreateWindowExA, DefWindowProcA, DispatchMessageA, GetMessageA, PostQuitMessage,
// RegisterClassA, ShowWindow, TranslateMessage, CW_USEDEFAULT, SW_NORMAL, WS_OVERLAPPED,
// },
// };
unsafe extern "system" fn wndproc(hwnd: HWND, msg: u32, wparam: usize, lparam: isize) -> LRESULT {
match msg {
WM_DESTROY => {
PostQuitMessage(0);
0
}
WM_PAINT => {
let mut ps: PAINTSTRUCT = std::mem::zeroed();
let hdc = BeginPaint(hwnd, &mut ps);
FillRect(hdc, &ps.rcPaint, (COLOR_WINDOW + 1) as HBRUSH);
EndPaint(hwnd, &ps);
0
}
_ => {
println!("msg: {}", msg);
DefWindowProcA(hwnd, msg, wparam, lparam)
},
}
}
unsafe fn create_window() -> HWND {
let class_name = b"gdi\0";
let wc = WNDCLASSA {
style: 0,
lpfnWndProc: Some(wndproc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: 0,
hIcon: 0,
hCursor: 0,
hbrBackground: 0,
lpszMenuName: std::ptr::null(),
lpszClassName: class_name.as_ptr(),
};
RegisterClassA(&wc);
let hwnd = CreateWindowExA(
0,
class_name.as_ptr(),
b"title\0".as_ptr(),
WS_OVERLAPPED,
CW_USEDEFAULT,
CW_USEDEFAULT,
400,
300,
0,
0,
0,
std::ptr::null(),
);
if hwnd == 0 {
panic!("create failed");
}
ShowWindow(hwnd, windows_sys::Win32::UI::WindowsAndMessaging::SW_HIDE);
hwnd
}
fn main() {
unsafe {
create_window();
let mut msg: MSG = std::mem::zeroed();
while GetMessageA(&mut msg, 0, 0, 0) > 0 {
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
}
}

93
src/main.rs.disabled2 Normal file
View File

@@ -0,0 +1,93 @@
use windows::Win32::Foundation::LRESULT;
use windows::Win32::{
Foundation::{HWND, LPARAM, WPARAM},
System::Power::POWERBROADCAST_SETTING,
};
use log::LevelFilter;
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Logger, Root};
use std::{thread::sleep, time::Duration};
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcA;
use log::debug;
#[cfg(windows)]
unsafe extern "system" fn wndproc(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
match message {
PBT_APMQUERYSUSPEND => {
debug!("PBT_APMQUERYSUSPEND");
LRESULT(0)
}
WM_POWERBROADCAST => {
debug!("WM_POWERBROADCAST: {:?} - {:?}", wparam, lparam);
if wparam == WPARAM(32787) {
let st: &mut POWERBROADCAST_SETTING =
&mut *(lparam.0 as *mut POWERBROADCAST_SETTING);
debug!("Power Setting: {:?}", st.PowerSetting);
// let guid = GUID::from(MONITOR_GUID);
// if st.PowerSetting == guid && st.Data == [0] {
// thread::spawn(|| send_mixed_input);
// let _ = registry_ops::RegistrySetting::new(
// &registry_ops::RegistryEntries::LastRobotInput,
// )
// .set_registry_data(&registry_ops::get_current_time());
// }
}
LRESULT(0)
}
_ => {
debug!("{} - {:?} - {:?}", message, wparam, lparam);
DefWindowProcA(window, message, wparam, lparam)
}
}
}
#[cfg(windows)]
fn main() -> () {
// Wait forever
let stdout = ConsoleAppender::builder().build();
let requests = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
.build("log/requests.log")
.unwrap();
let config = Config::builder()
.appender(Appender::builder().build("stdout", Box::new(stdout)))
.appender(Appender::builder().build("requests", Box::new(requests)))
.logger(Logger::builder().build("app::backend::db", LevelFilter::Debug))
.logger(Logger::builder()
.appender("requests")
.additive(false)
.build("app::requests", LevelFilter::Info))
.build(Root::builder().appender("stdout").build(LevelFilter::Trace))
.unwrap();
let handle = log4rs::init_config(config).unwrap();
loop {
sleep(Duration::from_secs(1000));
debug!("Hello, Windows!");
}
// use windows::Win32::UI::WindowsAndMessaging::{MessageBoxA, MB_OK};
// let _ = unsafe { MessageBoxA(
// None,
// "Hello, Windows!",
// "Hello, Windows!",
// MB_OK,
// ) };
}