1use std::mem::size_of;
8use std::ptr::null;
9use std::time::Duration;
10
11use futures::StreamExt;
12use log::{debug, error, trace, warn};
13use tokio::time;
14use tokio::time::{Interval, MissedTickBehavior, sleep};
15use windows::core::Interface;
16use windows::core::Result as WinResult;
17use windows::Win32::Foundation::{BOOL, E_ACCESSDENIED, E_INVALIDARG, GENERIC_READ, GetLastError, POINT};
18use windows::Win32::Graphics::Direct3D::{D3D_DRIVER_TYPE_UNKNOWN, D3D_FEATURE_LEVEL, D3D_FEATURE_LEVEL_11_1};
19use windows::Win32::Graphics::Direct3D11::{D3D11_BIND_FLAG, D3D11_BIND_RENDER_TARGET, D3D11_CREATE_DEVICE_FLAG, D3D11_RESOURCE_MISC_FLAG, D3D11_RESOURCE_MISC_GDI_COMPATIBLE, D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC, D3D11_USAGE, D3D11_USAGE_DEFAULT, D3D11CreateDevice, ID3D11Device4, ID3D11DeviceContext4};
20use windows::Win32::Graphics::Dxgi::{DXGI_ERROR_ACCESS_DENIED, DXGI_ERROR_ACCESS_LOST, DXGI_ERROR_INVALID_CALL, DXGI_ERROR_SESSION_DISCONNECTED, DXGI_ERROR_UNSUPPORTED, DXGI_ERROR_WAIT_TIMEOUT, IDXGIDevice4, IDXGIOutputDuplication, IDXGIResource, IDXGISurface1};
21use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_SAMPLE_DESC};
22use windows::Win32::Graphics::Gdi::DeleteObject;
23use windows::Win32::System::StationsAndDesktops::{DESKTOP_ACCESS_FLAGS, OpenInputDesktop, SetThreadDesktop};
24use windows::Win32::System::StationsAndDesktops::DF_ALLOWOTHERACCOUNTHOOK;
25use windows::Win32::UI::WindowsAndMessaging::{CURSOR_SHOWING, CURSORINFO, DI_NORMAL, DrawIconEx, GetCursorInfo, GetIconInfo, HCURSOR};
26
27use crate::devices::Adapter;
28use crate::errors::DDApiError;
29use crate::outputs::{Display, DisplayVSyncStream};
30use crate::Result;
31use crate::texture::{Texture, TextureDesc};
32
33#[cfg(test)]
34mod test {
35 use std::sync::Once;
36 use std::time::{Duration, Instant};
37
38 use futures::FutureExt;
39 use futures::select;
40 use log::LevelFilter::Debug;
41 use tokio::time::interval;
42
43 use crate::{DDApiError, DuplicationApiOptions};
44 use crate::devices::AdapterFactory;
45 use crate::duplication::DesktopDuplicationApi;
46 use crate::outputs::DisplayMode;
47 use crate::utils::{co_init, set_process_dpi_awareness};
48
49 static INIT: Once = Once::new();
50
51 pub fn initialize() {
52 INIT.call_once(|| {
53 let _ = env_logger::builder().is_test(true).filter_level(Debug).try_init();
54 });
55 }
56
57 #[test]
58 fn test_duplication() {
59 initialize();
60
61 let rt = tokio::runtime::Builder::new_current_thread()
62 .thread_name("graphics_thread".to_owned()).enable_time().build().unwrap();
63
64 rt.block_on(async {
65 set_process_dpi_awareness();
66 co_init();
67
68 let adapter = AdapterFactory::new().get_adapter_by_idx(0).unwrap();
69 let output = adapter.get_display_by_idx(0).unwrap();
70 let mut dupl = DesktopDuplicationApi::new(adapter, output.clone()).unwrap();
71 let curr_mode = output.get_current_display_mode().unwrap();
72 dupl.configure(DuplicationApiOptions {
73 skip_cursor: true
74 });
75 let new_mode = DisplayMode {
76 width: 1920,
77 height: 1080,
78 orientation: Default::default(),
79 refresh_num: curr_mode.refresh_num,
80 refresh_den: curr_mode.refresh_den,
81 hdr: false,
82 };
83
84 let mut counter = 0;
85 let mut secs = 0;
86 let mut interval = interval(Duration::from_secs(1));
87 output.set_display_mode(&new_mode).unwrap();
88 loop {
89 select! {
90 tex = dupl.acquire_next_vsync_frame().fuse()=>{
91 match &tex {
92 Err(DDApiError::AccessDenied)| Err(DDApiError::AccessLost) => {
93 println!("error: {:?}",tex.err())
94 }
95 Err(e)=>{
96 println!("error: {:?}",e)
97 }
98 Ok(_)=>{
99 counter += 1;
100 }
101 }
102 },
103 _ = interval.tick().fuse() => {
104 println!("fps: {}",counter);
105 counter = 0;
106 secs+=1;
107 if secs == 5 {
108 output.set_display_mode(&curr_mode).unwrap();
109 println!("5 secs");
110 } else if secs ==10 {
111 break;
112 }
113 }
114 }
115 ;
116 };
117 });
118 }
119
120 #[test]
121 fn test_duplication_blocking() {
122 initialize();
123
124 set_process_dpi_awareness();
125 co_init();
126
127 let adapter = AdapterFactory::new().get_adapter_by_idx(0).unwrap();
128 let output = adapter.get_display_by_idx(0).unwrap();
129 let mut dupl = DesktopDuplicationApi::new(adapter, output.clone()).unwrap();
130 let curr_mode = output.get_current_display_mode().unwrap();
131 let new_mode = DisplayMode {
132 width: 1920,
133 height: 1080,
134 orientation: Default::default(),
135 refresh_num: curr_mode.refresh_num,
136 refresh_den: curr_mode.refresh_den,
137 hdr: false,
138 };
139
140 let mut counter = 0;
141 let mut secs = 0;
142 let instant = Instant::now();
143 loop {
144 let _ = output.wait_for_vsync();
145 let tex = dupl.acquire_next_frame_now();
146 if let Err(e) = tex {
147 println!("error: {:?}", e)
148 } else {
149 counter += 1;
150 };
151 if secs != instant.elapsed().as_secs() {
152 println!("fps: {}", counter);
153 counter = 0;
154 secs += 1;
155 if secs == 1 {
156 println!("1 secs");
157 output.set_display_mode(&new_mode).unwrap();
158 } else if secs == 5 {
159 output.set_display_mode(&curr_mode).unwrap();
160 break;
161 }
162 }
163 }
164 }
165}
166
167
168pub struct DesktopDuplicationApi {
208 d3d_device: ID3D11Device4,
209 d3d_ctx: ID3D11DeviceContext4,
210 output: Display,
211 vsync_stream: DisplayVSyncStream,
212 dupl: Option<IDXGIOutputDuplication>,
213
214 options: DuplicationApiOptions,
215
216 state: DuplicationState,
217
218}
219
220unsafe impl Send for DesktopDuplicationApi {}
221
222unsafe impl Sync for DesktopDuplicationApi {}
223
224
225impl DesktopDuplicationApi {
226 pub fn new(adapter: Adapter, output: Display) -> Result<Self> {
235 let (device, ctx) = Self::create_device(&adapter)?;
236 Self::new_with(device, ctx, output)
237 }
238
239 pub fn new_with(d3d_device: ID3D11Device4, ctx: ID3D11DeviceContext4, output: Display) -> Result<Self> {
241 let dupl = Self::create_dupl_output(&d3d_device, &output)?;
242 Ok(Self {
243 d3d_device,
244 d3d_ctx: ctx,
245 vsync_stream: output.get_vsync_stream(),
246 output,
247 dupl: Some(dupl),
248 options: Default::default(),
249 state: Default::default(),
250 })
251 }
252
253 pub async fn acquire_next_vsync_frame(&mut self) -> Result<Texture> {
271 if (self.vsync_stream.next().await).is_some_and(|r| r.is_err()) {
273 return Err(DDApiError::Unexpected("DisplayVSyncStream failed unexpectedly".to_owned()));
274 }
275 let res = self.acquire_next_frame_now();
277 if res.is_err() {
278 trace!("something went wrong with acquiring next frame. probably desktop duplication \
279 instance failed");
280 }
281 res
282 }
283
284 fn create_device(adapter: &Adapter) -> Result<(ID3D11Device4, ID3D11DeviceContext4)> {
285 let feature_levels = [D3D_FEATURE_LEVEL_11_1];
286 let mut feature_level: D3D_FEATURE_LEVEL = Default::default();
287 let mut d3d_device = None;
288 let mut d3d_ctx = None;
289
290 let resp = unsafe {
291 D3D11CreateDevice(adapter.as_raw_ref(), D3D_DRIVER_TYPE_UNKNOWN,
292 None, D3D11_CREATE_DEVICE_FLAG(0),
293 Some(&feature_levels), D3D11_SDK_VERSION,
294 Some(&mut d3d_device), Some(&mut feature_level),
295 Some(&mut d3d_ctx))
296 };
297 if resp.is_err() {
298 Err(DDApiError::Unexpected(format!("faild d3d11 create device. {:?}", resp)))
299 } else {
300 Ok((d3d_device.unwrap().cast().unwrap(), d3d_ctx.unwrap().cast().unwrap()))
301 }
302 }
303
304 fn create_dupl_output(dev: &ID3D11Device4, output: &Display) -> Result<IDXGIOutputDuplication> {
305 let supported_formats = [DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_R16G16B16A16_FLOAT];
306 let device: IDXGIDevice4 = dev.cast().unwrap();
307 let dupl: WinResult<IDXGIOutputDuplication> = unsafe { output.as_raw_ref().DuplicateOutput1(&device, 0, &supported_formats) };
308
309 if let Err(err) = dupl {
310 return match err.code() {
311 E_INVALIDARG => {
312 Err(DDApiError::BadParam(format!("failed to create duplicate output. {:?}", err)))
313 }
314 E_ACCESSDENIED => {
315 Err(DDApiError::AccessDenied)
316 }
317 DXGI_ERROR_UNSUPPORTED => {
318 Err(DDApiError::Unsupported)
319 }
320 DXGI_ERROR_SESSION_DISCONNECTED => {
321 Err(DDApiError::Disconnected)
322 }
323 _ => {
324 Err(DDApiError::Unexpected(err.to_string()))
325 }
326 };
327 }
328 Ok(dupl.unwrap())
329 }
330
331 pub fn acquire_next_frame_now(&mut self) -> Result<Texture> {
349 let mut frame_info = Default::default();
350
351 if self.dupl.is_none() {
352 self.reacquire_dup()?;
353 }
354 let dupl = self.dupl.as_ref().unwrap();
355 let status = unsafe { dupl.AcquireNextFrame(0, &mut frame_info, &mut self.state.last_resource) };
356 if let Err(e) = status {
357 match e.code() {
358 DXGI_ERROR_ACCESS_LOST => {
359 warn!("display access lost. maybe desktop mode switch?, {:?}",e);
360 self.reacquire_dup()?;
361 return Err(DDApiError::AccessLost);
362 }
363 DXGI_ERROR_ACCESS_DENIED => {
364 warn!("display access is denied. Maybe running in a secure environment?");
365 self.reacquire_dup()?;
366 return Err(DDApiError::AccessDenied);
367 }
368 DXGI_ERROR_INVALID_CALL => {
369 warn!("dxgi_error_invalid_call. maybe forgot to ReleaseFrame()?");
370 self.reacquire_dup()?;
371 return Err(DDApiError::AccessLost);
372 }
373 DXGI_ERROR_WAIT_TIMEOUT => {
374 trace!("no new frame is available");
375 }
376 _ => {
377 return Err(DDApiError::Unexpected(format!("acquire frame failed {:?}", e)));
378 }
379 }
380 }
381
382
383 if let Some(resource) = self.state.last_resource.as_ref() {
384 debug!("got fresh resource. accumulated {} frames",frame_info.AccumulatedFrames);
385 self.state.frame_locked = true;
386 let new_frame = Texture::new(resource.cast().unwrap());
387 self.ensure_cache_frame(&new_frame).inspect_err(|_| {
388 self.release_locked_frame();
389 })?;
390 unsafe { self.d3d_ctx.CopyResource(self.state.frame.as_ref().unwrap().as_raw_ref(), new_frame.as_raw_ref()); }
391 self.release_locked_frame();
392 } else {
393 debug!("no fresh resource. accumulated {} frames",frame_info.AccumulatedFrames);
394 }
395 if self.state.frame.is_none() {
396 return Err(DDApiError::AccessLost);
397 }
398
399 let cache_frame = self.state.frame.clone().unwrap();
400
401 if !self.options.skip_cursor {
402 self.ensure_cache_cursor_frame(&cache_frame)?;
403 let cache_cursor_frame = self.state.cursor_frame.clone().unwrap();
404
405 unsafe {
406 self.d3d_ctx.CopyResource(
407 cache_cursor_frame.as_raw_ref(),
408 cache_frame.as_raw_ref())
409 }
410
411 self.draw_cursor(&cache_cursor_frame)?;
412 Ok(cache_cursor_frame)
413 } else {
414 Ok(cache_frame)
415 }
416 }
417
418
419 pub fn get_device_and_ctx(&self) -> (ID3D11Device4, ID3D11DeviceContext4) {
422 return (self.d3d_device.clone(), self.d3d_ctx.clone());
423 }
424
425 pub fn configure(&mut self, opt: DuplicationApiOptions) {
427 self.options = opt;
428 }
429
430 fn draw_cursor(&mut self, tex: &Texture) -> Result<()> {
431 trace!("drawing cursor");
432 let mut cursor_info = CURSORINFO {
433 cbSize: size_of::<CURSORINFO>() as u32,
434 ..Default::default()
435 };
436 let cursor_present = unsafe { GetCursorInfo(&mut cursor_info as *mut CURSORINFO) };
437
438 if cursor_present.is_err()
440 || (cursor_info.flags.0 & CURSOR_SHOWING.0 != CURSOR_SHOWING.0)
441 {
442 debug!("cursor is absent so not drawing anything");
443 return Ok(());
444 }
445
446 if self.state.cursor.is_none() || cursor_info.hCursor != *self.state.cursor.as_ref().unwrap() {
447 self.state.cursor = Some(cursor_info.hCursor);
448 let point = Self::get_icon_hotspot(cursor_info.hCursor)?;
449 self.state.hotspot_x = point.x as _;
450 self.state.hotspot_y = point.y as _;
451 }
452
453 let surface: IDXGISurface1 = tex.as_raw_ref().cast().unwrap();
454 let hdc = unsafe { surface.GetDC(BOOL::from(false)) };
455 if let Err(err) = hdc {
456 return Err(DDApiError::Unexpected(format!("failed to get DC for cursor image. {:?}", err)));
457 }
458 let hdc = hdc.unwrap();
459
460 let result = unsafe {
461 DrawIconEx(
462 hdc,
463 cursor_info.ptScreenPos.x - self.state.hotspot_x,
464 cursor_info.ptScreenPos.y - self.state.hotspot_y,
465 self.state.cursor.unwrap(),
466 0, 0, 0, None, DI_NORMAL,
467 )
468 };
469
470 if result.is_err() {
471 unsafe { return Err(DDApiError::Unexpected(format!("failed to draw icon. {:?}", GetLastError()))); }
472 }
473
474 let _ = unsafe { surface.ReleaseDC(None) };
475 Ok(())
476 }
477
478 fn get_icon_hotspot(cursor: HCURSOR) -> Result<POINT> {
479 let mut icon_info = Default::default();
481 let result = unsafe { GetIconInfo(cursor, &mut icon_info) };
482 if result.is_err() {
483 unsafe { return Err(DDApiError::Unexpected(format!("failed to get icon info. `{:?}`", GetLastError()))); }
484 }
485
486 if !icon_info.hbmMask.is_invalid() {
487 unsafe { DeleteObject(icon_info.hbmMask); }
488 }
489 if !icon_info.hbmColor.is_invalid() {
490 unsafe { DeleteObject(icon_info.hbmColor); }
491 }
492
493 Ok(POINT { x: icon_info.xHotspot as _, y: icon_info.yHotspot as _ })
494 }
495
496 fn reacquire_dup(&mut self) -> Result<()> {
497 self.state.reset();
498 self.dupl = None;
499
500 let dupl = Self::create_dupl_output(&self.d3d_device, &self.output);
501 if dupl.is_err() {
502 let _ = Self::switch_thread_desktop();
503 }
504 let dupl = dupl?;
505 debug!("successfully acquired new duplication instance");
506 self.dupl = Some(dupl);
507 Ok(())
508 }
509
510 fn release_locked_frame(&mut self) {
511 if self.state.last_resource.is_some() {
512 self.state.last_resource = None;
513 }
514 if self.dupl.is_some() {
515 if self.state.frame_locked {
516 let _ = unsafe { self.dupl.as_ref().unwrap().ReleaseFrame() };
517 self.state.frame_locked = false;
518 }
519 }
520 }
521
522 fn ensure_cache_frame(&mut self, frame: &Texture) -> Result<()> {
523 if self.state.frame.is_none() {
524 let tex = self.create_texture(frame.desc(), D3D11_USAGE_DEFAULT,
525 D3D11_BIND_RENDER_TARGET,
526 Default::default())?;
527 self.state.frame = Some(tex);
528 }
529 Ok(())
530 }
531
532 fn ensure_cache_cursor_frame(&mut self, frame: &Texture) -> Result<()> {
533 if self.state.cursor_frame.is_none() {
534 let tex = self.create_texture(frame.desc(), D3D11_USAGE_DEFAULT,
535 D3D11_BIND_RENDER_TARGET,
536 D3D11_RESOURCE_MISC_GDI_COMPATIBLE)?;
537 self.state.cursor_frame = Some(tex);
538 }
539 Ok(())
540 }
541
542 fn create_texture(&self, tex_desc: TextureDesc, usage: D3D11_USAGE, bind_flags: D3D11_BIND_FLAG,
543 misc_flag: D3D11_RESOURCE_MISC_FLAG) -> Result<Texture> {
544 let desc = D3D11_TEXTURE2D_DESC {
545 Width: tex_desc.width,
546 Height: tex_desc.height,
547 MipLevels: 1,
548 ArraySize: 1,
549 Format: tex_desc.format.into(),
550 SampleDesc: DXGI_SAMPLE_DESC {
551 Count: 1,
552 Quality: 0,
553 },
554 Usage: usage,
555 BindFlags: bind_flags.0 as u32,
556 CPUAccessFlags: Default::default(),
557 MiscFlags: misc_flag.0 as u32,
558 };
559 let mut tex = None;
560 let result = unsafe { self.d3d_device.CreateTexture2D(&desc, None, Some(&mut tex)) };
561 if let Err(e) = result {
562 Err(DDApiError::Unexpected(format!("failed to create texture. {:?}", e)))
563 } else {
564 Ok(Texture::new(tex.unwrap()))
565 }
566 }
567
568 fn switch_thread_desktop() -> Result<()> {
569 debug!("trying to switch Thread desktop");
570 let desk = unsafe { OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK as _, true, DESKTOP_ACCESS_FLAGS(GENERIC_READ.0)) };
571 if let Err(err) = desk {
572 error!("dint get desktop : {:?}", err);
573 return Err(DDApiError::AccessDenied);
574 }
575 let result = unsafe { SetThreadDesktop(desk.unwrap()) };
576 if result.is_err() {
577 error!("dint switch desktop: {:?}",unsafe{GetLastError().to_hresult()});
578 return Err(DDApiError::AccessDenied);
579 }
580 Ok(())
581 }
582}
583
584
585#[derive(Default)]
589pub struct DuplicationApiOptions {
590 pub skip_cursor: bool,
591}
592
593#[derive(Default)]
595struct DuplicationState {
596 frame_locked: bool,
597 last_resource: Option<IDXGIResource>,
598
599 frame: Option<Texture>,
600 cursor_frame: Option<Texture>,
601
602 cursor: Option<HCURSOR>,
603 hotspot_x: i32,
604 hotspot_y: i32,
605}
606
607impl DuplicationState {
608 pub fn reset(&mut self) {
609 self.frame = None;
610 self.last_resource = None;
611 self.cursor_frame = None;
612 self.frame_locked = false;
613 }
614}