fix(windows): resolve DWM transparency, eliminate white flash, fix capture staging, and terminate locked processes in installer
Some checks are pending
Release & Packaging / Build Linux Packages (.tar.gz, .deb, .rpm, .AppImage) (push) Waiting to run
Release & Packaging / Build Windows Binaries (.exe) (push) Waiting to run
Release & Packaging / Publish GitHub Release (push) Blocked by required conditions
Some checks are pending
Release & Packaging / Build Linux Packages (.tar.gz, .deb, .rpm, .AppImage) (push) Waiting to run
Release & Packaging / Build Windows Binaries (.exe) (push) Waiting to run
Release & Packaging / Publish GitHub Release (push) Blocked by required conditions
This commit is contained in:
parent
fbfe6f2360
commit
5b4797ceff
7 changed files with 131 additions and 34 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -3700,7 +3700,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "scythe"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"cpal",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "scythe"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
|
|
@ -45,9 +45,11 @@ windows = { version = "0.58.0", features = [
|
|||
"Win32_Graphics_Direct3D11",
|
||||
"Win32_Graphics_Dxgi",
|
||||
"Win32_Graphics_Dxgi_Common",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Security",
|
||||
"Win32_System_Com",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_UI_Controls",
|
||||
"Win32_System_Threading",
|
||||
] }
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ RequestExecutionLevel user
|
|||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
Section "Scythe" SecMain
|
||||
; Terminate any running instances so locked executables and DLLs can be updated cleanly
|
||||
nsExec::Exec 'cmd.exe /C taskkill /F /IM scythe-ui.exe /T >nul 2>&1'
|
||||
nsExec::Exec 'cmd.exe /C taskkill /F /IM scythe-daemon.exe /T >nul 2>&1'
|
||||
Sleep 500
|
||||
|
||||
SetOverwrite on
|
||||
SetOutPath "$INSTDIR"
|
||||
File /r "..\..\dist\bundle\*.*"
|
||||
|
||||
|
|
@ -43,6 +49,10 @@ Section "Scythe" SecMain
|
|||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
nsExec::Exec 'cmd.exe /C taskkill /F /IM scythe-ui.exe /T >nul 2>&1'
|
||||
nsExec::Exec 'cmd.exe /C taskkill /F /IM scythe-daemon.exe /T >nul 2>&1'
|
||||
Sleep 500
|
||||
|
||||
Delete "$DESKTOP\scythe.lnk"
|
||||
RMDir /r "$SMPROGRAMS\scythe"
|
||||
RMDir /r "$INSTDIR"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ SilentInstall silent
|
|||
RequestExecutionLevel user
|
||||
|
||||
Section
|
||||
; Terminate any running instances so locked executables and DLLs can be updated cleanly
|
||||
nsExec::Exec 'cmd.exe /C taskkill /F /IM scythe-ui.exe /T >nul 2>&1'
|
||||
nsExec::Exec 'cmd.exe /C taskkill /F /IM scythe-daemon.exe /T >nul 2>&1'
|
||||
Sleep 500
|
||||
|
||||
SetOverwrite on
|
||||
; Extract self-contained binaries and all runtime DLLs directly to user app data
|
||||
SetOutPath "$LOCALAPPDATA\scythe"
|
||||
File /r "..\..\dist\bundle\*.*"
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ pub struct WindowsCapture {
|
|||
device: ID3D11Device,
|
||||
context: ID3D11DeviceContext,
|
||||
duplication: IDXGIOutputDuplication,
|
||||
gpu_texture: ID3D11Texture2D,
|
||||
staging_texture: ID3D11Texture2D,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
last_frame: Vec<u8>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
@ -85,33 +86,34 @@ impl WindowsCapture {
|
|||
// Initialize Desktop Duplication
|
||||
let duplication = output1.DuplicateOutput(&device)?;
|
||||
|
||||
// Allocate a dedicated VRAM-resident GPU texture for 100% zero-copy capture
|
||||
let gpu_desc = D3D11_TEXTURE2D_DESC {
|
||||
// Allocate a CPU-accessible staging texture for hardware frame reading
|
||||
let staging_desc = D3D11_TEXTURE2D_DESC {
|
||||
Width: width,
|
||||
Height: height,
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
|
||||
Usage: D3D11_USAGE_DEFAULT,
|
||||
BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
|
||||
CPUAccessFlags: 0,
|
||||
Usage: D3D11_USAGE_STAGING,
|
||||
BindFlags: 0,
|
||||
CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
|
||||
MiscFlags: 0,
|
||||
};
|
||||
|
||||
let mut gpu_texture: Option<ID3D11Texture2D> = None;
|
||||
device.CreateTexture2D(&gpu_desc, None, Some(&mut gpu_texture))?;
|
||||
let gpu_texture = gpu_texture.ok_or("Failed to create GPU VRAM texture")?;
|
||||
let mut staging_texture: Option<ID3D11Texture2D> = None;
|
||||
device.CreateTexture2D(&staging_desc, None, Some(&mut staging_texture))?;
|
||||
let staging_texture = staging_texture.ok_or("Failed to create D3D11 staging texture")?;
|
||||
|
||||
println!("Windows DXGI Hardware Capture active (Pure GPU Zero-Copy): {}x{}", width, height);
|
||||
println!("Windows DXGI Hardware Desktop Duplication active: {}x{}", width, height);
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
context,
|
||||
duplication,
|
||||
gpu_texture,
|
||||
staging_texture,
|
||||
width,
|
||||
height,
|
||||
last_frame: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -143,33 +145,52 @@ impl FrameSource for WindowsCapture {
|
|||
let mut desktop_resource: Option<IDXGIResource> = None;
|
||||
|
||||
for _ in 0..5 {
|
||||
match self.duplication.AcquireNextFrame(150, &mut frame_info, &mut desktop_resource) {
|
||||
match self.duplication.AcquireNextFrame(100, &mut frame_info, &mut desktop_resource) {
|
||||
Ok(()) => {
|
||||
if let Some(resource) = desktop_resource {
|
||||
let texture: ID3D11Texture2D = resource.cast()?;
|
||||
|
||||
// Copy directly on the GPU from desktop buffer to persistent VRAM texture
|
||||
self.context.CopyResource(&self.gpu_texture, &texture);
|
||||
// Copy GPU desktop texture to staging texture accessible by CPU
|
||||
self.context.CopyResource(&self.staging_texture, &texture);
|
||||
|
||||
// Immediately release the desktop frame back to the DWM compositor
|
||||
let _ = self.duplication.ReleaseFrame();
|
||||
|
||||
return Ok(Frame::D3D11Texture {
|
||||
handle: self.gpu_texture.as_raw() as usize,
|
||||
let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
|
||||
self.context.Map(&self.staging_texture, 0, D3D11_MAP_READ, 0, Some(&mut mapped))?;
|
||||
|
||||
let stride = mapped.RowPitch as usize;
|
||||
let total_bytes = stride * self.height as usize;
|
||||
if self.last_frame.len() != total_bytes {
|
||||
self.last_frame.resize(total_bytes, 0);
|
||||
}
|
||||
std::ptr::copy_nonoverlapping(
|
||||
mapped.pData as *const u8,
|
||||
self.last_frame.as_mut_ptr(),
|
||||
total_bytes,
|
||||
);
|
||||
self.context.Unmap(&self.staging_texture, 0);
|
||||
|
||||
return Ok(Frame::Raw {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
stride: stride as u32,
|
||||
data: self.last_frame.clone(),
|
||||
});
|
||||
}
|
||||
let _ = self.duplication.ReleaseFrame();
|
||||
}
|
||||
Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(8));
|
||||
return Ok(Frame::D3D11Texture {
|
||||
handle: self.gpu_texture.as_raw() as usize,
|
||||
if !self.last_frame.is_empty() {
|
||||
return Ok(Frame::Raw {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
stride: self.width * 4,
|
||||
data: self.last_frame.clone(),
|
||||
});
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(8));
|
||||
}
|
||||
Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => {
|
||||
println!("DXGI access lost (display mode or fullscreen switch), reacquiring...");
|
||||
let _ = self.reinit_duplication();
|
||||
|
|
@ -182,11 +203,21 @@ impl FrameSource for WindowsCapture {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(Frame::D3D11Texture {
|
||||
handle: self.gpu_texture.as_raw() as usize,
|
||||
if !self.last_frame.is_empty() {
|
||||
Ok(Frame::Raw {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
stride: self.width * 4,
|
||||
data: self.last_frame.clone(),
|
||||
})
|
||||
} else {
|
||||
Ok(Frame::Raw {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
stride: self.width * 4,
|
||||
data: vec![0u8; (self.width * self.height * 4) as usize],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ pub fn parse_hotkey(s: &str) -> Option<HotKey> {
|
|||
"delete" | "del" => Some(Code::Delete),
|
||||
"home" => Some(Code::Home),
|
||||
"end" => Some(Code::End),
|
||||
"pageup" | "pgup" => Some(Code::PageUp),
|
||||
"pagedown" | "pgdn" => Some(Code::PageDown),
|
||||
"pageup" | "pgup" | "page_up" => Some(Code::PageUp),
|
||||
"pagedown" | "pgdn" | "page_down" => Some(Code::PageDown),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -912,7 +912,7 @@ impl ScytheOverlayApp {
|
|||
self.output_dir = new_dir.clone();
|
||||
self.config.output_directory = new_dir;
|
||||
let _ = self.config.save();
|
||||
let _ = ipc::send_command(Command::ReloadConfig);
|
||||
async_send_command(Command::ReloadConfig);
|
||||
self.refresh_clips();
|
||||
}
|
||||
}
|
||||
|
|
@ -2130,19 +2130,22 @@ impl eframe::App for ScytheOverlayApp {
|
|||
if self.listening_keybind.is_some() || self.hud_notification.is_some() {
|
||||
ctx.request_repaint();
|
||||
} else {
|
||||
ctx.request_repaint_after(Duration::from_millis(33));
|
||||
ctx.request_repaint_after(Duration::from_millis(16));
|
||||
}
|
||||
|
||||
// Auto-position and size to monitor on launch
|
||||
// Auto-position, DWM transparency, and size to monitor on launch
|
||||
if !self.initial_pos_set {
|
||||
if let Some(monitor_size) = ctx.input(|i| i.viewport().monitor_size) {
|
||||
if monitor_size.x > 100.0 && monitor_size.y > 100.0 {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::OuterPosition(egui::pos2(0.0, 0.0)));
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(monitor_size));
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
apply_windows_transparency("Scythe");
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Visible(true));
|
||||
self.initial_pos_set = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle interactive keybind recording mode
|
||||
if let Some(action) = self.listening_keybind {
|
||||
|
|
@ -2155,6 +2158,10 @@ impl eframe::App for ScytheOverlayApp {
|
|||
break;
|
||||
}
|
||||
if let Some(key_name) = format_egui_key(*key) {
|
||||
let k_lower = key_name.to_lowercase();
|
||||
if ["alt", "ctrl", "control", "shift", "super", "meta", "command"].contains(&k_lower.as_str()) {
|
||||
continue;
|
||||
}
|
||||
let mut parts = Vec::new();
|
||||
if modifiers.ctrl {
|
||||
parts.push("Ctrl");
|
||||
|
|
@ -2259,6 +2266,27 @@ impl eframe::App for ScytheOverlayApp {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn apply_windows_transparency(title: &str) {
|
||||
unsafe {
|
||||
use windows::Win32::UI::WindowsAndMessaging::FindWindowW;
|
||||
use windows::Win32::Graphics::Dwm::DwmExtendFrameIntoClientArea;
|
||||
use windows::Win32::UI::Controls::MARGINS;
|
||||
use windows::core::HSTRING;
|
||||
|
||||
let htitle = HSTRING::from(title);
|
||||
if let Ok(hwnd) = FindWindowW(None, &htitle) {
|
||||
let margins = MARGINS {
|
||||
cxLeftWidth: -1,
|
||||
cxRightWidth: -1,
|
||||
cyTopHeight: -1,
|
||||
cyBottomHeight: -1,
|
||||
};
|
||||
let _ = DwmExtendFrameIntoClientArea(hwnd, &margins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_egui_overlay() {
|
||||
#[cfg(target_os = "windows")]
|
||||
let (screen_w, screen_h): (f32, f32) = unsafe {
|
||||
|
|
@ -2280,6 +2308,7 @@ pub fn run_egui_overlay() {
|
|||
.with_resizable(false)
|
||||
.with_decorations(false)
|
||||
.with_transparent(true)
|
||||
.with_visible(false)
|
||||
.with_always_on_top(),
|
||||
..Default::default()
|
||||
};
|
||||
|
|
@ -2300,6 +2329,7 @@ pub struct ShadowPlayToastApp {
|
|||
accent: Color32,
|
||||
created_at: Instant,
|
||||
duration: Duration,
|
||||
initial_setup: bool,
|
||||
}
|
||||
|
||||
impl eframe::App for ShadowPlayToastApp {
|
||||
|
|
@ -2308,6 +2338,13 @@ impl eframe::App for ShadowPlayToastApp {
|
|||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
if !self.initial_setup {
|
||||
#[cfg(target_os = "windows")]
|
||||
apply_windows_transparency("Scythe Notification");
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Visible(true));
|
||||
self.initial_setup = true;
|
||||
}
|
||||
|
||||
let elapsed = self.created_at.elapsed().as_secs_f32();
|
||||
let total_dur = self.duration.as_secs_f32();
|
||||
if elapsed >= total_dur {
|
||||
|
|
@ -2427,6 +2464,15 @@ pub fn run_egui_toast(title: &str, subtitle: &str, icon: crate::overlay::ToastIc
|
|||
let toast_w: f32 = 340.0;
|
||||
let toast_h: f32 = 64.0;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
use windows::Win32::UI::WindowsAndMessaging::{FindWindowW, PostMessageW, WM_CLOSE};
|
||||
if let Ok(prev) = FindWindowW(None, windows::core::w!("Scythe Notification")) {
|
||||
let _ = PostMessageW(prev, WM_CLOSE, windows::Win32::Foundation::WPARAM(0), windows::Win32::Foundation::LPARAM(0));
|
||||
std::thread::sleep(Duration::from_millis(30));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let screen_w: f32 = unsafe {
|
||||
use windows::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_CXSCREEN};
|
||||
|
|
@ -2447,6 +2493,7 @@ pub fn run_egui_toast(title: &str, subtitle: &str, icon: crate::overlay::ToastIc
|
|||
.with_inner_size([toast_w, toast_h])
|
||||
.with_decorations(false)
|
||||
.with_transparent(true)
|
||||
.with_visible(false)
|
||||
.with_always_on_top()
|
||||
.with_resizable(false),
|
||||
..Default::default()
|
||||
|
|
@ -2459,6 +2506,7 @@ pub fn run_egui_toast(title: &str, subtitle: &str, icon: crate::overlay::ToastIc
|
|||
accent,
|
||||
created_at: Instant::now(),
|
||||
duration: Duration::from_millis(2800),
|
||||
initial_setup: false,
|
||||
};
|
||||
|
||||
// Watchdog thread to guarantee exit after duration
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue