record-daemon: initial commit

This commit is contained in:
2026-03-19 17:48:07 +01:00
commit d6c0334369
30 changed files with 9486 additions and 0 deletions

View File

@@ -0,0 +1,293 @@
//! Game capture source configuration.
use serde::{Deserialize, Serialize};
use tracing::{debug, info};
use crate::error::Result;
/// Game capture source for recording game footage.
#[derive(Debug, Clone)]
pub struct GameCapture {
/// Source name.
pub name: String,
/// Window title to capture (if specific window).
pub window: Option<String>,
/// Process name to capture.
pub process_name: Option<String>,
/// Capture mode.
pub mode: CaptureMode,
/// Whether to capture cursor.
pub capture_cursor: bool,
}
impl Default for GameCapture {
fn default() -> Self {
Self {
name: "Game Capture".to_string(),
window: None,
process_name: Some("League of Legends.exe".to_string()),
mode: CaptureMode::Any,
capture_cursor: false,
}
}
}
impl GameCapture {
/// Create a new game capture source.
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
..Default::default()
}
}
/// Set the window to capture.
pub fn with_window(mut self, window: &str) -> Self {
self.window = Some(window.to_string());
self.mode = CaptureMode::Window;
self
}
/// Set the process name to capture.
pub fn with_process(mut self, process: &str) -> Self {
self.process_name = Some(process.to_string());
self.mode = CaptureMode::Process;
self
}
/// Set whether to capture the cursor.
pub fn with_cursor(mut self, capture: bool) -> Self {
self.capture_cursor = capture;
self
}
/// Create the OBS source.
///
/// Note: This would create the actual obs_source_t in libobs.
pub fn create_source(&self) -> Result<CaptureSource> {
info!("Creating game capture source: {}", self.name);
// Note: Actual libobs source creation would happen here
// obs_source_create("game_capture", name, settings, nullptr)
let source = CaptureSource {
name: self.name.clone(),
source_type: SourceType::GameCapture,
active: false,
};
debug!("Game capture source created");
Ok(source)
}
}
/// Capture mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CaptureMode {
/// Capture any fullscreen application.
Any,
/// Capture a specific window.
Window,
/// Capture a specific process.
Process,
}
/// Capture source abstraction.
#[derive(Debug, Clone)]
pub struct CaptureSource {
/// Source name.
pub name: String,
/// Source type.
pub source_type: SourceType,
/// Whether the source is active.
pub active: bool,
}
impl CaptureSource {
/// Check if the source is active.
pub fn is_active(&self) -> bool {
self.active
}
/// Activate the source.
pub fn activate(&mut self) -> Result<()> {
if self.active {
return Ok(());
}
debug!("Activating capture source: {}", self.name);
// Note: Actual activation would involve obs_source_set_enabled
self.active = true;
Ok(())
}
/// Deactivate the source.
pub fn deactivate(&mut self) -> Result<()> {
if !self.active {
return Ok(());
}
debug!("Deactivating capture source: {}", self.name);
// Note: Actual deactivation would involve obs_source_set_enabled
self.active = false;
Ok(())
}
}
/// Source type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceType {
/// Game capture source.
GameCapture,
/// Window capture source.
WindowCapture,
/// Monitor capture source.
MonitorCapture,
}
/// Window capture source (alternative to game capture).
#[derive(Debug, Clone)]
pub struct WindowCapture {
/// Source name.
pub name: String,
/// Window title.
pub window_title: String,
/// Window class (X11).
pub window_class: Option<String>,
/// Whether to capture cursor.
pub capture_cursor: bool,
}
impl WindowCapture {
/// Create a new window capture source.
pub fn new(name: &str, window_title: &str) -> Self {
Self {
name: name.to_string(),
window_title: window_title.to_string(),
window_class: None,
capture_cursor: false,
}
}
/// Set the window class (for X11).
pub fn with_class(mut self, class: &str) -> Self {
self.window_class = Some(class.to_string());
self
}
/// Create the OBS source.
pub fn create_source(&self) -> Result<CaptureSource> {
info!(
"Creating window capture source: {} ({})",
self.name, self.window_title
);
let source = CaptureSource {
name: self.name.clone(),
source_type: SourceType::WindowCapture,
active: false,
};
Ok(source)
}
}
/// Monitor capture source (fallback).
#[derive(Debug, Clone)]
pub struct MonitorCapture {
/// Source name.
pub name: String,
/// Monitor index.
pub monitor: u32,
/// Whether to capture cursor.
pub capture_cursor: bool,
}
impl MonitorCapture {
/// Create a new monitor capture source.
pub fn new(name: &str, monitor: u32) -> Self {
Self {
name: name.to_string(),
monitor,
capture_cursor: false,
}
}
/// Create the OBS source.
pub fn create_source(&self) -> Result<CaptureSource> {
info!(
"Creating monitor capture source: {} (monitor {})",
self.name, self.monitor
);
let source = CaptureSource {
name: self.name.clone(),
source_type: SourceType::MonitorCapture,
active: false,
};
Ok(source)
}
}
/// Find the League of Legends game window.
pub fn find_league_window() -> Option<String> {
// Note: Actual window finding would use platform-specific APIs
// On Linux: X11/Wayland
// On Windows: Win32 API
#[cfg(target_os = "linux")]
{
// Would use x11rb or similar to find window
// For now, return the expected window title
Some("League of Legends (TM) Client".to_string())
}
#[cfg(target_os = "windows")]
{
// Would use FindWindowW
Some("League of Legends (TM) Client".to_string())
}
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_game_capture_creation() {
let capture = GameCapture::new("Test Capture");
assert_eq!(capture.name, "Test Capture");
assert_eq!(capture.mode, CaptureMode::Any);
}
#[test]
fn test_game_capture_with_process() {
let capture = GameCapture::new("Test").with_process("League of Legends.exe");
assert_eq!(
capture.process_name,
Some("League of Legends.exe".to_string())
);
assert_eq!(capture.mode, CaptureMode::Process);
}
#[test]
fn test_capture_source_activation() {
let mut source = CaptureSource {
name: "Test".to_string(),
source_type: SourceType::GameCapture,
active: false,
};
assert!(!source.is_active());
source.activate().unwrap();
assert!(source.is_active());
source.deactivate().unwrap();
assert!(!source.is_active());
}
}