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
37pub struct ScreenCaptureWidgetState {
40 pub config: ScreenCaptureConfig,
42 pub started: bool,
44 pub gl_texture_id: Option<u32>,
46 pub on_frame: OptionOnVideoFrame,
49}
50
51#[repr(C)]
54pub struct ScreenCaptureWidget {
55 pub config: ScreenCaptureConfig,
57 pub on_frame: OptionOnVideoFrame,
59}
60
61impl ScreenCaptureWidget {
62 pub fn create(config: ScreenCaptureConfig) -> Self {
64 Self {
65 config,
66 on_frame: OptionOnVideoFrame::None,
67 }
68 }
69
70 pub fn set_on_frame<C: Into<OnVideoFrameCallback>>(&mut self, data: RefAny, on_frame: C) {
74 self.on_frame = Some(OnVideoFrame {
75 refany: data,
76 callback: on_frame.into(),
77 })
78 .into();
79 }
80
81 pub fn with_on_frame<C: Into<OnVideoFrameCallback>>(
83 mut self,
84 data: RefAny,
85 on_frame: C,
86 ) -> Self {
87 self.set_on_frame(data, on_frame);
88 self
89 }
90
91 pub fn dom(self) -> Dom {
94 let state = ScreenCaptureWidgetState {
95 config: self.config,
96 started: false,
97 gl_texture_id: None,
98 on_frame: self.on_frame,
99 };
100 let dataset = RefAny::new(state);
101
102 let placeholder = ImageRef::null_image(
103 DEFAULT_W as usize,
104 DEFAULT_H as usize,
105 RawImageFormat::BGRA8,
106 b"azul-screencap-placeholder".to_vec(),
107 );
108
109 Dom::create_image(placeholder)
110 .with_dataset(OptionRefAny::Some(dataset.clone()))
111 .with_merge_callback(merge_screencap_state as DatasetMergeCallbackType)
112 .with_callback(
113 EventFilter::Component(ComponentEventFilter::AfterMount),
114 dataset,
115 Callback::from(screencap_on_after_mount as CallbackType),
116 )
117 }
118}
119
120extern "C" fn screencap_on_after_mount(mut data: RefAny, mut info: CallbackInfo) -> Update {
122 {
123 let mut s = match data.downcast_mut::<ScreenCaptureWidgetState>() {
124 Some(s) => s,
125 None => return Update::DoNothing,
126 };
127 if s.started {
128 return Update::DoNothing;
129 }
130 s.started = true;
131 }
132 info.add_thread(
133 ThreadId::unique(),
134 Thread::create(
135 RefAny::new(()),
136 data.clone(),
137 ThreadCallback::new(screencap_worker),
138 ),
139 );
140 Update::DoNothing
141}
142
143extern "C" fn screencap_worker(_init: RefAny, mut sender: ThreadSender, _recv: ThreadReceiver) {
146 if let Some(backend) = screen_backend() {
149 let handle = (backend.open)(0, DEFAULT_W as u32, DEFAULT_H as u32);
150 if handle != 0 {
151 let mut buf: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
152 loop {
153 let (fw, fh) = (backend.read)(handle, &mut buf);
154 if fw == 0 || fh == 0 {
155 break;
156 }
157 let frame = VideoFrame {
158 width: fw,
159 height: fh,
160 bytes: buf.clone().into(),
161 };
162 if !sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
163 WriteBackCallback::new(screencap_writeback),
164 RefAny::new(frame),
165 ))) {
166 break;
167 }
168 }
169 (backend.close)(handle);
170 return;
171 }
172 }
173
174 let (w, h) = (DEFAULT_W as usize, DEFAULT_H as usize);
175 let mut tick: u32 = 0;
176 loop {
177 let band = (tick as usize) % h;
178 let mut bytes = Vec::with_capacity(w * h * 4);
179 for y in 0..h {
180 let v = if y.abs_diff(band) < 8 { 235u8 } else { 28u8 };
181 for _ in 0..w {
182 bytes.extend_from_slice(&[v, v, v, 255]);
183 }
184 }
185 let frame = VideoFrame {
186 width: w as u32,
187 height: h as u32,
188 bytes: bytes.into(),
189 };
190 let sent = sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
191 WriteBackCallback::new(screencap_writeback),
192 RefAny::new(frame),
193 )));
194 if !sent {
195 break;
196 }
197 std::thread::sleep(std::time::Duration::from_millis(33));
198 tick = tick.wrapping_add(12);
199 }
200}
201
202extern "C" fn screencap_writeback(
205 mut writeback_data: RefAny,
206 mut frame_data: RefAny,
207 mut info: CallbackInfo,
208) -> Update {
209 let (current, hook) = match writeback_data.downcast_ref::<ScreenCaptureWidgetState>() {
210 Some(s) => (s.gl_texture_id, s.on_frame.clone()),
211 None => (None, OptionOnVideoFrame::None),
212 };
213 let mut user_update = Update::DoNothing;
214 let new_id = match frame_data.downcast_ref::<VideoFrame>() {
215 Some(frame) => {
216 let id = present_frame(&mut info, writeback_data.clone(), current, &frame);
217 user_update = invoke_on_frame(&hook, &mut info, &frame);
218 id
219 }
220 None => return Update::DoNothing,
221 };
222 if let Some(mut s) = writeback_data.downcast_mut::<ScreenCaptureWidgetState>() {
223 s.gl_texture_id = new_id;
224 }
225 user_update
226}
227
228extern "C" fn merge_screencap_state(mut new_data: RefAny, mut old_data: RefAny) -> RefAny {
230 {
231 let new_guard = new_data.downcast_mut::<ScreenCaptureWidgetState>();
232 let old_guard = old_data.downcast_ref::<ScreenCaptureWidgetState>();
233 if let (Some(mut new_g), Some(old_g)) = (new_guard, old_guard) {
234 new_g.started = old_g.started;
235 new_g.gl_texture_id = old_g.gl_texture_id;
236 }
237 }
238 new_data
239}