1pub mod proto;
84
85#[link(wasm_import_module = "oxide")]
88extern "C" {
89 #[link_name = "api_log"]
90 fn _api_log(ptr: u32, len: u32);
91
92 #[link_name = "api_warn"]
93 fn _api_warn(ptr: u32, len: u32);
94
95 #[link_name = "api_error"]
96 fn _api_error(ptr: u32, len: u32);
97
98 #[link_name = "api_get_location"]
99 fn _api_get_location(out_ptr: u32, out_cap: u32) -> u32;
100
101 #[link_name = "api_upload_file"]
102 fn _api_upload_file(name_ptr: u32, name_cap: u32, data_ptr: u32, data_cap: u32) -> u64;
103
104 #[link_name = "api_canvas_clear"]
105 fn _api_canvas_clear(r: u32, g: u32, b: u32, a: u32);
106
107 #[link_name = "api_canvas_rect"]
108 fn _api_canvas_rect(x: f32, y: f32, w: f32, h: f32, r: u32, g: u32, b: u32, a: u32);
109
110 #[link_name = "api_canvas_circle"]
111 fn _api_canvas_circle(cx: f32, cy: f32, radius: f32, r: u32, g: u32, b: u32, a: u32);
112
113 #[link_name = "api_canvas_text"]
114 fn _api_canvas_text(x: f32, y: f32, size: f32, r: u32, g: u32, b: u32, ptr: u32, len: u32);
115
116 #[link_name = "api_canvas_line"]
117 fn _api_canvas_line(x1: f32, y1: f32, x2: f32, y2: f32, r: u32, g: u32, b: u32, thickness: f32);
118
119 #[link_name = "api_canvas_dimensions"]
120 fn _api_canvas_dimensions() -> u64;
121
122 #[link_name = "api_canvas_image"]
123 fn _api_canvas_image(x: f32, y: f32, w: f32, h: f32, data_ptr: u32, data_len: u32);
124
125 #[link_name = "api_storage_set"]
126 fn _api_storage_set(key_ptr: u32, key_len: u32, val_ptr: u32, val_len: u32);
127
128 #[link_name = "api_storage_get"]
129 fn _api_storage_get(key_ptr: u32, key_len: u32, out_ptr: u32, out_cap: u32) -> u32;
130
131 #[link_name = "api_storage_remove"]
132 fn _api_storage_remove(key_ptr: u32, key_len: u32);
133
134 #[link_name = "api_clipboard_write"]
135 fn _api_clipboard_write(ptr: u32, len: u32);
136
137 #[link_name = "api_clipboard_read"]
138 fn _api_clipboard_read(out_ptr: u32, out_cap: u32) -> u32;
139
140 #[link_name = "api_time_now_ms"]
141 fn _api_time_now_ms() -> u64;
142
143 #[link_name = "api_set_timeout"]
144 fn _api_set_timeout(callback_id: u32, delay_ms: u32) -> u32;
145
146 #[link_name = "api_set_interval"]
147 fn _api_set_interval(callback_id: u32, interval_ms: u32) -> u32;
148
149 #[link_name = "api_clear_timer"]
150 fn _api_clear_timer(timer_id: u32);
151
152 #[link_name = "api_random"]
153 fn _api_random() -> u64;
154
155 #[link_name = "api_notify"]
156 fn _api_notify(title_ptr: u32, title_len: u32, body_ptr: u32, body_len: u32);
157
158 #[link_name = "api_fetch"]
159 fn _api_fetch(
160 method_ptr: u32,
161 method_len: u32,
162 url_ptr: u32,
163 url_len: u32,
164 ct_ptr: u32,
165 ct_len: u32,
166 body_ptr: u32,
167 body_len: u32,
168 out_ptr: u32,
169 out_cap: u32,
170 ) -> i64;
171
172 #[link_name = "api_load_module"]
173 fn _api_load_module(url_ptr: u32, url_len: u32) -> i32;
174
175 #[link_name = "api_hash_sha256"]
176 fn _api_hash_sha256(data_ptr: u32, data_len: u32, out_ptr: u32) -> u32;
177
178 #[link_name = "api_base64_encode"]
179 fn _api_base64_encode(data_ptr: u32, data_len: u32, out_ptr: u32, out_cap: u32) -> u32;
180
181 #[link_name = "api_base64_decode"]
182 fn _api_base64_decode(data_ptr: u32, data_len: u32, out_ptr: u32, out_cap: u32) -> u32;
183
184 #[link_name = "api_kv_store_set"]
185 fn _api_kv_store_set(key_ptr: u32, key_len: u32, val_ptr: u32, val_len: u32) -> i32;
186
187 #[link_name = "api_kv_store_get"]
188 fn _api_kv_store_get(key_ptr: u32, key_len: u32, out_ptr: u32, out_cap: u32) -> i32;
189
190 #[link_name = "api_kv_store_delete"]
191 fn _api_kv_store_delete(key_ptr: u32, key_len: u32) -> i32;
192
193 #[link_name = "api_navigate"]
196 fn _api_navigate(url_ptr: u32, url_len: u32) -> i32;
197
198 #[link_name = "api_push_state"]
199 fn _api_push_state(
200 state_ptr: u32,
201 state_len: u32,
202 title_ptr: u32,
203 title_len: u32,
204 url_ptr: u32,
205 url_len: u32,
206 );
207
208 #[link_name = "api_replace_state"]
209 fn _api_replace_state(
210 state_ptr: u32,
211 state_len: u32,
212 title_ptr: u32,
213 title_len: u32,
214 url_ptr: u32,
215 url_len: u32,
216 );
217
218 #[link_name = "api_get_url"]
219 fn _api_get_url(out_ptr: u32, out_cap: u32) -> u32;
220
221 #[link_name = "api_get_state"]
222 fn _api_get_state(out_ptr: u32, out_cap: u32) -> i32;
223
224 #[link_name = "api_history_length"]
225 fn _api_history_length() -> u32;
226
227 #[link_name = "api_history_back"]
228 fn _api_history_back() -> i32;
229
230 #[link_name = "api_history_forward"]
231 fn _api_history_forward() -> i32;
232
233 #[link_name = "api_register_hyperlink"]
236 fn _api_register_hyperlink(x: f32, y: f32, w: f32, h: f32, url_ptr: u32, url_len: u32) -> i32;
237
238 #[link_name = "api_clear_hyperlinks"]
239 fn _api_clear_hyperlinks();
240
241 #[link_name = "api_mouse_position"]
244 fn _api_mouse_position() -> u64;
245
246 #[link_name = "api_mouse_button_down"]
247 fn _api_mouse_button_down(button: u32) -> u32;
248
249 #[link_name = "api_mouse_button_clicked"]
250 fn _api_mouse_button_clicked(button: u32) -> u32;
251
252 #[link_name = "api_key_down"]
253 fn _api_key_down(key: u32) -> u32;
254
255 #[link_name = "api_key_pressed"]
256 fn _api_key_pressed(key: u32) -> u32;
257
258 #[link_name = "api_scroll_delta"]
259 fn _api_scroll_delta() -> u64;
260
261 #[link_name = "api_modifiers"]
262 fn _api_modifiers() -> u32;
263
264 #[link_name = "api_ui_button"]
267 fn _api_ui_button(
268 id: u32,
269 x: f32,
270 y: f32,
271 w: f32,
272 h: f32,
273 label_ptr: u32,
274 label_len: u32,
275 ) -> u32;
276
277 #[link_name = "api_ui_checkbox"]
278 fn _api_ui_checkbox(
279 id: u32,
280 x: f32,
281 y: f32,
282 label_ptr: u32,
283 label_len: u32,
284 initial: u32,
285 ) -> u32;
286
287 #[link_name = "api_ui_slider"]
288 fn _api_ui_slider(id: u32, x: f32, y: f32, w: f32, min: f32, max: f32, initial: f32) -> f32;
289
290 #[link_name = "api_ui_text_input"]
291 fn _api_ui_text_input(
292 id: u32,
293 x: f32,
294 y: f32,
295 w: f32,
296 init_ptr: u32,
297 init_len: u32,
298 out_ptr: u32,
299 out_cap: u32,
300 ) -> u32;
301
302 #[link_name = "api_audio_play"]
305 fn _api_audio_play(data_ptr: u32, data_len: u32) -> i32;
306
307 #[link_name = "api_audio_play_url"]
308 fn _api_audio_play_url(url_ptr: u32, url_len: u32) -> i32;
309
310 #[link_name = "api_audio_detect_format"]
311 fn _api_audio_detect_format(data_ptr: u32, data_len: u32) -> u32;
312
313 #[link_name = "api_audio_play_with_format"]
314 fn _api_audio_play_with_format(data_ptr: u32, data_len: u32, format_hint: u32) -> i32;
315
316 #[link_name = "api_audio_last_url_content_type"]
317 fn _api_audio_last_url_content_type(out_ptr: u32, out_cap: u32) -> u32;
318
319 #[link_name = "api_audio_pause"]
320 fn _api_audio_pause();
321
322 #[link_name = "api_audio_resume"]
323 fn _api_audio_resume();
324
325 #[link_name = "api_audio_stop"]
326 fn _api_audio_stop();
327
328 #[link_name = "api_audio_set_volume"]
329 fn _api_audio_set_volume(level: f32);
330
331 #[link_name = "api_audio_get_volume"]
332 fn _api_audio_get_volume() -> f32;
333
334 #[link_name = "api_audio_is_playing"]
335 fn _api_audio_is_playing() -> u32;
336
337 #[link_name = "api_audio_position"]
338 fn _api_audio_position() -> u64;
339
340 #[link_name = "api_audio_seek"]
341 fn _api_audio_seek(position_ms: u64) -> i32;
342
343 #[link_name = "api_audio_duration"]
344 fn _api_audio_duration() -> u64;
345
346 #[link_name = "api_audio_set_loop"]
347 fn _api_audio_set_loop(enabled: u32);
348
349 #[link_name = "api_audio_channel_play"]
350 fn _api_audio_channel_play(channel: u32, data_ptr: u32, data_len: u32) -> i32;
351
352 #[link_name = "api_audio_channel_play_with_format"]
353 fn _api_audio_channel_play_with_format(
354 channel: u32,
355 data_ptr: u32,
356 data_len: u32,
357 format_hint: u32,
358 ) -> i32;
359
360 #[link_name = "api_audio_channel_stop"]
361 fn _api_audio_channel_stop(channel: u32);
362
363 #[link_name = "api_audio_channel_set_volume"]
364 fn _api_audio_channel_set_volume(channel: u32, level: f32);
365
366 #[link_name = "api_video_detect_format"]
369 fn _api_video_detect_format(data_ptr: u32, data_len: u32) -> u32;
370
371 #[link_name = "api_video_load"]
372 fn _api_video_load(data_ptr: u32, data_len: u32, format_hint: u32) -> i32;
373
374 #[link_name = "api_video_load_url"]
375 fn _api_video_load_url(url_ptr: u32, url_len: u32) -> i32;
376
377 #[link_name = "api_video_last_url_content_type"]
378 fn _api_video_last_url_content_type(out_ptr: u32, out_cap: u32) -> u32;
379
380 #[link_name = "api_video_hls_variant_count"]
381 fn _api_video_hls_variant_count() -> u32;
382
383 #[link_name = "api_video_hls_variant_url"]
384 fn _api_video_hls_variant_url(index: u32, out_ptr: u32, out_cap: u32) -> u32;
385
386 #[link_name = "api_video_hls_open_variant"]
387 fn _api_video_hls_open_variant(index: u32) -> i32;
388
389 #[link_name = "api_video_play"]
390 fn _api_video_play();
391
392 #[link_name = "api_video_pause"]
393 fn _api_video_pause();
394
395 #[link_name = "api_video_stop"]
396 fn _api_video_stop();
397
398 #[link_name = "api_video_seek"]
399 fn _api_video_seek(position_ms: u64) -> i32;
400
401 #[link_name = "api_video_position"]
402 fn _api_video_position() -> u64;
403
404 #[link_name = "api_video_duration"]
405 fn _api_video_duration() -> u64;
406
407 #[link_name = "api_video_render"]
408 fn _api_video_render(x: f32, y: f32, w: f32, h: f32) -> i32;
409
410 #[link_name = "api_video_set_volume"]
411 fn _api_video_set_volume(level: f32);
412
413 #[link_name = "api_video_get_volume"]
414 fn _api_video_get_volume() -> f32;
415
416 #[link_name = "api_video_set_loop"]
417 fn _api_video_set_loop(enabled: u32);
418
419 #[link_name = "api_video_set_pip"]
420 fn _api_video_set_pip(enabled: u32);
421
422 #[link_name = "api_subtitle_load_srt"]
423 fn _api_subtitle_load_srt(ptr: u32, len: u32) -> i32;
424
425 #[link_name = "api_subtitle_load_vtt"]
426 fn _api_subtitle_load_vtt(ptr: u32, len: u32) -> i32;
427
428 #[link_name = "api_subtitle_clear"]
429 fn _api_subtitle_clear();
430
431 #[link_name = "api_camera_open"]
434 fn _api_camera_open() -> i32;
435
436 #[link_name = "api_camera_close"]
437 fn _api_camera_close();
438
439 #[link_name = "api_camera_capture_frame"]
440 fn _api_camera_capture_frame(out_ptr: u32, out_cap: u32) -> u32;
441
442 #[link_name = "api_camera_frame_dimensions"]
443 fn _api_camera_frame_dimensions() -> u64;
444
445 #[link_name = "api_microphone_open"]
446 fn _api_microphone_open() -> i32;
447
448 #[link_name = "api_microphone_close"]
449 fn _api_microphone_close();
450
451 #[link_name = "api_microphone_sample_rate"]
452 fn _api_microphone_sample_rate() -> u32;
453
454 #[link_name = "api_microphone_read_samples"]
455 fn _api_microphone_read_samples(out_ptr: u32, max_samples: u32) -> u32;
456
457 #[link_name = "api_screen_capture"]
458 fn _api_screen_capture(out_ptr: u32, out_cap: u32) -> i32;
459
460 #[link_name = "api_screen_capture_dimensions"]
461 fn _api_screen_capture_dimensions() -> u64;
462
463 #[link_name = "api_media_pipeline_stats"]
464 fn _api_media_pipeline_stats() -> u64;
465
466 #[link_name = "api_url_resolve"]
469 fn _api_url_resolve(
470 base_ptr: u32,
471 base_len: u32,
472 rel_ptr: u32,
473 rel_len: u32,
474 out_ptr: u32,
475 out_cap: u32,
476 ) -> i32;
477
478 #[link_name = "api_url_encode"]
479 fn _api_url_encode(input_ptr: u32, input_len: u32, out_ptr: u32, out_cap: u32) -> u32;
480
481 #[link_name = "api_url_decode"]
482 fn _api_url_decode(input_ptr: u32, input_len: u32, out_ptr: u32, out_cap: u32) -> u32;
483}
484
485pub fn log(msg: &str) {
489 unsafe { _api_log(msg.as_ptr() as u32, msg.len() as u32) }
490}
491
492pub fn warn(msg: &str) {
494 unsafe { _api_warn(msg.as_ptr() as u32, msg.len() as u32) }
495}
496
497pub fn error(msg: &str) {
499 unsafe { _api_error(msg.as_ptr() as u32, msg.len() as u32) }
500}
501
502pub fn get_location() -> String {
506 let mut buf = [0u8; 128];
507 let len = unsafe { _api_get_location(buf.as_mut_ptr() as u32, buf.len() as u32) };
508 String::from_utf8_lossy(&buf[..len as usize]).to_string()
509}
510
511pub struct UploadedFile {
515 pub name: String,
516 pub data: Vec<u8>,
517}
518
519pub fn upload_file() -> Option<UploadedFile> {
522 let mut name_buf = [0u8; 256];
523 let mut data_buf = vec![0u8; 1024 * 1024]; let result = unsafe {
526 _api_upload_file(
527 name_buf.as_mut_ptr() as u32,
528 name_buf.len() as u32,
529 data_buf.as_mut_ptr() as u32,
530 data_buf.len() as u32,
531 )
532 };
533
534 if result == 0 {
535 return None;
536 }
537
538 let name_len = (result >> 32) as usize;
539 let data_len = (result & 0xFFFF_FFFF) as usize;
540
541 Some(UploadedFile {
542 name: String::from_utf8_lossy(&name_buf[..name_len]).to_string(),
543 data: data_buf[..data_len].to_vec(),
544 })
545}
546
547pub fn canvas_clear(r: u8, g: u8, b: u8, a: u8) {
551 unsafe { _api_canvas_clear(r as u32, g as u32, b as u32, a as u32) }
552}
553
554pub fn canvas_rect(x: f32, y: f32, w: f32, h: f32, r: u8, g: u8, b: u8, a: u8) {
556 unsafe { _api_canvas_rect(x, y, w, h, r as u32, g as u32, b as u32, a as u32) }
557}
558
559pub fn canvas_circle(cx: f32, cy: f32, radius: f32, r: u8, g: u8, b: u8, a: u8) {
561 unsafe { _api_canvas_circle(cx, cy, radius, r as u32, g as u32, b as u32, a as u32) }
562}
563
564pub fn canvas_text(x: f32, y: f32, size: f32, r: u8, g: u8, b: u8, text: &str) {
566 unsafe {
567 _api_canvas_text(
568 x,
569 y,
570 size,
571 r as u32,
572 g as u32,
573 b as u32,
574 text.as_ptr() as u32,
575 text.len() as u32,
576 )
577 }
578}
579
580pub fn canvas_line(x1: f32, y1: f32, x2: f32, y2: f32, r: u8, g: u8, b: u8, thickness: f32) {
582 unsafe { _api_canvas_line(x1, y1, x2, y2, r as u32, g as u32, b as u32, thickness) }
583}
584
585pub fn canvas_dimensions() -> (u32, u32) {
587 let packed = unsafe { _api_canvas_dimensions() };
588 ((packed >> 32) as u32, (packed & 0xFFFF_FFFF) as u32)
589}
590
591pub fn canvas_image(x: f32, y: f32, w: f32, h: f32, data: &[u8]) {
594 unsafe { _api_canvas_image(x, y, w, h, data.as_ptr() as u32, data.len() as u32) }
595}
596
597pub fn storage_set(key: &str, value: &str) {
601 unsafe {
602 _api_storage_set(
603 key.as_ptr() as u32,
604 key.len() as u32,
605 value.as_ptr() as u32,
606 value.len() as u32,
607 )
608 }
609}
610
611pub fn storage_get(key: &str) -> String {
613 let mut buf = [0u8; 4096];
614 let len = unsafe {
615 _api_storage_get(
616 key.as_ptr() as u32,
617 key.len() as u32,
618 buf.as_mut_ptr() as u32,
619 buf.len() as u32,
620 )
621 };
622 String::from_utf8_lossy(&buf[..len as usize]).to_string()
623}
624
625pub fn storage_remove(key: &str) {
627 unsafe { _api_storage_remove(key.as_ptr() as u32, key.len() as u32) }
628}
629
630pub fn clipboard_write(text: &str) {
634 unsafe { _api_clipboard_write(text.as_ptr() as u32, text.len() as u32) }
635}
636
637pub fn clipboard_read() -> String {
639 let mut buf = [0u8; 4096];
640 let len = unsafe { _api_clipboard_read(buf.as_mut_ptr() as u32, buf.len() as u32) };
641 String::from_utf8_lossy(&buf[..len as usize]).to_string()
642}
643
644pub fn time_now_ms() -> u64 {
648 unsafe { _api_time_now_ms() }
649}
650
651pub fn set_timeout(callback_id: u32, delay_ms: u32) -> u32 {
655 unsafe { _api_set_timeout(callback_id, delay_ms) }
656}
657
658pub fn set_interval(callback_id: u32, interval_ms: u32) -> u32 {
662 unsafe { _api_set_interval(callback_id, interval_ms) }
663}
664
665pub fn clear_timer(timer_id: u32) {
667 unsafe { _api_clear_timer(timer_id) }
668}
669
670pub fn random_u64() -> u64 {
674 unsafe { _api_random() }
675}
676
677pub fn random_f64() -> f64 {
679 (random_u64() >> 11) as f64 / (1u64 << 53) as f64
680}
681
682pub fn notify(title: &str, body: &str) {
686 unsafe {
687 _api_notify(
688 title.as_ptr() as u32,
689 title.len() as u32,
690 body.as_ptr() as u32,
691 body.len() as u32,
692 )
693 }
694}
695
696#[repr(u32)]
700#[derive(Clone, Copy, Debug, PartialEq, Eq)]
701pub enum AudioFormat {
702 Unknown = 0,
704 Wav = 1,
705 Mp3 = 2,
706 Ogg = 3,
707 Flac = 4,
708}
709
710impl From<u32> for AudioFormat {
711 fn from(code: u32) -> Self {
712 match code {
713 1 => AudioFormat::Wav,
714 2 => AudioFormat::Mp3,
715 3 => AudioFormat::Ogg,
716 4 => AudioFormat::Flac,
717 _ => AudioFormat::Unknown,
718 }
719 }
720}
721
722impl From<AudioFormat> for u32 {
723 fn from(f: AudioFormat) -> u32 {
724 f as u32
725 }
726}
727
728pub fn audio_play(data: &[u8]) -> i32 {
731 unsafe { _api_audio_play(data.as_ptr() as u32, data.len() as u32) }
732}
733
734pub fn audio_detect_format(data: &[u8]) -> AudioFormat {
736 let code = unsafe { _api_audio_detect_format(data.as_ptr() as u32, data.len() as u32) };
737 AudioFormat::from(code)
738}
739
740pub fn audio_play_with_format(data: &[u8], format: AudioFormat) -> i32 {
743 unsafe {
744 _api_audio_play_with_format(data.as_ptr() as u32, data.len() as u32, u32::from(format))
745 }
746}
747
748pub fn audio_play_url(url: &str) -> i32 {
753 unsafe { _api_audio_play_url(url.as_ptr() as u32, url.len() as u32) }
754}
755
756pub fn audio_last_url_content_type() -> String {
758 let mut buf = [0u8; 512];
759 let len =
760 unsafe { _api_audio_last_url_content_type(buf.as_mut_ptr() as u32, buf.len() as u32) };
761 let n = (len as usize).min(buf.len());
762 String::from_utf8_lossy(&buf[..n]).to_string()
763}
764
765pub fn audio_pause() {
767 unsafe { _api_audio_pause() }
768}
769
770pub fn audio_resume() {
772 unsafe { _api_audio_resume() }
773}
774
775pub fn audio_stop() {
777 unsafe { _api_audio_stop() }
778}
779
780pub fn audio_set_volume(level: f32) {
782 unsafe { _api_audio_set_volume(level) }
783}
784
785pub fn audio_get_volume() -> f32 {
787 unsafe { _api_audio_get_volume() }
788}
789
790pub fn audio_is_playing() -> bool {
792 unsafe { _api_audio_is_playing() != 0 }
793}
794
795pub fn audio_position() -> u64 {
797 unsafe { _api_audio_position() }
798}
799
800pub fn audio_seek(position_ms: u64) -> i32 {
802 unsafe { _api_audio_seek(position_ms) }
803}
804
805pub fn audio_duration() -> u64 {
808 unsafe { _api_audio_duration() }
809}
810
811pub fn audio_set_loop(enabled: bool) {
814 unsafe { _api_audio_set_loop(if enabled { 1 } else { 0 }) }
815}
816
817pub fn audio_channel_play(channel: u32, data: &[u8]) -> i32 {
823 unsafe { _api_audio_channel_play(channel, data.as_ptr() as u32, data.len() as u32) }
824}
825
826pub fn audio_channel_play_with_format(channel: u32, data: &[u8], format: AudioFormat) -> i32 {
828 unsafe {
829 _api_audio_channel_play_with_format(
830 channel,
831 data.as_ptr() as u32,
832 data.len() as u32,
833 u32::from(format),
834 )
835 }
836}
837
838pub fn audio_channel_stop(channel: u32) {
840 unsafe { _api_audio_channel_stop(channel) }
841}
842
843pub fn audio_channel_set_volume(channel: u32, level: f32) {
845 unsafe { _api_audio_channel_set_volume(channel, level) }
846}
847
848#[repr(u32)]
852#[derive(Clone, Copy, Debug, PartialEq, Eq)]
853pub enum VideoFormat {
854 Unknown = 0,
855 Mp4 = 1,
856 Webm = 2,
857 Av1 = 3,
858}
859
860impl From<u32> for VideoFormat {
861 fn from(code: u32) -> Self {
862 match code {
863 1 => VideoFormat::Mp4,
864 2 => VideoFormat::Webm,
865 3 => VideoFormat::Av1,
866 _ => VideoFormat::Unknown,
867 }
868 }
869}
870
871impl From<VideoFormat> for u32 {
872 fn from(f: VideoFormat) -> u32 {
873 f as u32
874 }
875}
876
877pub fn video_detect_format(data: &[u8]) -> VideoFormat {
879 let code = unsafe { _api_video_detect_format(data.as_ptr() as u32, data.len() as u32) };
880 VideoFormat::from(code)
881}
882
883pub fn video_load(data: &[u8]) -> i32 {
886 unsafe {
887 _api_video_load(
888 data.as_ptr() as u32,
889 data.len() as u32,
890 VideoFormat::Unknown as u32,
891 )
892 }
893}
894
895pub fn video_load_with_format(data: &[u8], format: VideoFormat) -> i32 {
897 unsafe { _api_video_load(data.as_ptr() as u32, data.len() as u32, u32::from(format)) }
898}
899
900pub fn video_load_url(url: &str) -> i32 {
902 unsafe { _api_video_load_url(url.as_ptr() as u32, url.len() as u32) }
903}
904
905pub fn video_last_url_content_type() -> String {
907 let mut buf = [0u8; 512];
908 let len =
909 unsafe { _api_video_last_url_content_type(buf.as_mut_ptr() as u32, buf.len() as u32) };
910 let n = (len as usize).min(buf.len());
911 String::from_utf8_lossy(&buf[..n]).to_string()
912}
913
914pub fn video_hls_variant_count() -> u32 {
916 unsafe { _api_video_hls_variant_count() }
917}
918
919pub fn video_hls_variant_url(index: u32) -> String {
921 let mut buf = [0u8; 2048];
922 let len =
923 unsafe { _api_video_hls_variant_url(index, buf.as_mut_ptr() as u32, buf.len() as u32) };
924 let n = (len as usize).min(buf.len());
925 String::from_utf8_lossy(&buf[..n]).to_string()
926}
927
928pub fn video_hls_open_variant(index: u32) -> i32 {
930 unsafe { _api_video_hls_open_variant(index) }
931}
932
933pub fn video_play() {
934 unsafe { _api_video_play() }
935}
936
937pub fn video_pause() {
938 unsafe { _api_video_pause() }
939}
940
941pub fn video_stop() {
942 unsafe { _api_video_stop() }
943}
944
945pub fn video_seek(position_ms: u64) -> i32 {
946 unsafe { _api_video_seek(position_ms) }
947}
948
949pub fn video_position() -> u64 {
950 unsafe { _api_video_position() }
951}
952
953pub fn video_duration() -> u64 {
954 unsafe { _api_video_duration() }
955}
956
957pub fn video_render(x: f32, y: f32, w: f32, h: f32) -> i32 {
959 unsafe { _api_video_render(x, y, w, h) }
960}
961
962pub fn video_set_volume(level: f32) {
964 unsafe { _api_video_set_volume(level) }
965}
966
967pub fn video_get_volume() -> f32 {
968 unsafe { _api_video_get_volume() }
969}
970
971pub fn video_set_loop(enabled: bool) {
972 unsafe { _api_video_set_loop(if enabled { 1 } else { 0 }) }
973}
974
975pub fn video_set_pip(enabled: bool) {
977 unsafe { _api_video_set_pip(if enabled { 1 } else { 0 }) }
978}
979
980pub fn subtitle_load_srt(text: &str) -> i32 {
982 unsafe { _api_subtitle_load_srt(text.as_ptr() as u32, text.len() as u32) }
983}
984
985pub fn subtitle_load_vtt(text: &str) -> i32 {
987 unsafe { _api_subtitle_load_vtt(text.as_ptr() as u32, text.len() as u32) }
988}
989
990pub fn subtitle_clear() {
991 unsafe { _api_subtitle_clear() }
992}
993
994pub fn camera_open() -> i32 {
1000 unsafe { _api_camera_open() }
1001}
1002
1003pub fn camera_close() {
1005 unsafe { _api_camera_close() }
1006}
1007
1008pub fn camera_capture_frame(out: &mut [u8]) -> u32 {
1011 unsafe { _api_camera_capture_frame(out.as_mut_ptr() as u32, out.len() as u32) }
1012}
1013
1014pub fn camera_frame_dimensions() -> (u32, u32) {
1016 let packed = unsafe { _api_camera_frame_dimensions() };
1017 let w = (packed >> 32) as u32;
1018 let h = packed as u32;
1019 (w, h)
1020}
1021
1022pub fn microphone_open() -> i32 {
1026 unsafe { _api_microphone_open() }
1027}
1028
1029pub fn microphone_close() {
1030 unsafe { _api_microphone_close() }
1031}
1032
1033pub fn microphone_sample_rate() -> u32 {
1035 unsafe { _api_microphone_sample_rate() }
1036}
1037
1038pub fn microphone_read_samples(out: &mut [f32]) -> u32 {
1041 unsafe { _api_microphone_read_samples(out.as_mut_ptr() as u32, out.len() as u32) }
1042}
1043
1044pub fn screen_capture(out: &mut [u8]) -> Result<usize, i32> {
1048 let n = unsafe { _api_screen_capture(out.as_mut_ptr() as u32, out.len() as u32) };
1049 if n >= 0 {
1050 Ok(n as usize)
1051 } else {
1052 Err(n)
1053 }
1054}
1055
1056pub fn screen_capture_dimensions() -> (u32, u32) {
1058 let packed = unsafe { _api_screen_capture_dimensions() };
1059 let w = (packed >> 32) as u32;
1060 let h = packed as u32;
1061 (w, h)
1062}
1063
1064pub fn media_pipeline_stats() -> (u64, u32) {
1067 let packed = unsafe { _api_media_pipeline_stats() };
1068 let camera_frames = packed >> 32;
1069 let mic_ring = packed as u32;
1070 (camera_frames, mic_ring)
1071}
1072
1073pub struct FetchResponse {
1077 pub status: u32,
1078 pub body: Vec<u8>,
1079}
1080
1081impl FetchResponse {
1082 pub fn text(&self) -> String {
1084 String::from_utf8_lossy(&self.body).to_string()
1085 }
1086}
1087
1088pub fn fetch(
1094 method: &str,
1095 url: &str,
1096 content_type: &str,
1097 body: &[u8],
1098) -> Result<FetchResponse, i64> {
1099 let mut out_buf = vec![0u8; 4 * 1024 * 1024]; let result = unsafe {
1101 _api_fetch(
1102 method.as_ptr() as u32,
1103 method.len() as u32,
1104 url.as_ptr() as u32,
1105 url.len() as u32,
1106 content_type.as_ptr() as u32,
1107 content_type.len() as u32,
1108 body.as_ptr() as u32,
1109 body.len() as u32,
1110 out_buf.as_mut_ptr() as u32,
1111 out_buf.len() as u32,
1112 )
1113 };
1114 if result < 0 {
1115 return Err(result);
1116 }
1117 let status = (result >> 32) as u32;
1118 let body_len = (result & 0xFFFF_FFFF) as usize;
1119 Ok(FetchResponse {
1120 status,
1121 body: out_buf[..body_len].to_vec(),
1122 })
1123}
1124
1125pub fn fetch_get(url: &str) -> Result<FetchResponse, i64> {
1127 fetch("GET", url, "", &[])
1128}
1129
1130pub fn fetch_post(url: &str, content_type: &str, body: &[u8]) -> Result<FetchResponse, i64> {
1132 fetch("POST", url, content_type, body)
1133}
1134
1135pub fn fetch_post_proto(url: &str, msg: &proto::ProtoEncoder) -> Result<FetchResponse, i64> {
1137 fetch("POST", url, "application/protobuf", msg.as_bytes())
1138}
1139
1140pub fn fetch_put(url: &str, content_type: &str, body: &[u8]) -> Result<FetchResponse, i64> {
1142 fetch("PUT", url, content_type, body)
1143}
1144
1145pub fn fetch_delete(url: &str) -> Result<FetchResponse, i64> {
1147 fetch("DELETE", url, "", &[])
1148}
1149
1150pub fn load_module(url: &str) -> i32 {
1156 unsafe { _api_load_module(url.as_ptr() as u32, url.len() as u32) }
1157}
1158
1159pub fn hash_sha256(data: &[u8]) -> [u8; 32] {
1163 let mut out = [0u8; 32];
1164 unsafe {
1165 _api_hash_sha256(
1166 data.as_ptr() as u32,
1167 data.len() as u32,
1168 out.as_mut_ptr() as u32,
1169 );
1170 }
1171 out
1172}
1173
1174pub fn hash_sha256_hex(data: &[u8]) -> String {
1176 let hash = hash_sha256(data);
1177 let mut hex = String::with_capacity(64);
1178 for byte in &hash {
1179 hex.push(HEX_CHARS[(*byte >> 4) as usize]);
1180 hex.push(HEX_CHARS[(*byte & 0x0F) as usize]);
1181 }
1182 hex
1183}
1184
1185const HEX_CHARS: [char; 16] = [
1186 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
1187];
1188
1189pub fn base64_encode(data: &[u8]) -> String {
1193 let mut buf = vec![0u8; data.len() * 4 / 3 + 8];
1194 let len = unsafe {
1195 _api_base64_encode(
1196 data.as_ptr() as u32,
1197 data.len() as u32,
1198 buf.as_mut_ptr() as u32,
1199 buf.len() as u32,
1200 )
1201 };
1202 String::from_utf8_lossy(&buf[..len as usize]).to_string()
1203}
1204
1205pub fn base64_decode(encoded: &str) -> Vec<u8> {
1207 let mut buf = vec![0u8; encoded.len()];
1208 let len = unsafe {
1209 _api_base64_decode(
1210 encoded.as_ptr() as u32,
1211 encoded.len() as u32,
1212 buf.as_mut_ptr() as u32,
1213 buf.len() as u32,
1214 )
1215 };
1216 buf[..len as usize].to_vec()
1217}
1218
1219pub fn kv_store_set(key: &str, value: &[u8]) -> bool {
1224 let rc = unsafe {
1225 _api_kv_store_set(
1226 key.as_ptr() as u32,
1227 key.len() as u32,
1228 value.as_ptr() as u32,
1229 value.len() as u32,
1230 )
1231 };
1232 rc == 0
1233}
1234
1235pub fn kv_store_set_str(key: &str, value: &str) -> bool {
1237 kv_store_set(key, value.as_bytes())
1238}
1239
1240pub fn kv_store_get(key: &str) -> Option<Vec<u8>> {
1243 let mut buf = vec![0u8; 64 * 1024]; let rc = unsafe {
1245 _api_kv_store_get(
1246 key.as_ptr() as u32,
1247 key.len() as u32,
1248 buf.as_mut_ptr() as u32,
1249 buf.len() as u32,
1250 )
1251 };
1252 if rc < 0 {
1253 return None;
1254 }
1255 Some(buf[..rc as usize].to_vec())
1256}
1257
1258pub fn kv_store_get_str(key: &str) -> Option<String> {
1260 kv_store_get(key).map(|v| String::from_utf8_lossy(&v).into_owned())
1261}
1262
1263pub fn kv_store_delete(key: &str) -> bool {
1265 let rc = unsafe { _api_kv_store_delete(key.as_ptr() as u32, key.len() as u32) };
1266 rc == 0
1267}
1268
1269pub fn navigate(url: &str) -> i32 {
1275 unsafe { _api_navigate(url.as_ptr() as u32, url.len() as u32) }
1276}
1277
1278pub fn push_state(state: &[u8], title: &str, url: &str) {
1286 unsafe {
1287 _api_push_state(
1288 state.as_ptr() as u32,
1289 state.len() as u32,
1290 title.as_ptr() as u32,
1291 title.len() as u32,
1292 url.as_ptr() as u32,
1293 url.len() as u32,
1294 )
1295 }
1296}
1297
1298pub fn replace_state(state: &[u8], title: &str, url: &str) {
1301 unsafe {
1302 _api_replace_state(
1303 state.as_ptr() as u32,
1304 state.len() as u32,
1305 title.as_ptr() as u32,
1306 title.len() as u32,
1307 url.as_ptr() as u32,
1308 url.len() as u32,
1309 )
1310 }
1311}
1312
1313pub fn get_url() -> String {
1315 let mut buf = [0u8; 4096];
1316 let len = unsafe { _api_get_url(buf.as_mut_ptr() as u32, buf.len() as u32) };
1317 String::from_utf8_lossy(&buf[..len as usize]).to_string()
1318}
1319
1320pub fn get_state() -> Option<Vec<u8>> {
1323 let mut buf = vec![0u8; 64 * 1024]; let rc = unsafe { _api_get_state(buf.as_mut_ptr() as u32, buf.len() as u32) };
1325 if rc < 0 {
1326 return None;
1327 }
1328 Some(buf[..rc as usize].to_vec())
1329}
1330
1331pub fn history_length() -> u32 {
1333 unsafe { _api_history_length() }
1334}
1335
1336pub fn history_back() -> bool {
1338 unsafe { _api_history_back() == 1 }
1339}
1340
1341pub fn history_forward() -> bool {
1343 unsafe { _api_history_forward() == 1 }
1344}
1345
1346pub fn register_hyperlink(x: f32, y: f32, w: f32, h: f32, url: &str) -> i32 {
1354 unsafe { _api_register_hyperlink(x, y, w, h, url.as_ptr() as u32, url.len() as u32) }
1355}
1356
1357pub fn clear_hyperlinks() {
1359 unsafe { _api_clear_hyperlinks() }
1360}
1361
1362pub fn url_resolve(base: &str, relative: &str) -> Option<String> {
1367 let mut buf = [0u8; 4096];
1368 let rc = unsafe {
1369 _api_url_resolve(
1370 base.as_ptr() as u32,
1371 base.len() as u32,
1372 relative.as_ptr() as u32,
1373 relative.len() as u32,
1374 buf.as_mut_ptr() as u32,
1375 buf.len() as u32,
1376 )
1377 };
1378 if rc < 0 {
1379 return None;
1380 }
1381 Some(String::from_utf8_lossy(&buf[..rc as usize]).to_string())
1382}
1383
1384pub fn url_encode(input: &str) -> String {
1386 let mut buf = vec![0u8; input.len() * 3 + 4];
1387 let len = unsafe {
1388 _api_url_encode(
1389 input.as_ptr() as u32,
1390 input.len() as u32,
1391 buf.as_mut_ptr() as u32,
1392 buf.len() as u32,
1393 )
1394 };
1395 String::from_utf8_lossy(&buf[..len as usize]).to_string()
1396}
1397
1398pub fn url_decode(input: &str) -> String {
1400 let mut buf = vec![0u8; input.len() + 4];
1401 let len = unsafe {
1402 _api_url_decode(
1403 input.as_ptr() as u32,
1404 input.len() as u32,
1405 buf.as_mut_ptr() as u32,
1406 buf.len() as u32,
1407 )
1408 };
1409 String::from_utf8_lossy(&buf[..len as usize]).to_string()
1410}
1411
1412pub fn mouse_position() -> (f32, f32) {
1416 let packed = unsafe { _api_mouse_position() };
1417 let x = f32::from_bits((packed >> 32) as u32);
1418 let y = f32::from_bits((packed & 0xFFFF_FFFF) as u32);
1419 (x, y)
1420}
1421
1422pub fn mouse_button_down(button: u32) -> bool {
1425 unsafe { _api_mouse_button_down(button) != 0 }
1426}
1427
1428pub fn mouse_button_clicked(button: u32) -> bool {
1430 unsafe { _api_mouse_button_clicked(button) != 0 }
1431}
1432
1433pub fn key_down(key: u32) -> bool {
1436 unsafe { _api_key_down(key) != 0 }
1437}
1438
1439pub fn key_pressed(key: u32) -> bool {
1441 unsafe { _api_key_pressed(key) != 0 }
1442}
1443
1444pub fn scroll_delta() -> (f32, f32) {
1446 let packed = unsafe { _api_scroll_delta() };
1447 let x = f32::from_bits((packed >> 32) as u32);
1448 let y = f32::from_bits((packed & 0xFFFF_FFFF) as u32);
1449 (x, y)
1450}
1451
1452pub fn modifiers() -> u32 {
1454 unsafe { _api_modifiers() }
1455}
1456
1457pub fn shift_held() -> bool {
1459 modifiers() & 1 != 0
1460}
1461
1462pub fn ctrl_held() -> bool {
1464 modifiers() & 2 != 0
1465}
1466
1467pub fn alt_held() -> bool {
1469 modifiers() & 4 != 0
1470}
1471
1472pub const KEY_A: u32 = 0;
1475pub const KEY_B: u32 = 1;
1476pub const KEY_C: u32 = 2;
1477pub const KEY_D: u32 = 3;
1478pub const KEY_E: u32 = 4;
1479pub const KEY_F: u32 = 5;
1480pub const KEY_G: u32 = 6;
1481pub const KEY_H: u32 = 7;
1482pub const KEY_I: u32 = 8;
1483pub const KEY_J: u32 = 9;
1484pub const KEY_K: u32 = 10;
1485pub const KEY_L: u32 = 11;
1486pub const KEY_M: u32 = 12;
1487pub const KEY_N: u32 = 13;
1488pub const KEY_O: u32 = 14;
1489pub const KEY_P: u32 = 15;
1490pub const KEY_Q: u32 = 16;
1491pub const KEY_R: u32 = 17;
1492pub const KEY_S: u32 = 18;
1493pub const KEY_T: u32 = 19;
1494pub const KEY_U: u32 = 20;
1495pub const KEY_V: u32 = 21;
1496pub const KEY_W: u32 = 22;
1497pub const KEY_X: u32 = 23;
1498pub const KEY_Y: u32 = 24;
1499pub const KEY_Z: u32 = 25;
1500pub const KEY_0: u32 = 26;
1501pub const KEY_1: u32 = 27;
1502pub const KEY_2: u32 = 28;
1503pub const KEY_3: u32 = 29;
1504pub const KEY_4: u32 = 30;
1505pub const KEY_5: u32 = 31;
1506pub const KEY_6: u32 = 32;
1507pub const KEY_7: u32 = 33;
1508pub const KEY_8: u32 = 34;
1509pub const KEY_9: u32 = 35;
1510pub const KEY_ENTER: u32 = 36;
1511pub const KEY_ESCAPE: u32 = 37;
1512pub const KEY_TAB: u32 = 38;
1513pub const KEY_BACKSPACE: u32 = 39;
1514pub const KEY_DELETE: u32 = 40;
1515pub const KEY_SPACE: u32 = 41;
1516pub const KEY_UP: u32 = 42;
1517pub const KEY_DOWN: u32 = 43;
1518pub const KEY_LEFT: u32 = 44;
1519pub const KEY_RIGHT: u32 = 45;
1520pub const KEY_HOME: u32 = 46;
1521pub const KEY_END: u32 = 47;
1522pub const KEY_PAGE_UP: u32 = 48;
1523pub const KEY_PAGE_DOWN: u32 = 49;
1524
1525pub fn ui_button(id: u32, x: f32, y: f32, w: f32, h: f32, label: &str) -> bool {
1533 unsafe { _api_ui_button(id, x, y, w, h, label.as_ptr() as u32, label.len() as u32) != 0 }
1534}
1535
1536pub fn ui_checkbox(id: u32, x: f32, y: f32, label: &str, initial: bool) -> bool {
1540 unsafe {
1541 _api_ui_checkbox(
1542 id,
1543 x,
1544 y,
1545 label.as_ptr() as u32,
1546 label.len() as u32,
1547 if initial { 1 } else { 0 },
1548 ) != 0
1549 }
1550}
1551
1552pub fn ui_slider(id: u32, x: f32, y: f32, w: f32, min: f32, max: f32, initial: f32) -> f32 {
1556 unsafe { _api_ui_slider(id, x, y, w, min, max, initial) }
1557}
1558
1559pub fn ui_text_input(id: u32, x: f32, y: f32, w: f32, initial: &str) -> String {
1563 let mut buf = [0u8; 4096];
1564 let len = unsafe {
1565 _api_ui_text_input(
1566 id,
1567 x,
1568 y,
1569 w,
1570 initial.as_ptr() as u32,
1571 initial.len() as u32,
1572 buf.as_mut_ptr() as u32,
1573 buf.len() as u32,
1574 )
1575 };
1576 String::from_utf8_lossy(&buf[..len as usize]).to_string()
1577}