Add Mshroom machine implementation
[temp2RGB.git] / src / main.rs
index 5f6e55a..ed4eccb 100644 (file)
-#[macro_use]
-extern crate windows_service;
-
-use std::{
-    collections::HashMap,
-    env,
-    ffi::OsString,
-    sync::{
-        atomic::{AtomicBool, Ordering},
-        Arc,
-    },
-    thread::sleep,
-    time::{self, Duration},
-};
-
-use anyhow::Result;
-use log::{error, info, trace, warn, debug};
-use windows::Win32::Foundation::{ERROR_SERVICE_DOES_NOT_EXIST, WIN32_ERROR};
-use windows_service::{
-    service::{
-        ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, ServiceExitCode,
-        ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, ServiceType,
-    },
-    service_control_handler::{self, ServiceControlHandlerResult, ServiceStatusHandle},
-    service_dispatcher,
-    service_manager::{ServiceManager, ServiceManagerAccess},
-};
-use wmi::{COMLibrary, Variant, WMIConnection};
-
-use crate::rgb::RGB;
-
-define_windows_service!(ffi_service_main, service_main);
-
-mod winring0 {
-    #![allow(warnings, unused)]
-    include!(concat!(env!("OUT_DIR"), "/ols_api.rs"));
-}
-
-mod intel_arc {
-    #![allow(warnings, unused)]
-    include!(concat!(env!("OUT_DIR"), "/intel_arc.rs"));
-}
-
-mod a770;
-mod b650_e;
-mod machine;
-mod main_loop;
-// mod common;
-mod consts;
-mod corsair_vengeance;
-mod piix4_i2c;
-mod rgb;
-// mod roccat; Disabled.
-mod sensors_jiji;
-mod settings;
-mod timer;
-
-fn main() -> Result<()> {
-    let is_debug = cfg!(debug_assertions);
-
-    flexi_logger::Logger::try_with_str(if is_debug { "debug" } else { "info" })?
-        .log_to_file(
-            flexi_logger::FileSpec::default()
-                .directory(dirs::config_dir().unwrap().join(consts::SERVICE_NAME))
-                .basename(consts::SERVICE_NAME),
-        )
-        .duplicate_to_stdout(flexi_logger::Duplicate::All)
-        .format(if is_debug { flexi_logger::default_format } else { flexi_logger::detailed_format })
-        .rotate(
-            flexi_logger::Criterion::Size(1024 * 1024),
-            flexi_logger::Naming::Timestamps,
-            flexi_logger::Cleanup::KeepLogFiles(10),
-        )
-        .print_message()
-        .start()?;
-
-    let args: Vec<String> = env::args().collect();
-
-    info!("Temperature to RGB");
-
-    if args.contains(&"--no-service".to_string()) {
-        let completed: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
-        main_loop::main_loop(completed.clone());
-    } else if args.contains(&"--tests".to_string()) {
-        tests();
-    } else if args.contains(&"--install-service".to_string()) {
-        println!("Installing service...");
-        install_service()?;
-    } else if args.contains(&"--uninstall-service".to_string()) {
-        println!("Uninstalling service...");
-        uninstall_service()?;
-    } else {
-        service_dispatcher::start(consts::SERVICE_NAME, ffi_service_main)?;
-    }
-
-    Ok(())
-}
-
-fn install_service() -> 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()
-        .with_file_name("temp_2_rgb.exe");
-
-    println!("Installing service: {service_binary_path:?}");
-
-    let service_info = ServiceInfo {
-        name: OsString::from(consts::SERVICE_NAME),
-        display_name: OsString::from(consts::SERVICE_NAME),
-        service_type: ServiceType::OWN_PROCESS,
-        start_type: ServiceStartType::AutoStart,
-        error_control: ServiceErrorControl::Normal,
-        executable_path: service_binary_path,
-        launch_arguments: vec![],
-        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(
-        "A service to set the color of hardware according to the temperature of GPU and CPU",
-    )?;
-    Ok(())
-}
-
-fn uninstall_service() -> windows_service::Result<()> {
-    let manager_access = ServiceManagerAccess::CONNECT;
-    let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
-
-    let service_access = ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE;
-    let service = service_manager.open_service(consts::SERVICE_NAME, service_access)?;
-
-    // The service will be marked for deletion as long as this function call succeeds.
-    // However, it will not be deleted from the database until it is stopped and all open handles to it are closed.
-    service.delete()?;
-
-    // Our handle to it is not closed yet. So we can still query it.
-    if service.query_status()?.current_state != ServiceState::Stopped {
-        // If the service cannot be stopped, it will be deleted when the system restarts.
-        service.stop()?;
-    }
-
-    // Explicitly close our open handle to the service. This is automatically called when `service` goes out of scope.
-    drop(service);
-
-    // Win32 API does not give us a way to wait for service deletion.
-    // To check if the service is deleted from the database, we have to poll it ourselves.
-    let start = time::Instant::now();
-    let timeout = Duration::from_secs(5);
-    while start.elapsed() < timeout {
-        if let Err(windows_service::Error::Winapi(e)) =
-            service_manager.open_service(consts::SERVICE_NAME, ServiceAccess::QUERY_STATUS)
-        {
-            let WIN32_ERROR(error_num) = ERROR_SERVICE_DOES_NOT_EXIST;
-            if e.raw_os_error() == Some(error_num as i32) {
-                println!("{} is deleted.", consts::SERVICE_NAME);
-                return Ok(());
-            }
-        }
-        sleep(Duration::from_secs(1));
-    }
-    println!("{} is marked for deletion.", consts::SERVICE_NAME);
-
-    Ok(())
-}
-
-fn service_main(arguments: Vec<OsString>) {
-    if let Err(error) = run_service(arguments) {
-        println!("Error: {error}");
-    }
-}
-
-fn run_service(_arguments: Vec<OsString>) -> Result<(), windows_service::Error> {
-    let completed: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
-
-    let completed_event_handler = Arc::clone(&completed);
-    let event_handler = move |control_event| -> ServiceControlHandlerResult {
-        match control_event {
-            ServiceControl::Stop => {
-                completed_event_handler.store(true, Ordering::Relaxed);
-                // Handle stop event and return control back to the system.
-                ServiceControlHandlerResult::NoError
-            }
-            ServiceControl::Shutdown => {
-                completed_event_handler.store(true, Ordering::Relaxed);
-                // Handle stop event and return control back to the system.
-                ServiceControlHandlerResult::NoError
-            }
-            // ServiceControl::Preshutdown => {
-            //     completed_event_handler.store(true, Ordering::Relaxed);
-            //     ServiceControlHandlerResult::NoError
-            // }
-            // ServiceControl::PowerEvent(param) => {
-            //     ServiceControlHandlerResult::NotImplemented
-            // }
-            // All services must accept Interrogate even if it's a no-op.
-            ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
-            _ => ServiceControlHandlerResult::NotImplemented,
-        }
-    };
-
-    // Register system service event handler
-    let status_handle = service_control_handler::register(consts::SERVICE_NAME, event_handler)?;
-
-    let running_status = ServiceStatus {
-        // Should match the one from system service registry
-        service_type: ServiceType::OWN_PROCESS,
-
-        // The new state
-        current_state: ServiceState::Running,
-
-        // Accept stop events when running
-        controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,
-
-        // Used to report an error when starting or stopping only, otherwise must be zero
-        exit_code: ServiceExitCode::Win32(0),
-
-        // Only used for pending states, otherwise must be zero
-        checkpoint: 0,
-
-        // Only used for pending states, otherwise must be zero
-        wait_hint: Duration::default(),
-
-        process_id: None,
-    };
-
-    status_handle.set_service_status(running_status)?;
-
-    main_loop::main_loop(completed.clone());
-
-    status_handle.set_service_status(ServiceStatus {
-        service_type: ServiceType::OWN_PROCESS,
-        current_state: ServiceState::Stopped,
-        controls_accepted: ServiceControlAccept::empty(),
-        exit_code: ServiceExitCode::Win32(0),
-        checkpoint: 0,
-        wait_hint: Duration::default(),
-        process_id: None,
-    })?;
-
-    info!("Main loop stopped: Temperature to RGB will now shut down");
-
-    Ok(())
-}
-
-fn tests() {
-    println!("Running some tests...");
-
-    // test_b650_e();
-    // list_usb_devices();
-    // test_roccat();
-    // test_wmi();
-    // test_corsair();
-    test_a770();
-    // test_read_temp();
-
-    println!("Press any key to continue...");
-    std::io::stdin().read_line(&mut String::new()).unwrap();
-}
-
-fn test_wmi() {
-    let com_con = COMLibrary::new().unwrap();
-    let wmi_con = WMIConnection::new(com_con.into()).unwrap();
-
-    //let results: Vec<HashMap<String, Variant>> = wmi_con.raw_query("SELECT * FROM Win32_PnPSignedDriver WHERE Description LIKE '%SMBUS%' OR Description LIKE '%SM BUS%'").unwrap();
-    //let results: Vec<HashMap<String, Variant>> = wmi_con.raw_query("SELECT * FROM Win32_PnPSignedDriver WHERE Description LIKE 'Intel(R) NF I2C Host Controller'").unwrap();
-    let results: Vec<HashMap<String, Variant>> = wmi_con
-        .raw_query("SELECT * FROM Win32_PnPSignedDriver")
-        .unwrap();
-    //let results: Vec<HashMap<String, Variant>> = wmi_con.raw_query("SELECT * FROM Win32_PnPAllocatedResource").unwrap();
-
-    for os in results {
-        println!("-------------------");
-        println!("{:#?}", os);
-    }
-}
-fn list_usb_devices() {
-    let api = hidapi::HidApi::new().unwrap();
-    for device in api.device_list() {
-        println!("{:?}", device);
-        println!("name: {}", device.product_string().unwrap());
-        println!("interface number: {}", device.interface_number());
-        println!("page: {}", device.usage_page());
-        println!("usage: {}", device.usage());
-        println!("----");
-    }
-}
-
-// fn test_roccat() {
-//     let api = hidapi::HidApi::new().unwrap();
-//     let roccat_device = roccat::get_device(&api);
-
-//     let manufacturer = roccat_device.get_manufacturer_string().unwrap();
-//     dbg!(manufacturer);
-
-//     let product = roccat_device.get_product_string().unwrap();
-//     dbg!(product);
-
-//     let serial = roccat_device.get_serial_number_string().unwrap();
-//     dbg!(serial);
-
-//     roccat::init(&roccat_device);
-//     roccat::set_color(
-//         &roccat_device,
-//         &RGB {
-//             red: 0,
-//             green: 255,
-//             blue: 40,
-//         },
-//     );
-// }
-
-fn test_b650_e() {
-    let api = hidapi::HidApi::new().unwrap();
-
-    let b650e_device = b650_e::get_device(&api);
-
-    println!("Firmware: {}", b650_e::get_firmware_string(&b650e_device));
-
-    let configuration = b650_e::get_configuration_table(&b650e_device);
-    println!("Configuration:");
-    for i in 0..60 {
-        print!("{:02X} ", configuration[i]);
-        if (i + 1) % 6 == 0 {
-            println!("");
-        }
-    }
-
-    // Only once, at start.
-    b650_e::set_fixed_mode(&b650e_device);
-
-    b650_e::set_color(
-        &b650e_device,
-        &RGB {
-            red: 255,
-            green: 0,
-            blue: 0,
-        },
-    );
-    b650_e::save_current_color(&b650e_device);
-}
-
-fn test_corsair() {
-    let corsair_controllers = [
-        corsair_vengeance::Controller::new(0x19),
-        corsair_vengeance::Controller::new(0x1B),
-    ];
-    for controller in corsair_controllers {
-        controller.set_color(&RGB {
-            red: 255,
-            green: 0,
-            blue: 0,
-        });
-    }
-}
-
-fn test_a770() {
-    // a770::set_rgb(255, 0, 0);
-    let mut a770 = a770::A770::new();
-    a770.set_color(255, 0, 0);
-}
-
-fn test_read_temp() {
-    let sensors = sensors_jiji::Sensors::new();
-    println!("temp cpu: {}", sensors.read_cpu_temp());
-    println!("temp gpu: {}", sensors.read_gpu_temp());
-}
+#[macro_use]\r
+extern crate windows_service;\r
+\r
+use std::{\r
+    ffi::OsString,\r
+    sync::{\r
+        Arc,\r
+        atomic::{AtomicBool, Ordering},\r
+    },\r
+    thread::sleep,\r
+    time::{self, Duration},\r
+};\r
+\r
+use anyhow::Result;\r
+use clap::Parser;\r
+use log::{error, info};\r
+use windows::Win32::Foundation::{ERROR_SERVICE_DOES_NOT_EXIST, WIN32_ERROR};\r
+use windows_service::{\r
+    service::{\r
+        ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, ServiceExitCode,\r
+        ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, ServiceType,\r
+    },\r
+    service_control_handler::{self, ServiceControlHandlerResult},\r
+    service_dispatcher,\r
+    service_manager::{ServiceManager, ServiceManagerAccess},\r
+};\r
+\r
+define_windows_service!(ffi_service_main, service_main);\r
+\r
+mod wrapper_winring0 {\r
+    #![allow(warnings, unused)]\r
+    include!(concat!(env!("OUT_DIR"), "/ols_api.rs"));\r
+}\r
+// mod intel_arc {\r
+//     #![allow(warnings, unused)]\r
+//     include!(concat!(env!("OUT_DIR"), "/intel_arc.rs"));\r
+// }\r
+mod a770;\r
+mod asus_aura_usb;\r
+mod consts;\r
+mod corsair_lighting_pro;\r
+mod corsair_vengeance;\r
+mod gigabyte_rgb_fusion_usb;\r
+mod lian_li_sl_infinity;\r
+mod machine;\r
+mod main_loop;\r
+mod piix4_i2c;\r
+mod rgb;\r
+mod winring0;\r
+// mod roccat; Disabled.\r
+mod cpu_temperature;\r
+mod settings;\r
+mod tests;\r
+mod timer;\r
+\r
+#[derive(Parser, Debug)]\r
+#[command(\r
+    author = "Greg Burri",\r
+    version = "1.0",\r
+    about = "Set RGB according to CPU and GPU temperaturess"\r
+)]\r
+struct Args {\r
+    /// Launch without service.\r
+    #[arg(group = "main", long)]\r
+    no_service: bool,\r
+\r
+    /// Run tests.\r
+    #[arg(group = "main", long)]\r
+    tests: bool,\r
+\r
+    /// Install driver winring0.\r
+    #[arg(group = "main", long)]\r
+    install_winring0: bool,\r
+\r
+    /// Install the service.\r
+    #[arg(group = "main", long)]\r
+    install_service: bool,\r
+\r
+    /// Uninstall the service.\r
+    #[arg(group = "main", long)]\r
+    uninstall_service: bool,\r
+}\r
+\r
+// Important: when starting as a service, the directory where the log and config files\r
+// are put is 'C:\Windows\System32\config\systemprofile\AppData\Roaming\Temp2RGB'.\r
+fn main() -> Result<()> {\r
+    let is_debug = cfg!(debug_assertions);\r
+\r
+    flexi_logger::Logger::try_with_str(if is_debug { "debug" } else { "info" })?\r
+        .log_to_file(\r
+            flexi_logger::FileSpec::default()\r
+                .directory(dirs::config_dir().unwrap().join(consts::SERVICE_NAME))\r
+                .basename(consts::SERVICE_NAME),\r
+        )\r
+        .duplicate_to_stdout(flexi_logger::Duplicate::All)\r
+        .format(if is_debug {\r
+            flexi_logger::default_format\r
+        } else {\r
+            flexi_logger::detailed_format\r
+        })\r
+        .rotate(\r
+            flexi_logger::Criterion::Size(1024 * 1024),\r
+            flexi_logger::Naming::Timestamps,\r
+            flexi_logger::Cleanup::KeepLogFiles(10),\r
+        )\r
+        .print_message()\r
+        .start()?;\r
+\r
+    log_panics::init();\r
+\r
+    let args = Args::parse();\r
+\r
+    info!("Temperature to RGB");\r
+\r
+    if args.no_service {\r
+        let completed: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));\r
+        main_loop::main_loop(completed.clone());\r
+    } else if args.tests {\r
+        tests::tests();\r
+    } else if args.install_winring0 {\r
+        println!("Installing winring0 service...");\r
+        install_winring0()?;\r
+    } else if args.install_service {\r
+        println!("Installing service...");\r
+        install_service()?;\r
+    } else if args.uninstall_service {\r
+        println!("Uninstalling service...");\r
+        uninstall_service()?;\r
+    } else {\r
+        service_dispatcher::start(consts::SERVICE_NAME, ffi_service_main)?;\r
+    }\r
+\r
+    Ok(())\r
+}\r
+\r
+fn install_winring0() -> windows_service::Result<()> {\r
+    let system_dir = unsafe {\r
+        let mut system_dir = [0u8; windows::Win32::Foundation::MAX_PATH as usize];\r
+        let l =\r
+            windows::Win32::System::SystemInformation::GetSystemDirectoryA(Some(&mut system_dir))\r
+                as usize;\r
+        assert_ne!(l, 0);\r
+        String::from_utf8(system_dir[0..l].into()).unwrap()\r
+    };\r
+    // TODO: to const.\r
+    let winring0_filename = "WinRing0x64.sys";\r
+    let driver_name = "WinRing0x64";\r
+\r
+    let winring0_path = std::env::current_exe()\r
+        .unwrap()\r
+        .with_file_name(winring0_filename);\r
+\r
+    let destination = std::path::Path::new(&system_dir)\r
+        .join("drivers")\r
+        .join(winring0_filename);\r
+\r
+    std::fs::copy(winring0_path, &destination).unwrap();\r
+\r
+    let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;\r
+    let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;\r
+\r
+    // println!("Installing service: {service_binary_path:?}");\r
+\r
+    let service_info = ServiceInfo {\r
+        name: OsString::from(driver_name),\r
+        display_name: OsString::from(driver_name),\r
+        service_type: ServiceType::KERNEL_DRIVER,\r
+        start_type: ServiceStartType::AutoStart,\r
+        error_control: ServiceErrorControl::Normal,\r
+        executable_path: destination,\r
+        launch_arguments: vec![],\r
+        dependencies: vec![],\r
+        account_name: None, // run as System\r
+        account_password: None,\r
+    };\r
+\r
+    let service = service_manager.create_service(&service_info, ServiceAccess::CHANGE_CONFIG)?;\r
+    service.set_description("Winring0")?;\r
+\r
+    Ok(())\r
+}\r
+\r
+fn install_service() -> windows_service::Result<()> {\r
+    let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;\r
+    let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;\r
+\r
+    let service_binary_path = std::env::current_exe()\r
+        .unwrap()\r
+        .with_file_name("temp_2_rgb.exe");\r
+\r
+    println!("Installing service: {service_binary_path:?}");\r
+\r
+    let service_info = ServiceInfo {\r
+        name: OsString::from(consts::SERVICE_NAME),\r
+        display_name: OsString::from(consts::SERVICE_NAME),\r
+        service_type: ServiceType::OWN_PROCESS,\r
+        start_type: ServiceStartType::AutoStart,\r
+        error_control: ServiceErrorControl::Normal,\r
+        executable_path: service_binary_path,\r
+        launch_arguments: vec![],\r
+        dependencies: vec![],\r
+        account_name: None, // run as System\r
+        account_password: None,\r
+    };\r
+\r
+    let service = service_manager.create_service(&service_info, ServiceAccess::CHANGE_CONFIG)?;\r
+    service.set_description(\r
+        "A service to set the color of hardware according to the temperature of GPU and CPU",\r
+    )?;\r
+\r
+    Ok(())\r
+}\r
+\r
+fn uninstall_service() -> windows_service::Result<()> {\r
+    let manager_access = ServiceManagerAccess::CONNECT;\r
+    let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;\r
+\r
+    let service_access = ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE;\r
+    let service = service_manager.open_service(consts::SERVICE_NAME, service_access)?;\r
+\r
+    // The service will be marked for deletion as long as this function call succeeds.\r
+    // However, it will not be deleted from the database until it is stopped and all open handles to it are closed.\r
+    service.delete()?;\r
+\r
+    // Our handle to it is not closed yet. So we can still query it.\r
+    if service.query_status()?.current_state != ServiceState::Stopped {\r
+        // If the service cannot be stopped, it will be deleted when the system restarts.\r
+        service.stop()?;\r
+    }\r
+\r
+    // Explicitly close our open handle to the service. This is automatically called when `service` goes out of scope.\r
+    drop(service);\r
+\r
+    // Win32 API does not give us a way to wait for service deletion.\r
+    // To check if the service is deleted from the database, we have to poll it ourselves.\r
+    let start = time::Instant::now();\r
+    let timeout = Duration::from_secs(5);\r
+    while start.elapsed() < timeout {\r
+        if let Err(windows_service::Error::Winapi(e)) =\r
+            service_manager.open_service(consts::SERVICE_NAME, ServiceAccess::QUERY_STATUS)\r
+        {\r
+            let WIN32_ERROR(error_num) = ERROR_SERVICE_DOES_NOT_EXIST;\r
+            if e.raw_os_error() == Some(error_num as i32) {\r
+                println!("{} is deleted.", consts::SERVICE_NAME);\r
+                return Ok(());\r
+            }\r
+        }\r
+        sleep(Duration::from_secs(1));\r
+    }\r
+    println!("{} is marked for deletion.", consts::SERVICE_NAME);\r
+\r
+    Ok(())\r
+}\r
+\r
+fn service_main(arguments: Vec<OsString>) {\r
+    if let Err(error) = run_service(arguments) {\r
+        error!("{error}");\r
+    }\r
+}\r
+\r
+fn run_service(_arguments: Vec<OsString>) -> Result<(), windows_service::Error> {\r
+    let completed: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));\r
+\r
+    let completed_event_handler = Arc::clone(&completed);\r
+\r
+    info!("Setup the event handler...");\r
+\r
+    let event_handler = move |control_event| -> ServiceControlHandlerResult {\r
+        match control_event {\r
+            ServiceControl::Stop => {\r
+                completed_event_handler.store(true, Ordering::Relaxed);\r
+                // Handle stop event and return control back to the system.\r
+                ServiceControlHandlerResult::NoError\r
+            }\r
+            ServiceControl::Shutdown => {\r
+                completed_event_handler.store(true, Ordering::Relaxed);\r
+                // Handle stop event and return control back to the system.\r
+                ServiceControlHandlerResult::NoError\r
+            }\r
+            // ServiceControl::Preshutdown => {\r
+            //     completed_event_handler.store(true, Ordering::Relaxed);\r
+            //     ServiceControlHandlerResult::NoError\r
+            // }\r
+            // ServiceControl::PowerEvent(param) => {\r
+            //     ServiceControlHandlerResult::NotImplemented\r
+            // }\r
+            // All services must accept Interrogate even if it's a no-op.\r
+            ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,\r
+            _ => ServiceControlHandlerResult::NotImplemented,\r
+        }\r
+    };\r
+\r
+    // Register system service event handler\r
+    let status_handle = service_control_handler::register(consts::SERVICE_NAME, event_handler)?;\r
+\r
+    status_handle.set_service_status(ServiceStatus {\r
+        service_type: ServiceType::OWN_PROCESS,\r
+        current_state: ServiceState::Running,\r
+        controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,\r
+        exit_code: ServiceExitCode::Win32(0),\r
+        checkpoint: 0,\r
+        wait_hint: Duration::default(),\r
+        process_id: None, //Some(std::process::id()),\r
+    })?;\r
+\r
+    main_loop::main_loop(completed.clone());\r
+\r
+    status_handle.set_service_status(ServiceStatus {\r
+        service_type: ServiceType::OWN_PROCESS,\r
+        current_state: ServiceState::Stopped,\r
+        controls_accepted: ServiceControlAccept::empty(),\r
+        exit_code: ServiceExitCode::Win32(0),\r
+        checkpoint: 0,\r
+        wait_hint: Duration::default(),\r
+        process_id: None, //Some(std::process::id()),\r
+    })?;\r
+\r
+    info!("Main loop stopped: Temperature to RGB will now shut down");\r
+\r
+    Ok(())\r
+}\r