fix(desktop): harden capture and release packaging (#2049)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-27 23:54:38 +02:00
1 parent 5ee59c4675
commit 374db9ed2b
25 files changed
+1394 -1317

No files matched your search

@@ -8,6 +8,10 @@ publish = false
[workspace]
resolver = "2"
[features]
default = []
game-capture-hook = []
[lib]
crate-type = ["cdylib", "rlib"]
@@ -18,19 +18,7 @@
"index.d.ts",
"loader-diagnostics.cjs",
"win-game-capture.win32-x64-msvc.node",
"win-game-capture.win32-arm64-msvc.node",
"fluxer-game-hook.win32-x64-msvc.dll",
"fluxer-game-hook.win32-ia32-msvc.dll",
"fluxer-game-hook.win32-arm64-msvc.dll",
"fluxer-inject-helper.win32-x64-msvc.exe",
"fluxer-inject-helper.win32-ia32-msvc.exe",
"fluxer-inject-helper.win32-arm64-msvc.exe",
"fluxer-vulkan-layer.win32-x64-msvc.dll",
"fluxer-vulkan-layer.win32-x64-msvc.json",
"fluxer-vulkan-layer.win32-ia32-msvc.dll",
"fluxer-vulkan-layer.win32-ia32-msvc.json",
"fluxer-vulkan-layer.win32-arm64-msvc.dll",
"fluxer-vulkan-layer.win32-arm64-msvc.json"
"win-game-capture.win32-arm64-msvc.node"
],
"scripts": {
"build": "cargo run --locked --quiet --manifest-path ../../../tools/ci/Cargo.toml -- build-desktop-native-addon",
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::ptr::{null, null_mut};
use windows_sys::Win32::{
Foundation::{HWND, LPARAM, RECT},
Graphics::Gdi::{
EnumDisplayMonitors, GetMonitorInfoW, HMONITOR, MONITOR_DEFAULTTONEAREST, MONITORINFO,
MonitorFromRect,
},
System::Threading::GetCurrentProcessId,
UI::WindowsAndMessaging::{
EnumWindows, GW_OWNER, GWL_STYLE, GetForegroundWindow, GetWindow, GetWindowLongPtrW,
GetWindowRect, GetWindowThreadProcessId, IsWindow, IsWindowVisible, WS_BORDER, WS_MAXIMIZE,
},
};
const MONITOR_LIMIT: usize = 16;
fn parse_hwnd_source_id(source_id: &str) -> Option<HWND> {
let token = source_id.strip_prefix("window:")?.split(':').next()?;
let value = if let Some(hex) = token
.strip_prefix("0x")
.or_else(|| token.strip_prefix("0X"))
{
isize::from_str_radix(hex, 16).ok()?
} else {
token.parse::<isize>().ok()?
};
let hwnd = value as HWND;
(unsafe { IsWindow(hwnd) } != 0).then_some(hwnd)
}
fn parse_screen_ordinal(source_id: &str) -> Option<usize> {
let token = source_id.strip_prefix("screen:")?.split(':').next()?;
token.parse::<usize>().ok()
}
pub(crate) fn resolve_game_capture_target(
source_id: &str,
source_kind: &str,
) -> Result<HWND, String> {
if let Some(hwnd) = parse_hwnd_source_id(source_id) {
return Ok(hwnd);
}
if source_kind != "game" && source_kind != "screen" {
return Err(format!(
"invalid game capture source: {source_kind}:{source_id}"
));
}
let monitor = monitor_for_screen_source(source_id)?;
find_fullscreen_window_on_monitor(monitor)
.or_else(find_foreground_fullscreen_window)
.or_else(find_fullscreen_window_on_any_monitor)
.ok_or_else(|| "no fullscreen game window found on selected display".to_string())
}
fn monitor_for_screen_source(source_id: &str) -> Result<HMONITOR, String> {
let ordinal = parse_screen_ordinal(source_id).unwrap_or(0);
let monitors = enumerate_monitors();
monitors
.get(ordinal)
.copied()
.or_else(|| monitors.first().copied())
.ok_or_else(|| "no monitors available for game capture".to_string())
}
fn enumerate_monitors() -> Vec<HMONITOR> {
unsafe extern "system" fn enum_monitor(
monitor: HMONITOR,
_hdc: windows_sys::Win32::Graphics::Gdi::HDC,
_rect: *mut RECT,
param: LPARAM,
) -> i32 {
let monitors = &mut *(param as *mut Vec<HMONITOR>);
if monitors.len() >= MONITOR_LIMIT {
return 0;
}
monitors.push(monitor);
1
}
let mut monitors = Vec::with_capacity(MONITOR_LIMIT);
unsafe {
EnumDisplayMonitors(
null_mut(),
null(),
Some(enum_monitor),
&mut monitors as *mut _ as LPARAM,
);
}
assert!(monitors.len() <= MONITOR_LIMIT, "monitor targets bounded");
monitors
}
pub(crate) fn monitor_rect(monitor: HMONITOR) -> Option<RECT> {
let mut info = MONITORINFO {
cbSize: std::mem::size_of::<MONITORINFO>() as u32,
rcMonitor: RECT::default(),
rcWork: RECT::default(),
dwFlags: 0,
};
if unsafe { GetMonitorInfoW(monitor, &mut info) } == 0 {
return None;
}
Some(info.rcMonitor)
}
fn rect_matches_monitor(window_rect: &RECT, monitor_rect: &RECT) -> bool {
const TOLERANCE: i32 = 2;
(window_rect.left - monitor_rect.left).abs() <= TOLERANCE
&& (window_rect.top - monitor_rect.top).abs() <= TOLERANCE
&& (window_rect.right - monitor_rect.right).abs() <= TOLERANCE
&& (window_rect.bottom - monitor_rect.bottom).abs() <= TOLERANCE
}
fn is_regular_maximized_window(hwnd: HWND) -> bool {
let style = unsafe { GetWindowLongPtrW(hwnd, GWL_STYLE) } as u32;
(style & WS_MAXIMIZE) != 0 && (style & WS_BORDER) != 0
}
fn is_fullscreen_window_on_monitor(hwnd: HWND, monitor: HMONITOR, monitor_rect: &RECT) -> bool {
if unsafe { IsWindowVisible(hwnd) } == 0 {
return false;
}
if !unsafe { GetWindow(hwnd, GW_OWNER) }.is_null() {
return false;
}
let mut pid = 0u32;
unsafe {
GetWindowThreadProcessId(hwnd, &mut pid);
}
if pid == 0 || pid == unsafe { GetCurrentProcessId() } {
return false;
}
if is_regular_maximized_window(hwnd) {
return false;
}
let mut rect = RECT::default();
if unsafe { GetWindowRect(hwnd, &mut rect) } == 0 {
return false;
}
let window_monitor = unsafe { MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST) };
window_monitor == monitor && rect_matches_monitor(&rect, monitor_rect)
}
fn find_foreground_fullscreen_window() -> Option<HWND> {
let hwnd = unsafe { GetForegroundWindow() };
if hwnd.is_null() {
return None;
}
let mut rect = RECT::default();
if unsafe { GetWindowRect(hwnd, &mut rect) } == 0 {
return None;
}
let monitor = unsafe { MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST) };
let monitor_rect = monitor_rect(monitor)?;
is_fullscreen_window_on_monitor(hwnd, monitor, &monitor_rect).then_some(hwnd)
}
fn find_fullscreen_window_on_any_monitor() -> Option<HWND> {
for monitor in enumerate_monitors() {
if let Some(hwnd) = find_fullscreen_window_on_monitor(monitor) {
return Some(hwnd);
}
}
None
}
fn find_fullscreen_window_on_monitor(monitor: HMONITOR) -> Option<HWND> {
struct Search {
monitor: HMONITOR,
monitor_rect: RECT,
result: HWND,
own_pid: u32,
}
unsafe extern "system" fn enum_window(hwnd: HWND, param: LPARAM) -> i32 {
let search = &mut *(param as *mut Search);
let mut pid = 0u32;
GetWindowThreadProcessId(hwnd, &mut pid);
if pid == 0 || pid == search.own_pid {
return 1;
}
if !is_fullscreen_window_on_monitor(hwnd, search.monitor, &search.monitor_rect) {
return 1;
}
search.result = hwnd;
0
}
let monitor_rect = monitor_rect(monitor)?;
let mut search = Search {
monitor,
monitor_rect,
result: null_mut(),
own_pid: unsafe { GetCurrentProcessId() },
};
unsafe {
let _ = EnumWindows(Some(enum_window), &mut search as *mut _ as LPARAM);
}
(!search.result.is_null()).then_some(search.result)
}
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use windows::Win32::{
Foundation::HMODULE,
Graphics::{
Direct3D::{D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_UNKNOWN},
Direct3D11::{
D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_SDK_VERSION, D3D11CreateDevice, ID3D11Device,
ID3D11DeviceContext,
},
Dxgi::{IDXGIAdapter, IDXGIDevice},
},
};
use windows::core::Interface;
pub(crate) fn create_shared_texture_device(
adapter: Option<&IDXGIAdapter>,
) -> Result<(ID3D11Device, ID3D11DeviceContext), String> {
let mut device = None;
let mut context = None;
let driver_type = if adapter.is_some() {
D3D_DRIVER_TYPE_UNKNOWN
} else {
D3D_DRIVER_TYPE_HARDWARE
};
unsafe {
D3D11CreateDevice(
adapter,
driver_type,
HMODULE::default(),
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
None,
D3D11_SDK_VERSION,
Some(&mut device),
None,
Some(&mut context),
)
.map_err(|error| format!("D3D11CreateDevice for shared capture texture: {error}"))?;
}
let device = device.ok_or("D3D11 shared texture device was None")?;
let context = context.ok_or("D3D11 shared texture context was None")?;
if let Ok(dxgi_device) = device.cast::<IDXGIDevice>() {
let _ = unsafe { dxgi_device.SetGPUThreadPriority(7) };
}
Ok((device, context))
}
@@ -394,6 +394,7 @@ pub(crate) fn resolve_output_size(
}
#[cfg(target_os = "windows")]
#[cfg(feature = "game-capture-hook")]
pub(crate) fn wall_clock_us() -> i64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
@@ -5,7 +5,9 @@ use crate::game_capture_abi::{ENV_FORCE_CPU, ENV_VERBOSE, env_flag_enabled};
use crate::nv12_gpu::Nv12GpuConverter;
use crate::{
CaptureInner,
capture_target::{monitor_rect, resolve_game_capture_target},
compatibility::{InjectionPolicy, injection_policy_for_window},
d3d11_device::create_shared_texture_device,
dxgi_capture::{resolve_output_size, wall_clock_us},
emit_lifecycle, emit_shared_texture_frame,
game_capture_abi::{
@@ -26,26 +28,18 @@ use std::{
sync::{Arc, Mutex, atomic::Ordering},
};
use windows::Win32::{
Foundation::{HANDLE as WinHandle, HMODULE},
Foundation::HANDLE as WinHandle,
Graphics::{
Direct3D::{D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_UNKNOWN},
Direct3D11::{
D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC,
D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
},
Dxgi::{CreateDXGIFactory1, IDXGIAdapter, IDXGIDevice, IDXGIFactory1},
Direct3D11::{D3D11_TEXTURE2D_DESC, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D},
Dxgi::{CreateDXGIFactory1, IDXGIAdapter, IDXGIFactory1},
},
};
use windows::core::Interface;
use windows_sys::Win32::{
Foundation::{
CloseHandle, FreeLibrary, HANDLE, HINSTANCE, HMODULE as WinSysHmodule, HWND,
INVALID_HANDLE_VALUE, LPARAM, RECT, WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT,
},
Graphics::Gdi::{
EnumDisplayMonitors, GetMonitorInfoW, HMONITOR, MONITOR_DEFAULTTONEAREST, MONITORINFO,
MonitorFromRect,
},
Graphics::Gdi::{MONITOR_DEFAULTTONEAREST, MonitorFromRect},
System::{
Diagnostics::Debug::WriteProcessMemory,
LibraryLoader::{GetModuleHandleW, GetProcAddress, LoadLibraryW},
@@ -67,10 +61,8 @@ use windows_sys::Win32::{
},
},
UI::WindowsAndMessaging::{
EnumWindows, GW_OWNER, GWL_STYLE, GetClientRect, GetForegroundWindow, GetWindow,
GetWindowLongPtrW, GetWindowRect, GetWindowThreadProcessId, HHOOK, IsWindow,
IsWindowVisible, PostThreadMessageW, SetWindowsHookExW, UnhookWindowsHookEx, WH_GETMESSAGE,
WM_NULL, WS_BORDER, WS_MAXIMIZE,
GetClientRect, GetWindowRect, GetWindowThreadProcessId, HHOOK, IsWindow, IsWindowVisible,
PostThreadMessageW, SetWindowsHookExW, UnhookWindowsHookEx, WH_GETMESSAGE, WM_NULL,
},
};
@@ -812,87 +804,6 @@ fn inject_via_set_windows_hook(
Ok(InstalledWindowsHook { hook, module })
}
fn parse_hwnd_source_id(source_id: &str) -> Option<HWND> {
let token = source_id.strip_prefix("window:")?.split(':').next()?;
let value = if let Some(hex) = token
.strip_prefix("0x")
.or_else(|| token.strip_prefix("0X"))
{
isize::from_str_radix(hex, 16).ok()?
} else {
token.parse::<isize>().ok()?
};
let hwnd = value as HWND;
if unsafe { IsWindow(hwnd) } != 0 {
Some(hwnd)
} else {
None
}
}
fn parse_screen_ordinal(source_id: &str) -> Option<usize> {
let token = source_id.strip_prefix("screen:")?.split(':').next()?;
token.parse::<usize>().ok()
}
pub(crate) fn resolve_game_capture_target(
source_id: &str,
source_kind: &str,
) -> Result<HWND, String> {
if let Some(hwnd) = parse_hwnd_source_id(source_id) {
return Ok(hwnd);
}
if source_kind == "game" || source_kind == "screen" {
let monitor = monitor_for_screen_source(source_id)?;
if let Some(hwnd) = find_fullscreen_window_on_monitor(monitor) {
return Ok(hwnd);
}
if let Some(hwnd) = find_foreground_fullscreen_window() {
return Ok(hwnd);
}
if let Some(hwnd) = find_fullscreen_window_on_any_monitor() {
return Ok(hwnd);
}
return Err("no fullscreen game window found on selected display".into());
}
Err(format!(
"invalid game capture source: {source_kind}:{source_id}"
))
}
fn monitor_for_screen_source(source_id: &str) -> Result<HMONITOR, String> {
let ordinal = parse_screen_ordinal(source_id).unwrap_or(0);
let monitors = enumerate_monitors();
monitors
.get(ordinal)
.copied()
.or_else(|| monitors.first().copied())
.ok_or_else(|| "no monitors available for game capture".to_string())
}
fn enumerate_monitors() -> Vec<HMONITOR> {
unsafe extern "system" fn enum_monitor(
monitor: HMONITOR,
_hdc: windows_sys::Win32::Graphics::Gdi::HDC,
_rect: *mut RECT,
param: LPARAM,
) -> i32 {
let monitors = &mut *(param as *mut Vec<HMONITOR>);
monitors.push(monitor);
1
}
let mut monitors = Vec::new();
unsafe {
EnumDisplayMonitors(
null_mut(),
null(),
Some(enum_monitor),
&mut monitors as *mut _ as LPARAM,
);
}
monitors
}
fn target_process_id(hwnd: HWND) -> Result<u32, String> {
let mut pid = 0u32;
unsafe {
@@ -1039,155 +950,6 @@ fn choose_shared_buffer_dimensions(
Some((buffer_width, buffer_height))
}
fn monitor_rect(monitor: HMONITOR) -> Option<RECT> {
let mut info = MONITORINFO {
cbSize: std::mem::size_of::<MONITORINFO>() as u32,
rcMonitor: RECT::default(),
rcWork: RECT::default(),
dwFlags: 0,
};
if unsafe { GetMonitorInfoW(monitor, &mut info) } == 0 {
return None;
}
Some(info.rcMonitor)
}
fn rect_matches_monitor(window_rect: &RECT, monitor_rect: &RECT) -> bool {
let tolerance = 2;
(window_rect.left - monitor_rect.left).abs() <= tolerance
&& (window_rect.top - monitor_rect.top).abs() <= tolerance
&& (window_rect.right - monitor_rect.right).abs() <= tolerance
&& (window_rect.bottom - monitor_rect.bottom).abs() <= tolerance
}
fn is_regular_maximized_window(hwnd: HWND) -> bool {
let style = unsafe { GetWindowLongPtrW(hwnd, GWL_STYLE) } as u32;
(style & WS_MAXIMIZE) != 0 && (style & WS_BORDER) != 0
}
fn is_fullscreen_window_on_monitor(hwnd: HWND, monitor: HMONITOR, monitor_rect: &RECT) -> bool {
if unsafe { IsWindowVisible(hwnd) } == 0 || !unsafe { GetWindow(hwnd, GW_OWNER) }.is_null() {
return false;
}
let mut pid = 0u32;
unsafe {
GetWindowThreadProcessId(hwnd, &mut pid);
}
if pid == 0 || pid == unsafe { GetCurrentProcessId() } {
return false;
}
if is_regular_maximized_window(hwnd) {
return false;
}
let mut rect = RECT::default();
if unsafe { GetWindowRect(hwnd, &mut rect) } == 0 {
return false;
}
let window_monitor = unsafe { MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST) };
window_monitor == monitor && rect_matches_monitor(&rect, monitor_rect)
}
fn find_foreground_fullscreen_window() -> Option<HWND> {
let hwnd = unsafe { GetForegroundWindow() };
if hwnd.is_null() {
return None;
}
let mut rect = RECT::default();
if unsafe { GetWindowRect(hwnd, &mut rect) } == 0 {
return None;
}
let monitor = unsafe { MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST) };
let monitor_rect = monitor_rect(monitor)?;
if is_fullscreen_window_on_monitor(hwnd, monitor, &monitor_rect) {
Some(hwnd)
} else {
None
}
}
fn find_fullscreen_window_on_any_monitor() -> Option<HWND> {
for monitor in enumerate_monitors() {
if let Some(hwnd) = find_fullscreen_window_on_monitor(monitor) {
return Some(hwnd);
}
}
None
}
fn find_fullscreen_window_on_monitor(monitor: HMONITOR) -> Option<HWND> {
struct Search {
monitor: HMONITOR,
monitor_rect: RECT,
result: HWND,
own_pid: u32,
}
unsafe extern "system" fn enum_window(hwnd: HWND, param: LPARAM) -> i32 {
let search = &mut *(param as *mut Search);
let mut pid = 0u32;
GetWindowThreadProcessId(hwnd, &mut pid);
if pid != 0
&& pid != search.own_pid
&& is_fullscreen_window_on_monitor(hwnd, search.monitor, &search.monitor_rect)
{
search.result = hwnd;
return 0;
}
1
}
let monitor_rect = monitor_rect(monitor)?;
let mut search = Search {
monitor,
monitor_rect,
result: null_mut(),
own_pid: unsafe { GetCurrentProcessId() },
};
unsafe {
let _ = EnumWindows(Some(enum_window), &mut search as *mut _ as LPARAM);
}
if search.result.is_null() {
None
} else {
Some(search.result)
}
}
pub(crate) fn create_shared_texture_device(
adapter: Option<&IDXGIAdapter>,
) -> Result<(ID3D11Device, ID3D11DeviceContext), String> {
let mut device = None;
let mut context = None;
let driver_type = if adapter.is_some() {
D3D_DRIVER_TYPE_UNKNOWN
} else {
D3D_DRIVER_TYPE_HARDWARE
};
unsafe {
D3D11CreateDevice(
adapter,
driver_type,
HMODULE::default(),
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
None,
D3D11_SDK_VERSION,
Some(&mut device),
None,
Some(&mut context),
)
.map_err(|e| format!("D3D11CreateDevice for shared game texture: {e}"))?;
}
let device = device.ok_or("D3D11 shared texture device was None")?;
let context = context.ok_or("D3D11 shared texture context was None")?;
set_gpu_thread_priority(&device);
Ok((device, context))
}
fn set_gpu_thread_priority(device: &ID3D11Device) {
if let Ok(dxgi_device) = device.cast::<IDXGIDevice>() {
let _ = unsafe { dxgi_device.SetGPUThreadPriority(7) };
}
}
const SHARED_TEXTURE_ADAPTER_LIMIT: u32 = 16;
fn shared_texture_adapter_candidates() -> Vec<Option<IDXGIAdapter>> {
@@ -3,13 +3,17 @@
#![deny(clippy::all)]
#![allow(unsafe_op_in_unsafe_fn)]
#[cfg(any(target_os = "windows", test))]
#[cfg(target_os = "windows")]
mod capture_target;
#[cfg(any(all(target_os = "windows", feature = "game-capture-hook"), test))]
mod compatibility;
#[cfg(target_os = "windows")]
mod d3d11_device;
#[cfg(any(target_os = "windows", test))]
mod dxgi_capture;
pub mod encoder_attach;
mod fallback;
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
mod game_capture;
mod game_capture_abi;
mod gpu_priority;
@@ -19,7 +23,7 @@ mod nv12_gpu;
mod sources;
#[cfg(any(target_os = "windows", test))]
mod stall;
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
mod vulkan_layer_registry;
#[cfg(target_os = "windows")]
mod wgc_capture;
@@ -39,7 +43,7 @@ use dxgi_capture::DxgiCaptureSession;
use fluxer_encoder_ring::EncoderFrameRate;
#[cfg(target_os = "windows")]
use fluxer_screen_frame_bus::EnqueueOutcome;
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
use game_capture::GameCaptureSession;
#[cfg(target_os = "windows")]
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
@@ -48,7 +52,6 @@ use wgc_capture::WgcCaptureSession;
const LIFECYCLE_QUEUE_LIMIT: usize = 8;
const START_OPTION_UNSUPPORTED_LIMIT: usize = 4;
const GAME_CAPTURE_HOOK_FORCE_DISABLED: bool = true;
type LifecycleTsfn = Arc<
ThreadsafeFunction<
@@ -226,7 +229,7 @@ pub struct CaptureInner {
pub session: Mutex<Option<DxgiCaptureSession>>,
#[cfg(target_os = "windows")]
pub(crate) wgc_session: Mutex<Option<WgcCaptureSession>>,
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
pub game_session: Mutex<Option<Arc<GameCaptureSession>>>,
pub running: std::sync::atomic::AtomicBool,
pub fallback: Mutex<Option<fallback::FallbackTracker>>,
@@ -421,6 +424,7 @@ pub(crate) fn note_media_frame_without_sink(inner: &CaptureInner, message: &'sta
}
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
pub(crate) fn note_cpu_fallback_frame_dropped(inner: &CaptureInner, message: &'static str) {
inner
.cpu_fallback_frames_dropped
@@ -470,7 +474,7 @@ impl ScreenCapture {
session: Mutex::new(None),
#[cfg(target_os = "windows")]
wgc_session: Mutex::new(None),
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
game_session: Mutex::new(None),
running: std::sync::atomic::AtomicBool::new(false),
fallback: Mutex::new(None),
@@ -586,43 +590,48 @@ impl ScreenCapture {
#[cfg(target_os = "windows")]
{
let frame_sink = frame_sink_counter_snapshot(&self.inner);
let guard = self.inner.game_session.lock();
if let Some(session) = guard.as_ref() {
let requested_injection_method = session.requested_injection_method().to_string();
let injection_method = session.used_injection_method().to_string();
if let Some(info) = session.read_shared_info() {
return Some(CaptureDiagnostics {
state: info.state,
api_type: info.api_type,
transport: info.transport,
fallback_reason: info.fallback_reason,
capture_flags: info.capture_flags,
width: info.width,
height: info.height,
dxgi_format: info.dxgi_format,
frame_counter: info.frame_counter as f64,
dropped_frame_counter: info.dropped_frame_counter as f64,
last_present_timestamp_us: info.last_present_timestamp_us as f64,
last_error: info.last_error,
#[cfg(feature = "game-capture-hook")]
{
let guard = self.inner.game_session.lock();
if let Some(session) = guard.as_ref() {
let requested_injection_method =
session.requested_injection_method().to_string();
let injection_method = session.used_injection_method().to_string();
if let Some(info) = session.read_shared_info() {
return Some(CaptureDiagnostics {
state: info.state,
api_type: info.api_type,
transport: info.transport,
fallback_reason: info.fallback_reason,
capture_flags: info.capture_flags,
width: info.width,
height: info.height,
dxgi_format: info.dxgi_format,
frame_counter: info.frame_counter as f64,
dropped_frame_counter: info.dropped_frame_counter as f64,
last_present_timestamp_us: info.last_present_timestamp_us as f64,
last_error: info.last_error,
requested_injection_method,
injection_method,
active_strategy: snapshot.active_strategy,
last_fallback_reason: snapshot.last_fallback_reason,
start_options: current_start_options(&self.inner),
frame_sink_accepted: frame_sink.accepted as f64,
frame_sink_coalesced: frame_sink.coalesced as f64,
frame_sink_rejected: frame_sink.rejected as f64,
media_frames_dropped_without_sink: frame_sink.dropped_without_sink
as f64,
cpu_fallback_frames_dropped: frame_sink.cpu_fallback_dropped as f64,
});
}
return Some(strategy_only_diagnostics(
&snapshot,
requested_injection_method,
injection_method,
active_strategy: snapshot.active_strategy,
last_fallback_reason: snapshot.last_fallback_reason,
start_options: current_start_options(&self.inner),
frame_sink_accepted: frame_sink.accepted as f64,
frame_sink_coalesced: frame_sink.coalesced as f64,
frame_sink_rejected: frame_sink.rejected as f64,
media_frames_dropped_without_sink: frame_sink.dropped_without_sink as f64,
cpu_fallback_frames_dropped: frame_sink.cpu_fallback_dropped as f64,
});
current_start_options(&self.inner),
frame_sink,
));
}
return Some(strategy_only_diagnostics(
&snapshot,
requested_injection_method,
injection_method,
current_start_options(&self.inner),
frame_sink,
));
}
Some(strategy_only_diagnostics(
&snapshot,
@@ -669,7 +678,7 @@ impl ScreenCapture {
#[napi(js_name = "getSharedTextureHandle")]
pub fn get_shared_texture_handle(&self) -> Option<SharedTextureHandleInfo> {
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
{
let guard = self.inner.game_session.lock();
let session = guard.as_ref()?;
@@ -684,7 +693,7 @@ impl ScreenCapture {
}
None
}
#[cfg(not(target_os = "windows"))]
#[cfg(not(all(target_os = "windows", feature = "game-capture-hook")))]
{
None
}
@@ -706,6 +715,9 @@ impl ScreenCapture {
*guard = None;
let mut wgc_guard = self.inner.wgc_session.lock();
*wgc_guard = None;
}
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
{
let mut game_guard = self.inner.game_session.lock();
*game_guard = None;
}
@@ -955,7 +967,8 @@ impl ScreenCapture {
return Err(napi::Error::from_reason("Capture already running"));
}
if source_kind == "game" && !GAME_CAPTURE_HOOK_FORCE_DISABLED {
#[cfg(feature = "game-capture-hook")]
if source_kind == "game" {
return self.start_windows_game(
source_id,
source_kind,
@@ -991,7 +1004,7 @@ impl ScreenCapture {
}
let hwnd = if source_kind == "game" {
let target = game_capture::resolve_game_capture_target(&source_id, &source_kind)
let target = capture_target::resolve_game_capture_target(&source_id, &source_kind)
.map_err(|e| {
napi::Error::from_reason(format!("Failed to resolve game capture target: {e}"))
})?;
@@ -1115,6 +1128,7 @@ impl ScreenCapture {
})
}
#[cfg(feature = "game-capture-hook")]
#[allow(clippy::too_many_arguments)]
fn start_windows_game(
&self,
@@ -1278,7 +1292,7 @@ pub fn is_supported() -> bool {
#[napi(js_name = "isGameCaptureHookAvailable")]
pub fn is_game_capture_hook_available() -> bool {
cfg!(target_os = "windows") && !GAME_CAPTURE_HOOK_FORCE_DISABLED
cfg!(all(target_os = "windows", feature = "game-capture-hook"))
}
#[napi(js_name = "getAvailability")]
@@ -1314,37 +1328,37 @@ pub fn restore_gpu_scheduling_priority(process_id: Option<u32>) -> Result<()> {
#[napi(js_name = "registerVulkanLayerManifest")]
pub fn register_vulkan_layer_manifest(manifest_path: String) -> Result<()> {
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
{
vulkan_layer_registry::register_manifest(&manifest_path).map_err(napi::Error::from_reason)
}
#[cfg(not(target_os = "windows"))]
#[cfg(not(all(target_os = "windows", feature = "game-capture-hook")))]
{
let _ = manifest_path;
Err(napi::Error::from_reason(
"Vulkan game capture layer only supported on Windows",
"Vulkan game capture layer is not included in this build",
))
}
}
#[napi(js_name = "unregisterVulkanLayerManifest")]
pub fn unregister_vulkan_layer_manifest(manifest_path: String) -> Result<()> {
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
{
vulkan_layer_registry::unregister_manifest(&manifest_path).map_err(napi::Error::from_reason)
}
#[cfg(not(target_os = "windows"))]
#[cfg(not(all(target_os = "windows", feature = "game-capture-hook")))]
{
let _ = manifest_path;
Err(napi::Error::from_reason(
"Vulkan game capture layer only supported on Windows",
"Vulkan game capture layer is not included in this build",
))
}
}
#[napi(js_name = "getVulkanLayerRegistrationState")]
pub fn get_vulkan_layer_registration_state(manifest_path: String) -> VulkanLayerRegistrationState {
#[cfg(target_os = "windows")]
#[cfg(all(target_os = "windows", feature = "game-capture-hook"))]
{
let state = vulkan_layer_registry::registration_state(&manifest_path);
VulkanLayerRegistrationState {
@@ -1354,7 +1368,7 @@ pub fn get_vulkan_layer_registration_state(manifest_path: String) -> VulkanLayer
manifest_path: state.manifest_path,
}
}
#[cfg(not(target_os = "windows"))]
#[cfg(not(all(target_os = "windows", feature = "game-capture-hook")))]
{
VulkanLayerRegistrationState {
registered: false,
@@ -208,7 +208,7 @@ impl WgcCaptureSession {
requested_height: Option<u32>,
) -> Result<Self, String> {
ensure_winrt_initialized();
let (device, context) = crate::game_capture::create_shared_texture_device(None)?;
let (device, context) = crate::d3d11_device::create_shared_texture_device(None)?;
let dxgi_device: IDXGIDevice = device
.cast()
.map_err(|e| format!("IDXGIDevice cast: {e}"))?;