style(host/windows): rustfmt the Windows backends
apple / swift (push) Successful in 55s
android / android (push) Failing after 1m53s
ci / web (push) Failing after 17s
ci / docs-site (push) Successful in 42s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
ci / rust (push) Failing after 3m5s
ci / bench (push) Successful in 1m49s
decky / build-publish (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Failing after 2s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Failing after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Failing after 0s
flatpak / build-publish (push) Failing after 0s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 0s
docker / deploy-docs (push) Has been skipped
deb / build-publish (push) Failing after 1m43s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 1m15s
apple / swift (push) Successful in 55s
android / android (push) Failing after 1m53s
ci / web (push) Failing after 17s
ci / docs-site (push) Successful in 42s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
ci / rust (push) Failing after 3m5s
ci / bench (push) Successful in 1m49s
decky / build-publish (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Failing after 2s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Failing after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Failing after 0s
flatpak / build-publish (push) Failing after 0s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 0s
docker / deploy-docs (push) Has been skipped
deb / build-publish (push) Failing after 1m43s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 1m15s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,8 @@ pub fn open_audio_capture(channels: u32) -> Result<Box<dyn AudioCapturer>> {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn open_audio_capture(channels: u32) -> Result<Box<dyn AudioCapturer>> {
|
||||
wasapi_cap::WasapiLoopbackCapturer::open(channels).map(|c| Box::new(c) as Box<dyn AudioCapturer>)
|
||||
wasapi_cap::WasapiLoopbackCapturer::open(channels)
|
||||
.map(|c| Box::new(c) as Box<dyn AudioCapturer>)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
|
||||
|
||||
@@ -46,7 +46,9 @@ impl WasapiLoopbackCapturer {
|
||||
.context("spawn wasapi audio thread")?;
|
||||
match ready_rx.recv_timeout(Duration::from_secs(3)) {
|
||||
Ok(Ok(())) => {
|
||||
tracing::info!("WASAPI loopback capture: 48 kHz stereo f32 (default render endpoint)");
|
||||
tracing::info!(
|
||||
"WASAPI loopback capture: 48 kHz stereo f32 (default render endpoint)"
|
||||
);
|
||||
Ok(WasapiLoopbackCapturer {
|
||||
chunks: rx,
|
||||
channels,
|
||||
@@ -93,7 +95,10 @@ fn capture_thread(
|
||||
ready: SyncSender<Result<()>>,
|
||||
) -> Result<()> {
|
||||
// COM must be initialized on THIS thread (MTA), before any device call.
|
||||
if let Err(e) = wasapi::initialize_mta().ok().context("CoInitializeEx (MTA)") {
|
||||
if let Err(e) = wasapi::initialize_mta()
|
||||
.ok()
|
||||
.context("CoInitializeEx (MTA)")
|
||||
{
|
||||
let _ = ready.send(Err(e));
|
||||
return Ok(());
|
||||
}
|
||||
@@ -122,7 +127,9 @@ fn capture_thread(
|
||||
let capture_client = audio_client
|
||||
.get_audiocaptureclient()
|
||||
.context("IAudioCaptureClient")?;
|
||||
audio_client.start_stream().context("start loopback stream")?;
|
||||
audio_client
|
||||
.start_stream()
|
||||
.context("start loopback stream")?;
|
||||
let _ = ready.send(Ok(()));
|
||||
|
||||
let mut bytes: VecDeque<u8> = VecDeque::new();
|
||||
@@ -182,7 +189,10 @@ mod tests {
|
||||
};
|
||||
assert_eq!(cap.channels(), 2);
|
||||
match cap.next_chunk() {
|
||||
Ok(samples) => assert!(samples.len() % 2 == 0, "interleaved stereo => even sample count"),
|
||||
Ok(samples) => assert!(
|
||||
samples.len() % 2 == 0,
|
||||
"interleaved stereo => even sample count"
|
||||
),
|
||||
Err(e) => eprintln!("no audio within timeout (silent system?): {e:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +257,9 @@ pub fn capture_virtual_output(vout: crate::vdisplay::VirtualOutput) -> Result<Bo
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn capture_virtual_output(vout: crate::vdisplay::VirtualOutput) -> Result<Box<dyn Capturer>> {
|
||||
let target = vout.win_capture.clone().ok_or_else(|| {
|
||||
anyhow::anyhow!("SudoVDA target not yet an active display (needs a WDDM GPU to activate it)")
|
||||
anyhow::anyhow!(
|
||||
"SudoVDA target not yet an active display (needs a WDDM GPU to activate it)"
|
||||
)
|
||||
})?;
|
||||
dxgi::DuplCapturer::open(target, vout.preferred_mode, vout.keepalive)
|
||||
.map(|c| Box::new(c) as Box<dyn Capturer>)
|
||||
@@ -268,7 +270,7 @@ pub fn capture_virtual_output(_vout: crate::vdisplay::VirtualOutput) -> Result<B
|
||||
anyhow::bail!("virtual-output capture requires Linux or Windows")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod dxgi;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
|
||||
@@ -139,8 +139,8 @@ impl DuplCapturer {
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
let output = out1
|
||||
.with_context(|| format!("adapter has no output named {}", target.gdi_name))?;
|
||||
let output =
|
||||
out1.with_context(|| format!("adapter has no output named {}", target.gdi_name))?;
|
||||
// 4) duplicate the output.
|
||||
let dupl = output
|
||||
.DuplicateOutput(&device)
|
||||
@@ -171,7 +171,11 @@ impl DuplCapturer {
|
||||
height,
|
||||
refresh_hz,
|
||||
target.gdi_name,
|
||||
if gpu_mode { "D3D11 zero-copy" } else { "CPU staging" }
|
||||
if gpu_mode {
|
||||
"D3D11 zero-copy"
|
||||
} else {
|
||||
"CPU staging"
|
||||
}
|
||||
);
|
||||
Ok(Self {
|
||||
device,
|
||||
@@ -268,7 +272,10 @@ impl DuplCapturer {
|
||||
}
|
||||
let mut info = DXGI_OUTDUPL_FRAME_INFO::default();
|
||||
let mut res: Option<IDXGIResource> = None;
|
||||
match self.dupl.AcquireNextFrame(self.timeout_ms, &mut info, &mut res) {
|
||||
match self
|
||||
.dupl
|
||||
.AcquireNextFrame(self.timeout_ms, &mut info, &mut res)
|
||||
{
|
||||
Ok(()) => {}
|
||||
Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => return Ok(None),
|
||||
Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => {
|
||||
|
||||
@@ -185,8 +185,13 @@ pub fn open_video(
|
||||
);
|
||||
// Software H.264 realistically caps far below the negotiated hardware rates.
|
||||
const SW_BITRATE_CEIL: u64 = 100_000_000;
|
||||
let enc =
|
||||
sw::OpenH264Encoder::open(format, width, height, fps, bitrate_bps.min(SW_BITRATE_CEIL))?;
|
||||
let enc = sw::OpenH264Encoder::open(
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
fps,
|
||||
bitrate_bps.min(SW_BITRATE_CEIL),
|
||||
)?;
|
||||
Ok(Box::new(enc) as Box<dyn Encoder>)
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
|
||||
@@ -198,10 +203,10 @@ pub fn open_video(
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod sw;
|
||||
#[cfg(all(target_os = "windows", feature = "nvenc"))]
|
||||
mod nvenc;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod sw;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -18,8 +18,8 @@ use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use windows::core::Interface;
|
||||
use windows::Win32::Graphics::Direct3D11::{
|
||||
ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_BIND_RENDER_TARGET, D3D11_TEXTURE2D_DESC,
|
||||
D3D11_USAGE_DEFAULT,
|
||||
ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_BIND_RENDER_TARGET,
|
||||
D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT,
|
||||
};
|
||||
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC};
|
||||
|
||||
@@ -94,7 +94,11 @@ impl NvencD3d11Encoder {
|
||||
/// Lazily create the session on the first frame's D3D11 device (so capture + encode share it).
|
||||
fn init_session(&mut self, device: &ID3D11Device) -> Result<()> {
|
||||
unsafe {
|
||||
self.ctx = Some(device.GetImmediateContext().context("D3D11 immediate context")?);
|
||||
self.ctx = Some(
|
||||
device
|
||||
.GetImmediateContext()
|
||||
.context("D3D11 immediate context")?,
|
||||
);
|
||||
|
||||
// 1. open the session bound to the D3D11 device.
|
||||
let mut params = nv::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS {
|
||||
|
||||
@@ -132,10 +132,12 @@ impl Encoder for OpenH264Encoder {
|
||||
);
|
||||
|
||||
match self.src_format {
|
||||
PixelFormat::Rgb => self.yuv.read_rgb(RgbSliceU8::new(&bytes[..w * h * 3], (w, h))),
|
||||
PixelFormat::Bgra | PixelFormat::Bgrx => {
|
||||
self.yuv.read_rgb(BgraSliceU8::new(&bytes[..w * h * 4], (w, h)))
|
||||
}
|
||||
PixelFormat::Rgb => self
|
||||
.yuv
|
||||
.read_rgb(RgbSliceU8::new(&bytes[..w * h * 3], (w, h))),
|
||||
PixelFormat::Bgra | PixelFormat::Bgrx => self
|
||||
.yuv
|
||||
.read_rgb(BgraSliceU8::new(&bytes[..w * h * 4], (w, h))),
|
||||
PixelFormat::Rgba | PixelFormat::Rgbx => {
|
||||
self.normalize_to_bgra(bytes, 4, true);
|
||||
self.yuv.read_rgb(BgraSliceU8::new(&self.scratch, (w, h)));
|
||||
|
||||
@@ -317,7 +317,7 @@ pub mod gamepad {
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
mod libei;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod wlr;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod sendinput;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod wlr;
|
||||
|
||||
@@ -13,11 +13,11 @@ use windows::Win32::System::StationsAndDesktops::{
|
||||
};
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
MapVirtualKeyExW, SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, INPUT_MOUSE, KEYBDINPUT,
|
||||
KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, MAPVK_VK_TO_VSC_EX, MOUSEEVENTF_ABSOLUTE,
|
||||
MOUSEEVENTF_HWHEEL, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN,
|
||||
MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP,
|
||||
MOUSEEVENTF_VIRTUALDESK, MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT,
|
||||
VIRTUAL_KEY,
|
||||
KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, MAPVK_VK_TO_VSC_EX,
|
||||
MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_HWHEEL, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP,
|
||||
MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN,
|
||||
MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_VIRTUALDESK, MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN,
|
||||
MOUSEEVENTF_XUP, MOUSEINPUT, VIRTUAL_KEY,
|
||||
};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
GetSystemMetrics, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN,
|
||||
@@ -73,7 +73,10 @@ impl SendInputInjector {
|
||||
if n as usize != inputs.len() {
|
||||
// 0 = blocked (different/secure desktop). Surface as Err so the host service drops +
|
||||
// reopens the injector (which reattaches the input desktop).
|
||||
anyhow::bail!("SendInput injected {n}/{} events (blocked desktop?)", inputs.len());
|
||||
anyhow::bail!(
|
||||
"SendInput injected {n}/{} events (blocked desktop?)",
|
||||
inputs.len()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -130,11 +133,46 @@ impl InputInjector for SendInputInjector {
|
||||
InputKind::MouseButtonDown | InputKind::MouseButtonUp => {
|
||||
let down = event.kind == InputKind::MouseButtonDown;
|
||||
let (flag, data) = match event.code {
|
||||
1 => (if down { MOUSEEVENTF_LEFTDOWN } else { MOUSEEVENTF_LEFTUP }, 0u32),
|
||||
2 => (if down { MOUSEEVENTF_MIDDLEDOWN } else { MOUSEEVENTF_MIDDLEUP }, 0),
|
||||
3 => (if down { MOUSEEVENTF_RIGHTDOWN } else { MOUSEEVENTF_RIGHTUP }, 0),
|
||||
4 => (if down { MOUSEEVENTF_XDOWN } else { MOUSEEVENTF_XUP }, XBUTTON1),
|
||||
5 => (if down { MOUSEEVENTF_XDOWN } else { MOUSEEVENTF_XUP }, XBUTTON2),
|
||||
1 => (
|
||||
if down {
|
||||
MOUSEEVENTF_LEFTDOWN
|
||||
} else {
|
||||
MOUSEEVENTF_LEFTUP
|
||||
},
|
||||
0u32,
|
||||
),
|
||||
2 => (
|
||||
if down {
|
||||
MOUSEEVENTF_MIDDLEDOWN
|
||||
} else {
|
||||
MOUSEEVENTF_MIDDLEUP
|
||||
},
|
||||
0,
|
||||
),
|
||||
3 => (
|
||||
if down {
|
||||
MOUSEEVENTF_RIGHTDOWN
|
||||
} else {
|
||||
MOUSEEVENTF_RIGHTUP
|
||||
},
|
||||
0,
|
||||
),
|
||||
4 => (
|
||||
if down {
|
||||
MOUSEEVENTF_XDOWN
|
||||
} else {
|
||||
MOUSEEVENTF_XUP
|
||||
},
|
||||
XBUTTON1,
|
||||
),
|
||||
5 => (
|
||||
if down {
|
||||
MOUSEEVENTF_XDOWN
|
||||
} else {
|
||||
MOUSEEVENTF_XUP
|
||||
},
|
||||
XBUTTON2,
|
||||
),
|
||||
_ => return Ok(()),
|
||||
};
|
||||
let mi = MOUSEINPUT {
|
||||
@@ -155,7 +193,11 @@ impl InputInjector for SendInputInjector {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
mouseData: event.x as u32, // signed wheel delta reinterpreted as DWORD
|
||||
dwFlags: if horizontal { MOUSEEVENTF_HWHEEL } else { MOUSEEVENTF_WHEEL },
|
||||
dwFlags: if horizontal {
|
||||
MOUSEEVENTF_HWHEEL
|
||||
} else {
|
||||
MOUSEEVENTF_WHEEL
|
||||
},
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
};
|
||||
@@ -226,5 +268,8 @@ fn virtual_desktop_rect() -> (i32, i32, i32, i32) {
|
||||
// RCtrl (0xA3), RAlt (0xA5), Pause (0x90). MAPVK_VK_TO_VSC_EX already encodes E0 for most; this is a
|
||||
// thin safety net.
|
||||
fn forced_extended(vk: u16) -> bool {
|
||||
matches!(vk, 0x21..=0x28 | 0x2D | 0x2E | 0x5B | 0x5C | 0x5D | 0xA3 | 0xA5 | 0x90)
|
||||
matches!(
|
||||
vk,
|
||||
0x21..=0x28 | 0x2D | 0x2E | 0x5B | 0x5C | 0x5D | 0xA3 | 0xA5 | 0x90
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1528,52 +1528,52 @@ fn resolve_compositor(pref: CompositorPref) -> Result<crate::vdisplay::Composito
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
// Explicit operator override (legacy / CI / forcing a backend for a test) wins and is assumed
|
||||
// to come with a hand-set env — don't retarget the process env in that case.
|
||||
let overridden = std::env::var_os("PUNKTFUNK_COMPOSITOR").is_some();
|
||||
let detected = if overridden {
|
||||
crate::vdisplay::detect().ok()
|
||||
} else {
|
||||
// Auto: detect the LIVE session (Gaming vs Desktop) and retarget the process env at it so
|
||||
// every backend (video capture + input) this connect opens against the active session —
|
||||
// this is the state machine that lets one host follow a Bazzite box across Gaming↔Desktop.
|
||||
let active = crate::vdisplay::detect_active_session();
|
||||
crate::vdisplay::apply_session_env(&active);
|
||||
tracing::info!(
|
||||
active = ?active.kind,
|
||||
wayland = active.env.wayland_display.as_deref().unwrap_or("-"),
|
||||
"detected active graphical session"
|
||||
);
|
||||
crate::vdisplay::compositor_for_kind(active.kind)
|
||||
};
|
||||
let available = crate::vdisplay::available();
|
||||
let chosen = pick_compositor(pref, &available, detected).ok_or_else(|| {
|
||||
// Explicit operator override (legacy / CI / forcing a backend for a test) wins and is assumed
|
||||
// to come with a hand-set env — don't retarget the process env in that case.
|
||||
let overridden = std::env::var_os("PUNKTFUNK_COMPOSITOR").is_some();
|
||||
let detected = if overridden {
|
||||
crate::vdisplay::detect().ok()
|
||||
} else {
|
||||
// Auto: detect the LIVE session (Gaming vs Desktop) and retarget the process env at it so
|
||||
// every backend (video capture + input) this connect opens against the active session —
|
||||
// this is the state machine that lets one host follow a Bazzite box across Gaming↔Desktop.
|
||||
let active = crate::vdisplay::detect_active_session();
|
||||
crate::vdisplay::apply_session_env(&active);
|
||||
tracing::info!(
|
||||
active = ?active.kind,
|
||||
wayland = active.env.wayland_display.as_deref().unwrap_or("-"),
|
||||
"detected active graphical session"
|
||||
);
|
||||
crate::vdisplay::compositor_for_kind(active.kind)
|
||||
};
|
||||
let available = crate::vdisplay::available();
|
||||
let chosen = pick_compositor(pref, &available, detected).ok_or_else(|| {
|
||||
anyhow!("no usable compositor (no live graphical session for this uid; set PUNKTFUNK_COMPOSITOR or start a desktop/gaming session)")
|
||||
})?;
|
||||
if !overridden {
|
||||
// Point input at the same backend and select gamescope ATTACH (no churny managed restart).
|
||||
crate::vdisplay::apply_input_env(chosen);
|
||||
}
|
||||
let avail_ids: Vec<&str> = available.iter().map(|c| c.id()).collect();
|
||||
match Compositor::from_pref(pref) {
|
||||
Some(want) if want == chosen => {
|
||||
tracing::info!(
|
||||
compositor = chosen.id(),
|
||||
"honoring client compositor request"
|
||||
)
|
||||
if !overridden {
|
||||
// Point input at the same backend and select gamescope ATTACH (no churny managed restart).
|
||||
crate::vdisplay::apply_input_env(chosen);
|
||||
}
|
||||
Some(want) => tracing::warn!(
|
||||
requested = want.id(),
|
||||
chosen = chosen.id(),
|
||||
available = ?avail_ids,
|
||||
"client-requested compositor unavailable — falling back to auto-detect"
|
||||
),
|
||||
None => tracing::info!(
|
||||
compositor = chosen.id(),
|
||||
"auto-detected compositor (client: auto)"
|
||||
),
|
||||
}
|
||||
Ok(chosen)
|
||||
let avail_ids: Vec<&str> = available.iter().map(|c| c.id()).collect();
|
||||
match Compositor::from_pref(pref) {
|
||||
Some(want) if want == chosen => {
|
||||
tracing::info!(
|
||||
compositor = chosen.id(),
|
||||
"honoring client compositor request"
|
||||
)
|
||||
}
|
||||
Some(want) => tracing::warn!(
|
||||
requested = want.id(),
|
||||
chosen = chosen.id(),
|
||||
available = ?avail_ids,
|
||||
"client-requested compositor unavailable — falling back to auto-detect"
|
||||
),
|
||||
None => tracing::info!(
|
||||
compositor = chosen.id(),
|
||||
"auto-detected compositor (client: auto)"
|
||||
),
|
||||
}
|
||||
Ok(chosen)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -540,10 +540,10 @@ mod gamescope;
|
||||
mod kwin;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod mutter;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod wlroots;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod sudovda;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod wlroots;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -229,11 +229,16 @@ impl VirtualDisplay for SudoVdaDisplay {
|
||||
device_name,
|
||||
serial: [0u8; 14],
|
||||
};
|
||||
let add_bytes =
|
||||
unsafe { std::slice::from_raw_parts(&add as *const _ as *const u8, size_of::<AddParams>()) };
|
||||
let add_bytes = unsafe {
|
||||
std::slice::from_raw_parts(&add as *const _ as *const u8, size_of::<AddParams>())
|
||||
};
|
||||
let mut out = [0u8; size_of::<AddOut>()];
|
||||
unsafe { ioctl(self.device, IOCTL_ADD, add_bytes, &mut out) }
|
||||
.with_context(|| format!("SudoVDA ADD {}x{}@{}", mode.width, mode.height, mode.refresh_hz))?;
|
||||
unsafe { ioctl(self.device, IOCTL_ADD, add_bytes, &mut out) }.with_context(|| {
|
||||
format!(
|
||||
"SudoVDA ADD {}x{}@{}",
|
||||
mode.width, mode.height, mode.refresh_hz
|
||||
)
|
||||
})?;
|
||||
let ao = unsafe { *(out.as_ptr() as *const AddOut) };
|
||||
tracing::info!(
|
||||
"SudoVDA created {}x{}@{} (target_id={}, adapter_luid={:#x})",
|
||||
@@ -281,10 +286,12 @@ impl VirtualDisplay for SudoVdaDisplay {
|
||||
Ok(VirtualOutput {
|
||||
node_id: 0, // unused on Windows; the capture target is the GDI name below
|
||||
preferred_mode: Some((mode.width, mode.height, mode.refresh_hz)),
|
||||
win_capture: gdi_name.clone().map(|n| crate::capture::dxgi::WinCaptureTarget {
|
||||
adapter_luid: crate::capture::dxgi::pack_luid(ao.luid),
|
||||
gdi_name: n,
|
||||
}),
|
||||
win_capture: gdi_name
|
||||
.clone()
|
||||
.map(|n| crate::capture::dxgi::WinCaptureTarget {
|
||||
adapter_luid: crate::capture::dxgi::pack_luid(ao.luid),
|
||||
gdi_name: n,
|
||||
}),
|
||||
keepalive: Box::new(SudoVdaKeepalive {
|
||||
device: device_raw,
|
||||
guid: MONITOR_GUID,
|
||||
@@ -314,8 +321,9 @@ impl Drop for SudoVdaKeepalive {
|
||||
let _ = j.join();
|
||||
}
|
||||
let rp = RemoveParams { guid: self.guid };
|
||||
let rp_bytes =
|
||||
unsafe { std::slice::from_raw_parts(&rp as *const _ as *const u8, size_of::<RemoveParams>()) };
|
||||
let rp_bytes = unsafe {
|
||||
std::slice::from_raw_parts(&rp as *const _ as *const u8, size_of::<RemoveParams>())
|
||||
};
|
||||
let mut none: [u8; 0] = [];
|
||||
let h = HANDLE(self.device as *mut c_void);
|
||||
if let Err(e) = unsafe { ioctl(h, IOCTL_REMOVE, rp_bytes, &mut none) } {
|
||||
|
||||
Reference in New Issue
Block a user