From 3dd82dc66f3e4f48badb5087c1766900d83740e6 Mon Sep 17 00:00:00 2001 From: PVV Sanctuary Date: Sat, 20 Sep 2025 23:23:56 +0200 Subject: [PATCH] service --- Cargo.toml | 1 + src/main.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 133 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a983588..742d03d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" clap = { version = "4.5.47", features = ["derive"] } rand = "0.9.2" rodio = "0.21.1" +windows-service = "0.8.0" diff --git a/src/main.rs b/src/main.rs index 5830799..2e8b471 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,69 @@ +#![windows_subsystem = "windows"] use clap::Parser; +use rand::prelude::*; use rodio::Decoder; +use std::ffi::OsString; use std::{io::Cursor, path::Path, time::Duration}; +use windows_service::{ + Result, define_windows_service, + service::{ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType, ServiceType}, + service::{ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus}, + service_control_handler::{self, ServiceControlHandlerResult}, + service_dispatcher, + service_manager::{ServiceManager, ServiceManagerAccess}, +}; + +define_windows_service!(ffi_service_main, my_service_main); + +const SERVICE_NAME: &str = "interruptsfx"; + +fn my_service_main(arguments: Vec) { + if let Err(e) = run_service(arguments) { + eprintln!("Service failed: {:?}", e); + } +} #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { - #[arg(short, long, default_value_t = false)] - dynamic_sounds: bool, - #[arg(long, default_value_t = 60 * 4)] + #[arg(long, default_value_t = 0.1)] + volume: f32, + #[arg(long, default_value_t = 60 * 30)] min_time_interval_seconds: u64, - #[arg(long, default_value_t = 60 * 15)] + #[arg(long, default_value_t = 60 * 60 * 3)] max_time_interval_seconds: u64, - #[arg(long, default_value_t = String::from("./custom_sounds"))] + #[arg(long, default_value_t = String::from(r"C:\Users\Pleb\AppData\Local\Larian Studios\Baldur's Gate 3\Sounds"))] custom_sounds_directory: String, } fn main() { - let args = Args::parse(); - play_audio(&args); + if std::env::args().any(|arg| arg == "install") { + install_service(); + return; + } + + if std::env::args().any(|arg| arg == "uninstall") { + uninstall_service(); + return; + } + if let Err(e) = service_dispatcher::start(SERVICE_NAME, ffi_service_main) { + eprintln!("Failed to start service: {:?}", e); + } } -fn play_audio(args: &Args) { - use rand::prelude::*; - let mut rng = rand::rng(); +struct AudioData { + data: Vec>>, + rng: ThreadRng, + sink: rodio::Sink, +} + +fn setup_audio(args: &Args) -> AudioData { + let rng = rand::rng(); let stream_handle = rodio::OutputStreamBuilder::open_default_stream().expect("open default audio stream"); - let sink = rodio::Sink::connect_new(&stream_handle.mixer()); + let sink = rodio::Sink::connect_new(stream_handle.mixer()); + sink.set_volume(args.volume); let mut data = vec![ Cursor::new( include_bytes!("../sounds/35-roblox-death-sound-variations-in-60-seconds-pt.mp3") @@ -69,7 +106,7 @@ fn play_audio(args: &Args) { Cursor::new(include_bytes!("../sounds/whistle_RjClohy.mp3").to_vec()), ]; - if args.dynamic_sounds { + if args.custom_sounds_directory.as_str() != "none" { let path = Path::new(&args.custom_sounds_directory); for item in path.read_dir().unwrap() { let item = item.unwrap(); @@ -82,13 +119,91 @@ fn play_audio(args: &Args) { } } + AudioData { data, rng, sink } +} + +fn play_audio(args: &Args, audio_data: &mut AudioData) { loop { - let i = rng.random_range(0..data.len()); - let source = Decoder::try_from(data[i].clone()).unwrap(); - sink.append(source); - sink.sleep_until_end(); - std::thread::sleep(Duration::from_secs(rng.random_range( + std::thread::sleep(Duration::from_secs(audio_data.rng.random_range( args.min_time_interval_seconds..args.max_time_interval_seconds, ))); + let i = audio_data.rng.random_range(0..audio_data.data.len()); + let source = Decoder::try_from(audio_data.data[i].clone()).unwrap(); + audio_data.sink.append(source); + audio_data.sink.sleep_until_end(); } } + +pub fn install_service() { + println!("starting to install"); + let manager = + ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CREATE_SERVICE).unwrap(); + let service_info = ServiceInfo { + name: SERVICE_NAME.into(), + display_name: "interruptsfx".into(), + service_type: ServiceType::OWN_PROCESS, + start_type: ServiceStartType::AutoStart, + error_control: ServiceErrorControl::Normal, + executable_path: std::env::current_exe().unwrap(), + launch_arguments: vec![], + dependencies: vec![], + account_name: None, // Local system account + account_password: None, + }; + manager + .create_service(&service_info, ServiceAccess::START) + .unwrap(); + println!("Service installed successfully"); +} +pub fn uninstall_service() { + println!("starting to uninstall"); + let manager = + ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT).unwrap(); + let service = manager + .open_service(SERVICE_NAME, ServiceAccess::DELETE) + .unwrap(); + service.delete().unwrap(); + println!("Service uninstalled successfully"); +} + +fn run_service(args: Vec) -> Result<()> { + let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel(); + let status_handle = + service_control_handler::register( + SERVICE_NAME, + move |control_event| match control_event { + ServiceControl::Stop => { + shutdown_tx.send(()).unwrap(); + ServiceControlHandlerResult::NoError + } + _ => ServiceControlHandlerResult::NotImplemented, + }, + )?; + 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::from_secs(10), + process_id: None, + })?; + let args = Args::parse_from(args); + let mut audio_data = setup_audio(&args); + loop { + play_audio(&args, &mut audio_data); + if shutdown_rx.try_recv().is_ok() { + break; + } + } + 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::from_secs(0), + process_id: None, + })?; + Ok(()) +}