mirror of
https://github.com/Xevion/smart-rgb.git
synced 2025-12-05 23:16:23 -06:00
Initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
14
.vscode/settings.json
vendored
Normal file
14
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"APMQUERYSUSPEND",
|
||||
"appender",
|
||||
"HWND",
|
||||
"LPARAM",
|
||||
"LRESULT",
|
||||
"POWERBROADCAST",
|
||||
"simplelog",
|
||||
"USEDEFAULT",
|
||||
"wndproc",
|
||||
"WPARAM"
|
||||
]
|
||||
}
|
||||
1302
Cargo.lock
generated
Normal file
1302
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
57
Cargo.toml
Normal file
57
Cargo.toml
Normal 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
14
README.md
Normal 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
32
STORY.md
Normal 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
196
src/main.rs
Normal 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
88
src/main.rs.bad
Normal 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
93
src/main.rs.disabled2
Normal 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(
|
||||
// ®istry_ops::RegistryEntries::LastRobotInput,
|
||||
// )
|
||||
// .set_registry_data(®istry_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,
|
||||
// ) };
|
||||
}
|
||||
Reference in New Issue
Block a user