Skip to content

Commit 023b11b

Browse files
committed
implement support for display capture
1 parent c43bd56 commit 023b11b

File tree

14 files changed

+632
-415
lines changed

14 files changed

+632
-415
lines changed

Cargo.lock

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

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Alternatively, you can install suitable wheel from [releases page](https://githu
2121
```python
2222
from zbl import Capture
2323

24-
with Capture('visual studio code') as cap:
24+
with Capture(window_name='visual studio code') as cap:
2525
frame = next(cap.frames())
2626
print(frame.shape)
2727
```
@@ -31,7 +31,7 @@ The snippet above will capture a window which title contains the string `visual
3131
To run an example using OpenCV's `highgui`:
3232

3333
1. Install `opencv-python`
34-
2. Run `python -m zbl '<full or partial window name, case insensitive>'`
34+
2. Run `python -m zbl --window-name '<full or partial window name, case insensitive>'`
3535

3636
## Rust
3737

zbl/Cargo.toml

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "zbl"
3-
version = "0.0.4"
3+
version = "0.1.0"
44
edition = "2021"
55

66
[lib]
@@ -13,27 +13,29 @@ lazy_static = "1"
1313
version = "0.43"
1414
features = [
1515
"Foundation",
16+
"Graphics",
17+
"Graphics_Capture",
18+
"Graphics_DirectX",
19+
"Graphics_DirectX_Direct3D11",
20+
"UI",
1621
"Win32_Foundation",
17-
"Win32_UI_HiDpi",
18-
"Win32_UI_WindowsAndMessaging",
19-
"Win32_UI_Accessibility",
20-
"Win32_System_Console",
22+
"Win32_Graphics_Direct3D",
23+
"Win32_Graphics_Direct3D11",
2124
"Win32_Graphics_Dwm",
22-
"Win32_Graphics_Gdi",
2325
"Win32_Graphics_Dxgi",
2426
"Win32_Graphics_Dxgi_Common",
25-
"Win32_Graphics_Direct3D",
26-
"Win32_Graphics_Direct3D11",
27+
"Win32_Graphics_Gdi",
28+
"Win32_System_Console",
2729
"Win32_System_WinRT",
2830
"Win32_System_WinRT_Direct3D11",
2931
"Win32_System_WinRT_Graphics_Capture",
30-
"UI",
31-
"Graphics",
32-
"Graphics_Capture",
33-
"Graphics_DirectX",
34-
"Graphics_DirectX_Direct3D11",
32+
"Win32_UI_Accessibility",
33+
"Win32_UI_HiDpi",
34+
"Win32_UI_Shell",
35+
"Win32_UI_Shell_Common",
36+
"Win32_UI_WindowsAndMessaging",
3537
]
3638

3739
[dev-dependencies]
3840
clap = { version = "4", features = ["derive"] }
39-
opencv = "0.77"
41+
opencv = "0.82"

zbl/examples/trivial.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,35 @@ use std::time::Instant;
22

33
use clap::Parser;
44
use opencv::{highgui, prelude::*};
5-
use zbl::{init, Capture, Frame, Window};
5+
use zbl::{display::Display, Capturable, Capture, Frame, Window};
66

77
#[derive(Parser, Debug)]
88
#[clap(version)]
99
struct Args {
1010
#[clap(long)]
11-
window: String,
11+
window_name: Option<String>,
12+
#[clap(long)]
13+
display_id: Option<usize>,
1214
}
1315

1416
fn main() {
15-
init();
17+
zbl::init();
1618

1719
let args = Args::parse();
18-
let window = Window::find_first(&args.window).expect("failed to find window");
19-
let mut capturer = Capture::new(window).expect("failed to initialize capture");
2020

21-
capturer.start().expect("failed to start capture");
21+
let mut target = if let Some(window_name) = args.window_name {
22+
let window = Window::find_first(&window_name).expect("failed to find window");
23+
Box::new(window) as Box<dyn Capturable>
24+
} else if let Some(display_id) = args.display_id {
25+
let display = Display::find_by_id(display_id).expect("failed to find display");
26+
Box::new(display) as Box<dyn Capturable>
27+
} else {
28+
panic!("either --window-name or --display-id should be set!");
29+
};
30+
31+
let mut capture = Capture::new(target, true).expect("failed to initialize capture");
32+
33+
capture.start().expect("failed to start capture");
2234

2335
highgui::named_window("Test", highgui::WINDOW_AUTOSIZE).expect("failed to setup opencv window");
2436

@@ -28,7 +40,7 @@ fn main() {
2840
let mut tt = 0f32;
2941
loop {
3042
let t = Instant::now();
31-
if let Some(Frame { texture, ptr }) = capturer.grab().expect("failed to get frame") {
43+
if let Some(Frame { texture, ptr }) = capture.grab().expect("failed to get frame") {
3244
let mat = unsafe {
3345
Mat::new_size_with_data(
3446
opencv::core::Size::new(texture.desc.Width as i32, texture.desc.Height as i32),
@@ -56,5 +68,5 @@ fn main() {
5668
}
5769
}
5870

59-
capturer.stop().expect("failed to stop capture");
71+
capture.stop().expect("failed to stop capture");
6072
}

zbl/src/capture.rs

Lines changed: 35 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
1-
use std::{
2-
collections::HashMap,
3-
sync::{
4-
mpsc::{sync_channel, Receiver, SyncSender, TryRecvError, TrySendError},
5-
RwLock,
6-
},
7-
};
1+
use std::sync::mpsc::{sync_channel, Receiver, TryRecvError, TrySendError};
82

9-
use lazy_static::lazy_static;
103
use windows::{
114
core::{IInspectable, Interface, Result},
125
Foundation::TypedEventHandler,
@@ -15,62 +8,18 @@ use windows::{
158
DirectX::{Direct3D11::IDirect3DDevice, DirectXPixelFormat},
169
SizeInt32,
1710
},
18-
Win32::{
19-
Foundation::HWND,
20-
Graphics::Direct3D11::{
21-
ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_BOX,
22-
D3D11_MAPPED_SUBRESOURCE, D3D11_TEXTURE2D_DESC,
23-
},
24-
UI::{
25-
Accessibility::{SetWinEventHook, UnhookWinEvent, HWINEVENTHOOK},
26-
WindowsAndMessaging::{EVENT_OBJECT_DESTROY, WINEVENT_OUTOFCONTEXT},
27-
},
11+
Win32::Graphics::Direct3D11::{
12+
ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_BOX, D3D11_MAPPED_SUBRESOURCE,
13+
D3D11_TEXTURE2D_DESC,
2814
},
2915
};
3016

3117
use crate::{
3218
staging_texture::StagingTexture,
3319
util::{create_d3d_device, create_direct3d_device, get_dxgi_interface_from_object},
34-
window::Window,
20+
Capturable,
3521
};
3622

37-
lazy_static! {
38-
static ref OBJECT_DESTROYED_USER_DATA: RwLock<HashMap<isize, (isize, SyncSender<()>)>> =
39-
Default::default();
40-
}
41-
42-
extern "system" fn object_destroyed_cb(
43-
this: HWINEVENTHOOK,
44-
_: u32,
45-
handle: HWND,
46-
id_object: i32,
47-
id_child: i32,
48-
_: u32,
49-
_: u32,
50-
) {
51-
if id_object == 0 && id_child == 0 && handle != HWND::default() {
52-
let has_been_closed = if let Ok(handles) = OBJECT_DESTROYED_USER_DATA.read() {
53-
if let Some((window_handle, tx)) = handles.get(&this.0) {
54-
if *window_handle == handle.0 {
55-
tx.send(()).ok();
56-
true
57-
} else {
58-
false
59-
}
60-
} else {
61-
false
62-
}
63-
} else {
64-
// TODO is that correct?
65-
true
66-
};
67-
68-
if has_been_closed {
69-
unsafe { UnhookWinEvent(this) };
70-
}
71-
}
72-
}
73-
7423
pub struct Frame<'a> {
7524
pub texture: &'a StagingTexture,
7625
pub ptr: D3D11_MAPPED_SUBRESOURCE,
@@ -80,9 +29,9 @@ pub struct Capture {
8029
device: ID3D11Device,
8130
direct3d_device: IDirect3DDevice,
8231
context: ID3D11DeviceContext,
83-
window: Window,
84-
window_box: D3D11_BOX,
85-
window_closed_signal: Receiver<()>,
32+
capturable: Box<dyn Capturable>,
33+
capture_box: D3D11_BOX,
34+
capture_done_signal: Receiver<()>,
8635
frame_pool: Direct3D11CaptureFramePool,
8736
frame_source: Receiver<Option<Direct3D11CaptureFrame>>,
8837
session: GraphicsCaptureSession,
@@ -92,51 +41,31 @@ pub struct Capture {
9241
}
9342

9443
impl Capture {
95-
/// Create a new capture of `window`. This will initialize D3D11 devices, context, and Windows.Graphics.Capture's
44+
/// Create a new capture. This will initialize D3D11 devices, context, and Windows.Graphics.Capture's
9645
/// frame pool / capture session.
9746
///
9847
/// Note that this will not start capturing yet. Call `start()` to actually start receiving frames.
99-
pub fn new(window: Window) -> Result<Self> {
100-
let d3d_device = create_d3d_device()?;
101-
let d3d_context = {
48+
pub fn new(capturable: Box<dyn Capturable>, capture_cursor: bool) -> Result<Self> {
49+
let device = create_d3d_device()?;
50+
let context = unsafe {
10251
let mut d3d_context = None;
103-
unsafe { d3d_device.GetImmediateContext(&mut d3d_context) };
52+
device.GetImmediateContext(&mut d3d_context);
10453
d3d_context.expect("failed to create d3d_context")
10554
};
106-
let device = create_direct3d_device(&d3d_device)?;
55+
let direct3d_device = create_direct3d_device(&device)?;
10756

108-
let capture_item = window.create_capture_item()?;
57+
let capture_item = capturable.create_capture_item()?;
10958
let capture_item_size = capture_item.Size()?;
11059

11160
let frame_pool = Direct3D11CaptureFramePool::CreateFreeThreaded(
112-
&device,
61+
&direct3d_device,
11362
DirectXPixelFormat::B8G8R8A8UIntNormalized,
11463
1,
11564
capture_item_size,
11665
)?;
11766

11867
let session = frame_pool.CreateCaptureSession(&capture_item)?;
119-
session.SetIsCursorCaptureEnabled(false)?;
120-
121-
let (sender, window_closed_signal) = sync_channel(1);
122-
let hook_id = unsafe {
123-
SetWinEventHook(
124-
EVENT_OBJECT_DESTROY,
125-
EVENT_OBJECT_DESTROY,
126-
None,
127-
Some(object_destroyed_cb),
128-
// TODO filtering by process id does not always catch the moment when the window is closed
129-
// why? aren't windows bound to their process ids?
130-
// moreover, for explorer windows even that does not work.
131-
// need some more realiable and simpler way to track window closing
132-
0,
133-
0,
134-
WINEVENT_OUTOFCONTEXT,
135-
)
136-
};
137-
if let Ok(mut handles) = OBJECT_DESTROYED_USER_DATA.write() {
138-
handles.insert(hook_id.0, (window.handle.0, sender));
139-
}
68+
session.SetIsCursorCaptureEnabled(capture_cursor)?;
14069

14170
let (sender, receiver) = sync_channel(1 << 5);
14271
frame_pool.FrameArrived(
@@ -160,15 +89,16 @@ impl Capture {
16089
),
16190
)?;
16291

163-
let window_box = window.get_client_box();
92+
let capture_box = capturable.get_client_box()?;
93+
let capture_done_signal = capturable.get_close_notification_channel();
16494

16595
Ok(Self {
166-
device: d3d_device,
167-
direct3d_device: device,
168-
context: d3d_context,
169-
window,
170-
window_box,
171-
window_closed_signal,
96+
device,
97+
direct3d_device,
98+
context,
99+
capturable,
100+
capture_box,
101+
capture_done_signal,
172102
frame_pool,
173103
frame_source: receiver,
174104
session,
@@ -178,9 +108,9 @@ impl Capture {
178108
})
179109
}
180110

181-
/// Get attached window.
182-
pub fn window(&self) -> &Window {
183-
&self.window
111+
/// Get attached capturable.
112+
pub fn capturable(&self) -> &Box<dyn Capturable> {
113+
&self.capturable
184114
}
185115

186116
/// Start capturing frames.
@@ -195,7 +125,7 @@ impl Capture {
195125
///
196126
/// Returns:
197127
/// * `Ok(Some(...))` if there is a frame and it's been successfully captured;
198-
/// * `Ok(None)` if no frames can be received from the application (i.e. when the window was closed).
128+
/// * `Ok(None)` if no frames can be received (e.g. when the window was closed).
199129
/// * `Err(...)` if an error has occured while capturing a frame.
200130
pub fn grab(&mut self) -> Result<Option<Frame>> {
201131
if self.grab_next()? {
@@ -223,9 +153,9 @@ impl Capture {
223153
}
224154

225155
fn recreate_frame_pool(&mut self) -> Result<()> {
226-
let capture_item = self.window.create_capture_item()?;
156+
let capture_item = self.capturable.create_capture_item()?;
227157
let capture_item_size = capture_item.Size()?;
228-
self.window_box = self.window.get_client_box();
158+
self.capture_box = self.capturable.get_client_box()?;
229159
self.frame_pool.Recreate(
230160
&self.direct3d_device,
231161
DirectXPixelFormat::B8G8R8A8UIntNormalized,
@@ -245,7 +175,7 @@ impl Capture {
245175
Err(TryRecvError::Empty) => {
246176
// TODO busy loop? so uncivilized
247177
if let Ok(()) | Err(TryRecvError::Disconnected) =
248-
self.window_closed_signal.try_recv()
178+
self.capture_done_signal.try_recv()
249179
{
250180
self.stop()?;
251181
return Ok(false);
@@ -267,8 +197,8 @@ impl Capture {
267197
self.recreate_frame_pool()?;
268198
let new_staging_texture = StagingTexture::new(
269199
&self.device,
270-
self.window_box.right - self.window_box.left,
271-
self.window_box.bottom - self.window_box.top,
200+
self.capture_box.right - self.capture_box.left,
201+
self.capture_box.bottom - self.capture_box.top,
272202
desc.Format,
273203
)?;
274204
self.staging_texture = Some(new_staging_texture);
@@ -286,7 +216,7 @@ impl Capture {
286216
0,
287217
Some(&copy_src),
288218
0,
289-
Some(&self.window_box as *const _),
219+
Some(&self.capture_box as *const _),
290220
);
291221
}
292222

0 commit comments

Comments
 (0)