1use alloc::vec::Vec;
13
14use azul_core::callbacks::Update;
15use azul_core::dom::{ComponentEventFilter, DatasetMergeCallbackType, Dom, EventFilter};
16use azul_core::refany::{OptionRefAny, RefAny};
17use azul_core::resources::{ImageRef, RawImageFormat};
18use azul_core::screencap::ScreenCaptureConfig;
19use azul_core::task::{ThreadId, ThreadReceiver};
20
21use azul_core::video::VideoFrame;
22
23use super::capture_common::{
24 invoke_on_frame, present_frame, screen_backend, OnVideoFrame, OnVideoFrameCallback,
25 OptionOnVideoFrame,
26};
27use crate::callbacks::{Callback, CallbackInfo, CallbackType};
28use crate::thread::{
29 Thread, ThreadCallback, ThreadReceiveMsg, ThreadSender, ThreadWriteBackMsg, WriteBackCallback,
30};
31
32const DEFAULT_W: u32 = 1280;
35const DEFAULT_H: u32 = 720;
36
37#[derive(Debug)]
40pub struct ScreenCaptureWidgetState {
41 pub config: ScreenCaptureConfig,
43 pub started: bool,
45 pub gl_texture_id: Option<u32>,
47 pub on_frame: OptionOnVideoFrame,
50}
51
52#[repr(C)]
55#[derive(Debug)]
56pub struct ScreenCaptureWidget {
57 pub config: ScreenCaptureConfig,
59 pub on_frame: OptionOnVideoFrame,
61}
62
63impl ScreenCaptureWidget {
64 #[must_use] pub const fn create(config: ScreenCaptureConfig) -> Self {
66 Self {
67 config,
68 on_frame: OptionOnVideoFrame::None,
69 }
70 }
71
72 pub fn set_on_frame<C: Into<OnVideoFrameCallback>>(&mut self, data: RefAny, on_frame: C) {
76 self.on_frame = Some(OnVideoFrame {
77 refany: data,
78 callback: on_frame.into(),
79 })
80 .into();
81 }
82
83 #[must_use]
85 pub fn with_on_frame<C: Into<OnVideoFrameCallback>>(
86 mut self,
87 data: RefAny,
88 on_frame: C,
89 ) -> Self {
90 self.set_on_frame(data, on_frame);
91 self
92 }
93
94 #[must_use] pub fn dom(self) -> Dom {
97 let state = ScreenCaptureWidgetState {
98 config: self.config,
99 started: false,
100 gl_texture_id: None,
101 on_frame: self.on_frame,
102 };
103 let dataset = RefAny::new(state);
104
105 let placeholder = ImageRef::null_image(
106 DEFAULT_W as usize,
107 DEFAULT_H as usize,
108 RawImageFormat::BGRA8,
109 b"azul-screencap-placeholder".to_vec(),
110 );
111
112 Dom::create_image(placeholder)
113 .with_dataset(OptionRefAny::Some(dataset.clone()))
114 .with_merge_callback(azul_core::dom::DatasetMergeCallback::from_ptr(merge_screencap_state))
115 .with_callback(
116 EventFilter::Component(ComponentEventFilter::AfterMount),
117 dataset,
118 Callback::from_ptr(screencap_on_after_mount),
119 )
120 }
121}
122
123extern "C" fn screencap_on_after_mount(mut data: RefAny, mut info: CallbackInfo) -> Update {
125 {
126 let Some(mut s) = data.downcast_mut::<ScreenCaptureWidgetState>() else {
127 return Update::DoNothing;
128 };
129 if s.started {
130 return Update::DoNothing;
131 }
132 s.started = true;
133 }
134 info.add_thread(
135 ThreadId::unique(),
136 Thread::create(
137 RefAny::new(()),
138 data.clone(),
139 ThreadCallback::new(screencap_worker),
140 ),
141 );
142 Update::DoNothing
143}
144
145extern "C" fn screencap_worker(_init: RefAny, mut sender: ThreadSender, _recv: ThreadReceiver) {
148 if let Some(backend) = screen_backend() {
151 let handle = (backend.open)(0, DEFAULT_W, DEFAULT_H);
152 if handle != 0 {
153 let mut buf: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
154 loop {
155 let (fw, fh) = (backend.read)(handle, &mut buf);
156 if fw == 0 || fh == 0 {
157 break;
158 }
159 let frame = VideoFrame {
160 width: fw,
161 height: fh,
162 bytes: buf.clone().into(),
163 };
164 if !sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
165 WriteBackCallback::new(screencap_writeback),
166 RefAny::new(frame),
167 ))) {
168 break;
169 }
170 }
171 (backend.close)(handle);
172 return;
173 }
174 }
175
176 let (w, h) = (DEFAULT_W as usize, DEFAULT_H as usize);
177 let mut tick: u32 = 0;
178 loop {
179 let band = (tick as usize) % h;
180 let mut bytes = Vec::with_capacity(w * h * 4);
181 for y in 0..h {
182 let v = if y.abs_diff(band) < 8 { 235u8 } else { 28u8 };
183 for _ in 0..w {
184 bytes.extend_from_slice(&[v, v, v, 255]);
185 }
186 }
187 let frame = VideoFrame {
188 width: u32::try_from(w).unwrap_or(0),
189 height: u32::try_from(h).unwrap_or(0),
190 bytes: bytes.into(),
191 };
192 let sent = sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
193 WriteBackCallback::new(screencap_writeback),
194 RefAny::new(frame),
195 )));
196 if !sent {
197 break;
198 }
199 std::thread::sleep(std::time::Duration::from_millis(33));
200 tick = tick.wrapping_add(12);
201 }
202}
203
204extern "C" fn screencap_writeback(
207 mut writeback_data: RefAny,
208 mut frame_data: RefAny,
209 mut info: CallbackInfo,
210) -> Update {
211 let (current, hook) = writeback_data.downcast_ref::<ScreenCaptureWidgetState>().map_or_else(|| (None, OptionOnVideoFrame::None), |s| (s.gl_texture_id, s.on_frame.clone()));
212 let mut user_update = Update::DoNothing;
213 let new_id = match frame_data.downcast_ref::<VideoFrame>() {
214 Some(frame) => {
215 let id = present_frame(&mut info, writeback_data.clone(), current, &frame);
216 user_update = invoke_on_frame(&hook, &mut info, &frame);
217 id
218 }
219 None => return Update::DoNothing,
220 };
221 if let Some(mut s) = writeback_data.downcast_mut::<ScreenCaptureWidgetState>() {
222 s.gl_texture_id = new_id;
223 }
224 user_update
225}
226
227extern "C" fn merge_screencap_state(mut new_data: RefAny, mut old_data: RefAny) -> RefAny {
229 {
230 let new_guard = new_data.downcast_mut::<ScreenCaptureWidgetState>();
231 let old_guard = old_data.downcast_ref::<ScreenCaptureWidgetState>();
232 if let (Some(mut new_g), Some(old_g)) = (new_guard, old_guard) {
233 new_g.started = old_g.started;
234 new_g.gl_texture_id = old_g.gl_texture_id;
235 }
236 }
237 new_data
238}