Skip to content

Commit eb164ce

Browse files
committed
Add directx interop support
- cargo update - refactor window filtering code
1 parent 33f45a1 commit eb164ce

File tree

8 files changed

+235
-244
lines changed

8 files changed

+235
-244
lines changed

Cargo.lock

Lines changed: 128 additions & 186 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ members = ["zbl", "zbl_py"]
33
resolver = "2"
44

55
[workspace.package]
6-
version = "0.3.0"
6+
version = "0.4.0"
7+
8+
[profile.release]
9+
strip = true

zbl/Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ once_cell = "1"
1111
log = "0.4"
1212

1313
[dependencies.windows]
14-
version = "0.54"
14+
version = "0.56"
1515
features = [
1616
"Foundation",
1717
"Graphics_Capture",
1818
"Graphics_DirectX_Direct3D11",
1919
"Win32_Foundation",
2020
"Win32_Graphics_Direct3D",
21+
"Win32_Graphics_Direct3D9",
22+
"Win32_Graphics_Direct3D10",
2123
"Win32_Graphics_Direct3D11",
2224
"Win32_Graphics_Dwm",
2325
"Win32_Graphics_Dxgi_Common",
@@ -34,7 +36,6 @@ features = [
3436
clap = { version = "4", features = ["derive"] }
3537

3638
[dev-dependencies.opencv]
37-
git = "https://github.com/modelflat/opencv-rust"
38-
branch = "directx-interop"
39+
version = "0.91"
3940
default-features = false
40-
features = ["highgui", "directx"]
41+
features = ["highgui"]

zbl/examples/directx_interop.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ fn main() {
2929
};
3030

3131
let mut capture = Capture::new(target, true, false).expect("failed to initialize capture");
32-
unsafe {
33-
opencv::core::initialize_context_from_d3d11_device(&capture.d3d.device)
34-
.expect("initialize d3d11")
35-
};
32+
33+
opencv::core::initialize_context_from_d3d11_device(&mut capture.d3d.device)
34+
.expect("initialize d3d11");
3635

3736
capture.start().expect("failed to start capture");
3837

@@ -46,11 +45,9 @@ fn main() {
4645
let mut gpu_mat = UMat::new_def();
4746
loop {
4847
let t_frame_start = Instant::now();
49-
if let Some(frame) = capture.grab().expect("failed to get frame") {
50-
unsafe {
51-
opencv::core::convert_from_d3d11_texture_2d(&frame.texture, &mut gpu_mat)
52-
.expect("convert from d3d11 texture")
53-
}
48+
if let Some(mut frame) = capture.grab().expect("failed to get frame") {
49+
opencv::core::convert_from_d3d11_texture_2d(&mut frame.texture, &mut gpu_mat)
50+
.expect("convert from d3d11 texture");
5451
let t_frame_end = Instant::now();
5552

5653
highgui::imshow("Test", &gpu_mat).expect("failed to show frame");

zbl/examples/simple.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use opencv::{
55
core::{Mat, Size, CV_8UC4},
66
highgui,
77
};
8+
use windows::Win32::Foundation::HWND;
89
use zbl::{Capturable, Capture, Display, Window};
910

1011
#[derive(Parser, Debug)]
@@ -13,6 +14,8 @@ struct Args {
1314
#[clap(long)]
1415
window_name: Option<String>,
1516
#[clap(long)]
17+
window_handle: Option<isize>,
18+
#[clap(long)]
1619
display_id: Option<usize>,
1720
}
1821

@@ -23,6 +26,11 @@ fn main() {
2326

2427
let target = if let Some(window_name) = args.window_name {
2528
let window = Window::find_first(&window_name).expect("failed to find window");
29+
window.print_info();
30+
Box::new(window) as Box<dyn Capturable>
31+
} else if let Some(window_handle) = args.window_handle {
32+
let window = Window::new(HWND(window_handle));
33+
window.print_info();
2634
Box::new(window) as Box<dyn Capturable>
2735
} else if let Some(display_id) = args.display_id {
2836
let display = Display::find_by_id(display_id).expect("failed to find display");
@@ -47,7 +55,7 @@ fn main() {
4755
if let Some(frame) = capture.grab().expect("failed to get frame") {
4856
let desc = frame.desc();
4957
let mat = unsafe {
50-
Mat::new_size_with_data(
58+
Mat::new_size_with_data_unsafe(
5159
Size::new(desc.Width as i32, desc.Height as i32),
5260
CV_8UC4,
5361
frame.mapped_ptr.pData,

zbl/src/capture/window.rs

Lines changed: 82 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ extern "system" fn object_destroyed_cb(
6666
};
6767

6868
if has_been_closed {
69-
unsafe { UnhookWinEvent(this) };
69+
unsafe {
70+
let _ = UnhookWinEvent(this);
71+
}
7072
}
7173
}
7274
}
@@ -152,50 +154,73 @@ impl Window {
152154
unsafe { IsWindowVisible(self.handle).as_bool() }
153155
}
154156

155-
pub fn is_capturable(&self) -> bool {
156-
unsafe {
157-
if self.title.is_empty()
158-
|| !self.is_visible()
159-
|| self.handle == GetShellWindow()
160-
|| self.handle == GetConsoleWindow()
161-
|| GetAncestor(self.handle, GA_ROOT) != self.handle
162-
{
163-
return false;
164-
}
165-
}
157+
pub fn is_shell_window(&self) -> bool {
158+
self.handle == unsafe { GetShellWindow() }
159+
}
166160

167-
let style = unsafe { GetWindowLongW(self.handle, GWL_STYLE) };
168-
if style & (WS_DISABLED.0 as i32) == 1 {
169-
return false;
170-
}
161+
pub fn is_console_window(&self) -> bool {
162+
self.handle == unsafe { GetConsoleWindow() }
163+
}
171164

172-
// No tooltips
173-
let ex_style = unsafe { GetWindowLongW(self.handle, GWL_EXSTYLE) };
174-
if ex_style & (WS_EX_TOOLWINDOW.0 as i32) == 1 {
175-
return false;
176-
}
165+
pub fn get_root(&self) -> HWND {
166+
unsafe { GetAncestor(self.handle, GA_ROOT) }
167+
}
177168

178-
// Unfortunate work-around. Not sure how to avoid this.
179-
if self.is_known_blocked_window() {
169+
pub fn is_top_level(&self) -> bool {
170+
self.get_root() == self.handle
171+
}
172+
173+
/// https://learn.microsoft.com/en-us/windows/win32/winmsg/window-styles
174+
pub fn get_style(&self) -> i32 {
175+
unsafe { GetWindowLongW(self.handle, GWL_STYLE) }
176+
}
177+
178+
/// https://learn.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
179+
pub fn get_ex_style(&self) -> i32 {
180+
unsafe { GetWindowLongW(self.handle, GWL_EXSTYLE) }
181+
}
182+
183+
pub fn is_disabled(&self) -> bool {
184+
self.get_style() & (WS_DISABLED.0 as i32) == 1
185+
}
186+
187+
pub fn is_tooltip(&self) -> bool {
188+
self.get_ex_style() & (WS_EX_TOOLWINDOW.0 as i32) == 1
189+
}
190+
191+
pub fn is_uwp_window(&self) -> bool {
192+
self.class_name == "Windows.UI.Core.CoreWindow"
193+
|| self.class_name == "ApplicationFrameWindow"
194+
}
195+
196+
pub fn is_dwm_cloaked(&self) -> bool {
197+
let mut cloaked: u32 = 0;
198+
let dwm_attr_cloaked = unsafe {
199+
DwmGetWindowAttribute(
200+
self.handle,
201+
DWMWA_CLOAKED,
202+
&mut cloaked as *mut _ as *mut _,
203+
std::mem::size_of::<u32>() as u32,
204+
)
205+
};
206+
dwm_attr_cloaked.is_ok() && cloaked == DWM_CLOAKED_SHELL
207+
}
208+
209+
pub fn is_capturable(&self) -> bool {
210+
if !self.is_visible()
211+
|| self.is_shell_window()
212+
|| self.is_console_window()
213+
|| !self.is_top_level()
214+
|| self.is_disabled()
215+
|| self.is_tooltip()
216+
|| self.is_known_blocked_window()
217+
{
180218
return false;
181219
}
182220

183221
// Check to see if the self is cloaked if it's a UWP
184-
if self.class_name == "Windows.UI.Core.CoreWindow"
185-
|| self.class_name == "ApplicationFrameWindow"
186-
{
187-
let mut cloaked: u32 = 0;
188-
let dwm_attr_cloaked = unsafe {
189-
DwmGetWindowAttribute(
190-
self.handle,
191-
DWMWA_CLOAKED,
192-
&mut cloaked as *mut _ as *mut _,
193-
std::mem::size_of::<u32>() as u32,
194-
)
195-
};
196-
if dwm_attr_cloaked.is_ok() && cloaked == DWM_CLOAKED_SHELL {
197-
return false;
198-
}
222+
if self.is_uwp_window() && self.is_dwm_cloaked() {
223+
return false;
199224
}
200225

201226
true
@@ -206,6 +231,24 @@ impl Window {
206231
unsafe { GetWindowThreadProcessId(self.handle, Some(&mut process_id)) };
207232
process_id
208233
}
234+
235+
pub fn print_info(&self) {
236+
println!("title = {}", self.title);
237+
println!("class = {}", self.class_name);
238+
println!("is_capturable = {}", self.is_capturable());
239+
println!("\tis_visible = {}", self.is_visible());
240+
println!("\tis_shell_window = {}", self.is_shell_window());
241+
println!("\tis_console_window = {}", self.is_console_window());
242+
println!("\tis_top_level = {}", self.is_top_level());
243+
println!("\tis_disabled = {}", self.is_disabled());
244+
println!("\tis_tooltip = {}", self.is_tooltip());
245+
println!("\tis_uwp_window = {}", self.is_uwp_window());
246+
println!("\tis_dwm_cloaked = {}", self.is_dwm_cloaked());
247+
println!(
248+
"\tis_known_blocked_window = {}",
249+
self.is_known_blocked_window()
250+
);
251+
}
209252
}
210253

211254
impl Capturable for Window {
@@ -220,7 +263,7 @@ impl Capturable for Window {
220263
let mut top_left = POINT::default();
221264
unsafe {
222265
GetWindowRect(self.handle, &mut window_rect)?;
223-
ClientToScreen(self.handle, &mut top_left);
266+
let _ = ClientToScreen(self.handle, &mut top_left);
224267
GetClientRect(self.handle, &mut client_rect)?;
225268
}
226269

zbl_py/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,3 @@ thiserror = "1"
1414
[dependencies.pyo3]
1515
version = "0.21"
1616
features = ["extension-module"]
17-
18-
[profile.release]
19-
strip = true

zbl_py/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "zbl"
33
description = "real-time window capture library based on D3D11 and Windows.Graphics.Capture"
4-
version = "0.3.0"
4+
version = "0.4.0"
55
readme = "../README.md"
66
requires-python = ">=3.7"
77
license = { file = "../LICENSE.txt" }

0 commit comments

Comments
 (0)