use crate::{
capturer::{Area, Options, Point, Resolution, Size},
frame::{BGRAFrame, Frame, FrameType},
targets::{self, Target},
};
use ::windows::Win32::System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency};
use std::cmp;
use std::sync::mpsc;
use std::time::{Duration, SystemTime};
use windows_capture::{
capture::{CaptureControl, Context, GraphicsCaptureApiHandler},
frame::Frame as WCFrame,
graphics_capture_api::{GraphicsCaptureApi, InternalCaptureControl},
monitor::Monitor as WCMonitor,
settings::{
ColorFormat, CursorCaptureSettings, DirtyRegionSettings, DrawBorderSettings,
MinimumUpdateIntervalSettings, SecondaryWindowSettings, Settings as WCSettings,
},
window::Window as WCWindow,
};
#[derive(Debug)]
struct Capturer {
pub tx: mpsc::Sender<Frame>,
pub crop: Option<Area>,
pub start_time: (i64, SystemTime),
pub perf_freq: i64,
}
#[derive(Clone)]
enum Settings {
Window(WCSettings<FlagStruct, WCWindow>),
Display(WCSettings<FlagStruct, WCMonitor>),
}
pub struct WCStream {
settings: Settings,
capture_control: Option<CaptureControl<Capturer, Box<dyn std::error::Error + Send + Sync>>>,
}
impl GraphicsCaptureApiHandler for Capturer {
type Flags = FlagStruct;
type Error = Box<dyn std::error::Error + Send + Sync>;
fn new(context: Context<Self::Flags>) -> Result<Self, Self::Error> {
Ok(Self {
tx: context.flags.tx,
crop: context.flags.crop,
start_time: (
unsafe {
let mut time = 0;
QueryPerformanceCounter(&mut time)?;
time
},
SystemTime::now(),
),
perf_freq: unsafe {
let mut freq = 0;
QueryPerformanceFrequency(&mut freq)?;
freq
},
})
}
fn on_frame_arrived(
&mut self,
frame: &mut WCFrame,
_: InternalCaptureControl,
) -> Result<(), Self::Error> {
let elapsed = frame.timestamp().Duration - self.start_time.0;
let display_time = self
.start_time
.1
.checked_add(Duration::from_secs_f64(
elapsed as f64 / self.perf_freq as f64,
))
.unwrap();
match &self.crop {
Some(cropped_area) => {
let start_x = cropped_area.origin.x as u32;
let start_y = cropped_area.origin.y as u32;
let end_x = (cropped_area.origin.x + cropped_area.size.width) as u32;
let end_y = (cropped_area.origin.y + cropped_area.size.height) as u32;
let mut cropped_buffer = frame
.buffer_crop(start_x, start_y, end_x, end_y)
.expect("Failed to crop buffer");
let raw_frame_buffer = match cropped_buffer.as_nopadding_buffer() {
Ok(buffer) => buffer,
Err(_) => return Err(("Failed to get raw buffer").into()),
};
let bgr_frame = BGRAFrame {
display_time,
width: cropped_area.size.width as i32,
height: cropped_area.size.height as i32,
data: raw_frame_buffer.to_vec(),
};
let _ = self.tx.send(Frame::BGRA(bgr_frame));
}
None => {
let mut frame_buffer = frame.buffer().unwrap();
let raw_frame_buffer = frame_buffer.as_raw_buffer();
let frame_data = raw_frame_buffer.to_vec();
let bgr_frame = BGRAFrame {
display_time,
width: frame.width() as i32,
height: frame.height() as i32,
data: frame_data,
};
let _ = self.tx.send(Frame::BGRA(bgr_frame));
}
}
Ok(())
}
fn on_closed(&mut self) -> Result<(), Self::Error> {
println!("Closed");
Ok(())
}
}
impl WCStream {
pub fn start_capture(&mut self) {
let cc = match &self.settings {
Settings::Display(st) => Capturer::start_free_threaded(st.to_owned()).unwrap(),
Settings::Window(st) => Capturer::start_free_threaded(st.to_owned()).unwrap(),
};
self.capture_control = Some(cc)
}
pub fn stop_capture(&mut self) {
let capture_control = self.capture_control.take().unwrap();
let _ = capture_control.stop();
}
}
#[derive(Clone, Debug)]
struct FlagStruct {
pub tx: mpsc::Sender<Frame>,
pub crop: Option<Area>,
}
pub fn create_capturer(
options: &Options,
tx: mpsc::Sender<Frame>,
) -> Result<WCStream, Box<dyn std::error::Error + Send + Sync>> {
let target = options
.target
.clone()
.unwrap_or_else(|| Target::Display(targets::get_main_display()));
let color_format = match options.output_type {
FrameType::BGRAFrame => ColorFormat::Bgra8,
_ => ColorFormat::Rgba8,
};
let show_cursor = match options.show_cursor {
true => CursorCaptureSettings::WithCursor,
false => CursorCaptureSettings::WithoutCursor,
};
let draw_border = if GraphicsCaptureApi::is_border_settings_supported().unwrap_or(false) {
options
.show_highlight
.then_some(DrawBorderSettings::WithBorder)
.unwrap_or(DrawBorderSettings::WithoutBorder)
} else {
DrawBorderSettings::Default
};
let settings = match target {
Target::Display(display) => Settings::Display(WCSettings::new(
WCMonitor::from_raw_hmonitor(display.raw_handle.0),
show_cursor,
draw_border,
SecondaryWindowSettings::Default,
MinimumUpdateIntervalSettings::Default,
DirtyRegionSettings::Default,
color_format,
FlagStruct {
tx: tx.clone(),
crop: Some(get_crop_area(options)),
},
)),
Target::Window(window) => Settings::Window(WCSettings::new(
WCWindow::from_raw_hwnd(window.raw_handle.0),
show_cursor,
draw_border,
SecondaryWindowSettings::Default,
MinimumUpdateIntervalSettings::Default,
DirtyRegionSettings::Default,
color_format,
FlagStruct {
tx: tx.clone(),
crop: Some(get_crop_area(options)),
},
)),
};
Ok(WCStream {
settings,
capture_control: None,
})
}
pub fn get_output_frame_size(options: &Options) -> [u32; 2] {
let crop_area = get_crop_area(options);
let mut output_width = (crop_area.size.width) as u32;
let mut output_height = (crop_area.size.height) as u32;
match options.output_resolution {
Resolution::Captured => {}
_ => {
let [resolved_width, resolved_height] = options
.output_resolution
.value((crop_area.size.width as f32) / (crop_area.size.height as f32));
output_width = cmp::min(output_width, resolved_width);
output_height = cmp::min(output_height, resolved_height);
}
}
output_width -= output_width % 2;
output_height -= output_height % 2;
[output_width, output_height]
}
fn get_absolute_value(value: f64, _scale_factor: f64) -> f64 {
let value = (value).floor();
value + value % 2.0
}
pub fn get_crop_area(options: &Options) -> Area {
let target = options
.target
.clone()
.unwrap_or_else(|| Target::Display(targets::get_main_display()));
let (width, height) = targets::get_target_dimensions(&target);
let scale_factor = targets::get_scale_factor(&target);
options
.crop_area
.as_ref()
.map(|val| {
Area {
origin: Point {
x: get_absolute_value(val.origin.x, scale_factor),
y: get_absolute_value(val.origin.y, scale_factor),
},
size: Size {
width: get_absolute_value(val.size.width, scale_factor),
height: get_absolute_value(val.size.height, scale_factor),
},
}
})
.unwrap_or_else(|| Area {
origin: Point { x: 0.0, y: 0.0 },
size: Size {
width: width as f64,
height: height as f64,
},
})
}