@desklink/host 0.5.14 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/bin/desklink-host.mjs +9 -3
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +220 -0
- package/dist/doctor.js.map +1 -0
- package/dist/engineProcess.d.ts +5 -0
- package/dist/engineProcess.d.ts.map +1 -1
- package/dist/engineProcess.js +15 -5
- package/dist/engineProcess.js.map +1 -1
- package/docs/PROTOCOL.md +4 -2
- package/engine/Cargo.lock +1 -1
- package/engine/Cargo.toml +1 -1
- package/engine/native/mac_stream.mm +4 -1
- package/engine/src/capture.rs +0 -79
- package/engine/src/diagnose.rs +99 -0
- package/engine/src/encoder.rs +0 -48
- package/engine/src/h264_nvenc.rs +0 -165
- package/engine/src/h264_openh264.rs +0 -245
- package/engine/src/h264_vaapi.rs +0 -97
- package/engine/src/indicator.rs +0 -101
- package/engine/src/input_portal.rs +337 -0
- package/engine/src/mac.rs +229 -6
- package/engine/src/main.rs +7 -88
- package/engine/src/observe.rs +0 -33
- package/engine/src/peer.rs +0 -433
- package/engine/src/portal.rs +0 -75
- package/engine/src/session.rs +112 -1085
- package/examples/reference-client.html +13 -0
- package/package.json +3 -3
package/engine/src/mac.rs
CHANGED
|
@@ -592,6 +592,152 @@ fn capture_probe(seconds: u64) -> Result<Value, String> {
|
|
|
592
592
|
)
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
/// One real ScreenCaptureKit screenshot of a screen-point rectangle, converted
|
|
596
|
+
/// to packed BGRA with its row stride. `capture-probe --frame/--record` uses it
|
|
597
|
+
/// to keep a real desktop view for proof; it is not a render.
|
|
598
|
+
fn capture_rect_bgra(area: CGRect) -> Result<(Vec<u8>, usize, usize, usize), String> {
|
|
599
|
+
use block2::RcBlock;
|
|
600
|
+
use objc2_core_foundation::{
|
|
601
|
+
CGPoint as NativePoint, CGRect as NativeRect, CGSize as NativeSize,
|
|
602
|
+
};
|
|
603
|
+
use objc2_screen_capture_kit::SCScreenshotManager;
|
|
604
|
+
|
|
605
|
+
let rect = NativeRect {
|
|
606
|
+
origin: NativePoint {
|
|
607
|
+
x: area.origin.x,
|
|
608
|
+
y: area.origin.y,
|
|
609
|
+
},
|
|
610
|
+
size: NativeSize {
|
|
611
|
+
width: area.size.width,
|
|
612
|
+
height: area.size.height,
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
let ready = Arc::new((
|
|
616
|
+
Mutex::new(None::<Result<(Vec<u8>, usize, usize, usize), String>>),
|
|
617
|
+
Condvar::new(),
|
|
618
|
+
));
|
|
619
|
+
let callback_state = ready.clone();
|
|
620
|
+
let callback = RcBlock::new(
|
|
621
|
+
move |image: *mut objc2_core_graphics::CGImage, error: *mut objc2_foundation::NSError| {
|
|
622
|
+
let frame = if !error.is_null() || image.is_null() {
|
|
623
|
+
Err(String::from(
|
|
624
|
+
"ScreenCaptureKit returned an error or no image",
|
|
625
|
+
))
|
|
626
|
+
} else {
|
|
627
|
+
let image = unsafe { &*image };
|
|
628
|
+
let width = objc2_core_graphics::CGImage::width(Some(image));
|
|
629
|
+
let height = objc2_core_graphics::CGImage::height(Some(image));
|
|
630
|
+
let mut length = 0usize;
|
|
631
|
+
let mut stride = 0usize;
|
|
632
|
+
let pointer = unsafe {
|
|
633
|
+
desklink_image_bgra(
|
|
634
|
+
image as *const _ as *const c_void,
|
|
635
|
+
&mut length,
|
|
636
|
+
&mut stride,
|
|
637
|
+
)
|
|
638
|
+
};
|
|
639
|
+
if pointer.is_null() || width == 0 || height == 0 {
|
|
640
|
+
Err(String::from(
|
|
641
|
+
"ScreenCaptureKit image could not be converted to BGRA",
|
|
642
|
+
))
|
|
643
|
+
} else {
|
|
644
|
+
let pixels = unsafe { std::slice::from_raw_parts(pointer, length) }.to_vec();
|
|
645
|
+
unsafe { libc::free(pointer.cast()) };
|
|
646
|
+
Ok((pixels, width, height, stride))
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
let (lock, wake) = &*callback_state;
|
|
650
|
+
if let Ok(mut result) = lock.lock() {
|
|
651
|
+
*result = Some(frame);
|
|
652
|
+
wake.notify_one();
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
);
|
|
656
|
+
unsafe {
|
|
657
|
+
SCScreenshotManager::captureImageInRect_completionHandler(rect, Some(&callback));
|
|
658
|
+
}
|
|
659
|
+
let deadline = Instant::now() + Duration::from_secs(5);
|
|
660
|
+
let (lock, wake) = &*ready;
|
|
661
|
+
let mut result = lock
|
|
662
|
+
.lock()
|
|
663
|
+
.map_err(|_| String::from("capture lock poisoned"))?;
|
|
664
|
+
while result.is_none() && Instant::now() < deadline {
|
|
665
|
+
let (next, _) = wake
|
|
666
|
+
.wait_timeout(result, deadline.saturating_duration_since(Instant::now()))
|
|
667
|
+
.map_err(|_| String::from("capture wait failed"))?;
|
|
668
|
+
result = next;
|
|
669
|
+
}
|
|
670
|
+
result
|
|
671
|
+
.take()
|
|
672
|
+
.unwrap_or_else(|| Err(String::from("ScreenCaptureKit timed out")))
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/// Write one real desktop frame, or a short real desktop sequence, to raw BGRA
|
|
676
|
+
/// files. The proof harness turns these into stills and recordings; no pixels
|
|
677
|
+
/// are ever rendered by the engine itself.
|
|
678
|
+
fn capture_rects(
|
|
679
|
+
seconds: u64,
|
|
680
|
+
frame_path: Option<String>,
|
|
681
|
+
record_dir: Option<String>,
|
|
682
|
+
fps: u64,
|
|
683
|
+
area: Option<CGRect>,
|
|
684
|
+
) -> Result<Value, String> {
|
|
685
|
+
let area = area.unwrap_or_else(|| unsafe { CGDisplayBounds(CGMainDisplayID()) });
|
|
686
|
+
let rect = serde_json::json!({
|
|
687
|
+
"x": area.origin.x, "y": area.origin.y,
|
|
688
|
+
"width": area.size.width, "height": area.size.height
|
|
689
|
+
});
|
|
690
|
+
let mut written = Vec::new();
|
|
691
|
+
let mut dimensions = None;
|
|
692
|
+
if let Some(path) = frame_path {
|
|
693
|
+
let (pixels, width, height, stride) = capture_rect_bgra(area)?;
|
|
694
|
+
std::fs::write(&path, &pixels)
|
|
695
|
+
.map_err(|error| format!("could not write {path}: {error}"))?;
|
|
696
|
+
dimensions = Some((width, height));
|
|
697
|
+
written.push(serde_json::json!({
|
|
698
|
+
"kind": "frame", "path": path, "width": width, "height": height,
|
|
699
|
+
"stride": stride, "format": "bgra"
|
|
700
|
+
}));
|
|
701
|
+
}
|
|
702
|
+
if let Some(dir) = record_dir {
|
|
703
|
+
std::fs::create_dir_all(&dir)
|
|
704
|
+
.map_err(|error| format!("could not create {dir}: {error}"))?;
|
|
705
|
+
let fps = fps.clamp(1, 30);
|
|
706
|
+
let count = seconds.saturating_mul(fps).max(1);
|
|
707
|
+
for index in 0..count {
|
|
708
|
+
let (pixels, width, height, stride) = capture_rect_bgra(area)?;
|
|
709
|
+
dimensions = Some((width, height));
|
|
710
|
+
let path = std::path::Path::new(&dir).join(format!("frame-{index:04}.bgra"));
|
|
711
|
+
std::fs::write(&path, &pixels)
|
|
712
|
+
.map_err(|error| format!("could not write {}: {error}", path.display()))?;
|
|
713
|
+
if index == 0 {
|
|
714
|
+
written.push(serde_json::json!({
|
|
715
|
+
"kind": "record", "dir": dir, "width": width, "height": height,
|
|
716
|
+
"stride": stride, "format": "bgra", "fps": fps, "frames": count
|
|
717
|
+
}));
|
|
718
|
+
}
|
|
719
|
+
std::thread::sleep(Duration::from_millis(1000 / fps));
|
|
720
|
+
}
|
|
721
|
+
let manifest = serde_json::json!({
|
|
722
|
+
"format": "bgra", "fps": fps, "frames": count,
|
|
723
|
+
"width": dimensions.map(|d| d.0), "height": dimensions.map(|d| d.1),
|
|
724
|
+
"rect": rect
|
|
725
|
+
});
|
|
726
|
+
std::fs::write(
|
|
727
|
+
std::path::Path::new(&dir).join("manifest.json"),
|
|
728
|
+
serde_json::to_vec_pretty(&manifest).unwrap(),
|
|
729
|
+
)
|
|
730
|
+
.map_err(|error| format!("could not write manifest: {error}"))?;
|
|
731
|
+
}
|
|
732
|
+
Ok(serde_json::json!({
|
|
733
|
+
"source": {
|
|
734
|
+
"kind": "screencapturekit", "rect": rect.clone(),
|
|
735
|
+
"width": dimensions.map(|d| d.0), "height": dimensions.map(|d| d.1)
|
|
736
|
+
},
|
|
737
|
+
"written": written
|
|
738
|
+
}))
|
|
739
|
+
}
|
|
740
|
+
|
|
595
741
|
pub fn run() -> i32 {
|
|
596
742
|
let mut args = std::env::args().skip(1);
|
|
597
743
|
let command = args.next().unwrap_or_else(|| "help".into());
|
|
@@ -600,6 +746,7 @@ pub fn run() -> i32 {
|
|
|
600
746
|
"serve"
|
|
601
747
|
| "capabilities"
|
|
602
748
|
| "capture-probe"
|
|
749
|
+
| "warp-pointer"
|
|
603
750
|
| "indicator-demo"
|
|
604
751
|
| "axi-record"
|
|
605
752
|
| "setup-input"
|
|
@@ -668,6 +815,46 @@ pub fn run() -> i32 {
|
|
|
668
815
|
let Some(path) = args.next() else { eprintln!("axi-record requires an output path"); return 2; };
|
|
669
816
|
if let Err(error) = record_agent_overlay(&path) { eprintln!("axi-record: {error}"); return 1; }
|
|
670
817
|
}
|
|
818
|
+
"warp-pointer" => {
|
|
819
|
+
let x = args.next().and_then(|value| value.parse::<i64>().ok());
|
|
820
|
+
let y = args.next().and_then(|value| value.parse::<i64>().ok());
|
|
821
|
+
let display = args
|
|
822
|
+
.next()
|
|
823
|
+
.and_then(|value| value.parse::<u32>().ok())
|
|
824
|
+
.unwrap_or_else(|| unsafe { CGMainDisplayID() });
|
|
825
|
+
let (Some(x), Some(y)) = (x, y) else {
|
|
826
|
+
eprintln!("usage: desklink-host warp-pointer <source x> <source y> [display]");
|
|
827
|
+
return 2;
|
|
828
|
+
};
|
|
829
|
+
let mode = unsafe { CGDisplayCopyDisplayMode(display) };
|
|
830
|
+
let (width, height) = if mode.is_null() {
|
|
831
|
+
(
|
|
832
|
+
unsafe { CGDisplayPixelsWide(display) },
|
|
833
|
+
unsafe { CGDisplayPixelsHigh(display) },
|
|
834
|
+
)
|
|
835
|
+
} else {
|
|
836
|
+
let pixels = unsafe {
|
|
837
|
+
(
|
|
838
|
+
CGDisplayModeGetPixelWidth(mode),
|
|
839
|
+
CGDisplayModeGetPixelHeight(mode),
|
|
840
|
+
)
|
|
841
|
+
};
|
|
842
|
+
unsafe { CGDisplayModeRelease(mode) };
|
|
843
|
+
pixels
|
|
844
|
+
};
|
|
845
|
+
let (width, height) = (width as i32, height as i32);
|
|
846
|
+
match crate::input::InputDevices::create_for_display(width, height, display) {
|
|
847
|
+
Ok(mut devices) => {
|
|
848
|
+
devices.move_absolute(x, y);
|
|
849
|
+
std::thread::sleep(Duration::from_millis(200));
|
|
850
|
+
println!("{}", json!({"warped": true, "x": x, "y": y, "display": display}));
|
|
851
|
+
}
|
|
852
|
+
Err(error) => {
|
|
853
|
+
eprintln!("warp-pointer: {error}");
|
|
854
|
+
return 1;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
671
858
|
#[cfg(all(desklink_vpx, not(desklink_macos_cli)))]
|
|
672
859
|
"indicator-demo" => {
|
|
673
860
|
let display = args.next().and_then(|value| value.parse().ok()).unwrap_or(0);
|
|
@@ -679,15 +866,51 @@ pub fn run() -> i32 {
|
|
|
679
866
|
"capture-probe" => {
|
|
680
867
|
let mut seconds = 3;
|
|
681
868
|
let mut output_path = None;
|
|
869
|
+
let mut frame_path = None;
|
|
870
|
+
let mut record_dir = None;
|
|
871
|
+
let mut fps = 10;
|
|
872
|
+
let mut area = None;
|
|
682
873
|
while let Some(argument) = args.next() {
|
|
683
|
-
|
|
684
|
-
output_path = args.next()
|
|
685
|
-
|
|
686
|
-
|
|
874
|
+
match argument.as_str() {
|
|
875
|
+
"--out" => output_path = args.next(),
|
|
876
|
+
"--frame" => frame_path = args.next(),
|
|
877
|
+
"--record" => record_dir = args.next(),
|
|
878
|
+
"--fps" => {
|
|
879
|
+
fps = args.next().and_then(|value| value.parse().ok()).unwrap_or(10);
|
|
880
|
+
}
|
|
881
|
+
"--rect" => {
|
|
882
|
+
let parts: Vec<f64> = args
|
|
883
|
+
.next()
|
|
884
|
+
.unwrap_or_default()
|
|
885
|
+
.split(',')
|
|
886
|
+
.filter_map(|value| value.trim().parse().ok())
|
|
887
|
+
.collect();
|
|
888
|
+
if parts.len() == 4 {
|
|
889
|
+
area = Some(CGRect {
|
|
890
|
+
origin: CGPoint {
|
|
891
|
+
x: parts[0],
|
|
892
|
+
y: parts[1],
|
|
893
|
+
},
|
|
894
|
+
size: CGSize {
|
|
895
|
+
width: parts[2],
|
|
896
|
+
height: parts[3],
|
|
897
|
+
},
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
other => {
|
|
902
|
+
if let Ok(value) = other.parse() {
|
|
903
|
+
seconds = value;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
687
906
|
}
|
|
688
907
|
}
|
|
689
908
|
let result = if unsafe { CGPreflightScreenCaptureAccess() } {
|
|
690
|
-
|
|
909
|
+
if frame_path.is_some() || record_dir.is_some() {
|
|
910
|
+
capture_rects(seconds, frame_path, record_dir, fps, area)
|
|
911
|
+
} else {
|
|
912
|
+
capture_probe(seconds)
|
|
913
|
+
}
|
|
691
914
|
} else {
|
|
692
915
|
request_screen_capture_content().and_then(|_| Err(format!("capture-permission: allow {} in System Settings › Privacy & Security › Screen & System Audio Recording, then reconnect", tcc_responsible_app_name())))
|
|
693
916
|
};
|
|
@@ -722,7 +945,7 @@ pub fn run() -> i32 {
|
|
|
722
945
|
println!("If the Input Monitoring alert did not open, allow {} in System Settings › Privacy & Security › Input Monitoring.", tcc_responsible_app_name());
|
|
723
946
|
}
|
|
724
947
|
}
|
|
725
|
-
_ => println!("desklink-host {}\nUSAGE: desklink-host [serve|capabilities|capture-probe|encode-probe|setup-input|version]", env!("CARGO_PKG_VERSION")),
|
|
948
|
+
_ => println!("desklink-host {}\nUSAGE: desklink-host [serve|capabilities|capture-probe|warp-pointer|encode-probe|setup-input|version]", env!("CARGO_PKG_VERSION")),
|
|
726
949
|
}
|
|
727
950
|
0
|
|
728
951
|
}
|
package/engine/src/main.rs
CHANGED
|
@@ -26,6 +26,8 @@ mod clipboard;
|
|
|
26
26
|
mod clipboard;
|
|
27
27
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
|
|
28
28
|
mod convert;
|
|
29
|
+
#[cfg(target_os = "linux")]
|
|
30
|
+
mod diagnose;
|
|
29
31
|
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
30
32
|
mod cursor;
|
|
31
33
|
#[cfg(target_os = "linux")]
|
|
@@ -54,6 +56,8 @@ mod hyprland;
|
|
|
54
56
|
mod indicator;
|
|
55
57
|
#[cfg(target_os = "linux")]
|
|
56
58
|
mod input;
|
|
59
|
+
#[cfg(target_os = "linux")]
|
|
60
|
+
mod input_portal;
|
|
57
61
|
#[cfg(target_os = "macos")]
|
|
58
62
|
#[path = "input_mac.rs"]
|
|
59
63
|
mod input;
|
|
@@ -245,6 +249,8 @@ fn main() {
|
|
|
245
249
|
);
|
|
246
250
|
0
|
|
247
251
|
}
|
|
252
|
+
#[cfg(target_os = "linux")]
|
|
253
|
+
"diagnose" => report(runtime.block_on(diagnose::run())),
|
|
248
254
|
"serve" => report(runtime.block_on(serve())),
|
|
249
255
|
"keep" => keeper::command(&args[1..]),
|
|
250
256
|
"capture-probe" => report(runtime.block_on(probe(
|
|
@@ -293,6 +299,7 @@ USAGE:
|
|
|
293
299
|
keep one private X screen: kiosk geometry, focus,
|
|
294
300
|
and a JSON-lines window report on stdout
|
|
295
301
|
desklink-host capabilities print what this machine can do right now
|
|
302
|
+
desklink-host diagnose print the non-prompting checks behind `desklink-host doctor`
|
|
296
303
|
desklink-host capture-probe [seconds] [display]
|
|
297
304
|
capture frames and report the stream; with a
|
|
298
305
|
display argument it reads that X display's root
|
|
@@ -981,91 +988,3 @@ fn main() {
|
|
|
981
988
|
fn main() {
|
|
982
989
|
std::process::exit(win::run());
|
|
983
990
|
}
|
|
984
|
-
|
|
985
|
-
#[cfg(all(test, target_os = "linux"))]
|
|
986
|
-
mod tests {
|
|
987
|
-
use super::*;
|
|
988
|
-
|
|
989
|
-
#[tokio::test]
|
|
990
|
-
async fn a_consumer_speaking_another_protocol_version_is_refused() {
|
|
991
|
-
let mut hello_seen = false;
|
|
992
|
-
let mut current = None;
|
|
993
|
-
let (events, _received) = tokio_mpsc::unbounded_channel();
|
|
994
|
-
let request = Request {
|
|
995
|
-
id: Some(1),
|
|
996
|
-
method: String::from("hello"),
|
|
997
|
-
params: serde_json::json!({ "protocol": 99, "client": "test" }),
|
|
998
|
-
};
|
|
999
|
-
let error = dispatch(&request, &mut hello_seen, &mut current, &events)
|
|
1000
|
-
.await
|
|
1001
|
-
.unwrap_err();
|
|
1002
|
-
assert_eq!(error.code, "unsupported-protocol");
|
|
1003
|
-
assert!(!hello_seen, "a refused handshake must not open the gate");
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
#[test]
|
|
1007
|
-
fn cursor_notice_preserves_source_coordinates_visibility_and_clock() {
|
|
1008
|
-
let line = render_event(session::Notice {
|
|
1009
|
-
session_id: String::from("cursor-session"),
|
|
1010
|
-
event: session::SessionEvent::Cursor {
|
|
1011
|
-
position: protocol::CursorPosition {
|
|
1012
|
-
x: 32,
|
|
1013
|
-
y: 64,
|
|
1014
|
-
visible: false,
|
|
1015
|
-
hotspot: None,
|
|
1016
|
-
timestamp_us: 123456,
|
|
1017
|
-
},
|
|
1018
|
-
},
|
|
1019
|
-
})
|
|
1020
|
-
.unwrap();
|
|
1021
|
-
let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
|
|
1022
|
-
assert_eq!(
|
|
1023
|
-
parsed,
|
|
1024
|
-
serde_json::json!({"event":"session.cursor","params":{"sessionId":"cursor-session","x":32,"y":64,"visible":false,"timestamp_us":123456}})
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
#[test]
|
|
1029
|
-
fn the_skipped_ice_server_warning_names_the_url_once() {
|
|
1030
|
-
let line = render_event(session::Notice {
|
|
1031
|
-
session_id: String::from("session-1"),
|
|
1032
|
-
event: session::SessionEvent::IceServerSkipped {
|
|
1033
|
-
url: String::from("turn:turn.example.com:3478?transport=tcp"),
|
|
1034
|
-
},
|
|
1035
|
-
})
|
|
1036
|
-
.expect("a skipped-URL warning is renderable");
|
|
1037
|
-
let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
|
|
1038
|
-
assert_eq!(parsed["event"], "session.iceServerSkipped");
|
|
1039
|
-
assert_eq!(
|
|
1040
|
-
parsed["params"],
|
|
1041
|
-
serde_json::json!({
|
|
1042
|
-
"sessionId": "session-1",
|
|
1043
|
-
"url": "turn:turn.example.com:3478?transport=tcp"
|
|
1044
|
-
})
|
|
1045
|
-
);
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
#[tokio::test]
|
|
1049
|
-
async fn the_state_notification_carries_exactly_what_the_document_promises() {
|
|
1050
|
-
let line = render_event(session::Notice {
|
|
1051
|
-
session_id: String::from("session-1"),
|
|
1052
|
-
event: session::SessionEvent::State {
|
|
1053
|
-
capture: "streaming",
|
|
1054
|
-
transport: String::from("connected"),
|
|
1055
|
-
first_frame: true,
|
|
1056
|
-
},
|
|
1057
|
-
})
|
|
1058
|
-
.expect("a state event is renderable");
|
|
1059
|
-
let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
|
|
1060
|
-
assert_eq!(parsed["event"], "session.state");
|
|
1061
|
-
assert_eq!(
|
|
1062
|
-
parsed["params"],
|
|
1063
|
-
serde_json::json!({
|
|
1064
|
-
"sessionId": "session-1",
|
|
1065
|
-
"capture": "streaming",
|
|
1066
|
-
"transport": "connected",
|
|
1067
|
-
"firstFrame": true
|
|
1068
|
-
})
|
|
1069
|
-
);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
package/engine/src/observe.rs
CHANGED
|
@@ -59,36 +59,3 @@ pub fn status(result: Option<&Result<Tap, Unavailable>>) -> serde_json::Value {
|
|
|
59
59
|
}),
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
#[cfg(test)]
|
|
64
|
-
mod tests {
|
|
65
|
-
use super::*;
|
|
66
|
-
|
|
67
|
-
#[test]
|
|
68
|
-
#[cfg(not(target_os = "macos"))]
|
|
69
|
-
fn a_platform_that_cannot_observe_says_so_and_delivers_nothing() {
|
|
70
|
-
let delivered = Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
|
71
|
-
let counted = delivered.clone();
|
|
72
|
-
let sink: Sink = Arc::new(move |_| {
|
|
73
|
-
counted.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
74
|
-
});
|
|
75
|
-
let refused = start(0, (1920, 1080), sink);
|
|
76
|
-
let status = status(Some(&refused));
|
|
77
|
-
assert_eq!(status["observing"], false);
|
|
78
|
-
assert_eq!(status["code"], "unsupported-platform");
|
|
79
|
-
assert_eq!(delivered.load(std::sync::atomic::Ordering::SeqCst), 0);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
#[test]
|
|
83
|
-
fn a_refusal_carries_its_code_reason_and_remedy() {
|
|
84
|
-
let refused: Result<Tap, Unavailable> = Err(Unavailable {
|
|
85
|
-
code: "missing-input-monitoring",
|
|
86
|
-
reason: String::from("reason"),
|
|
87
|
-
remedy: String::from("remedy"),
|
|
88
|
-
});
|
|
89
|
-
assert_eq!(
|
|
90
|
-
status(Some(&refused)),
|
|
91
|
-
serde_json::json!({"observing": false, "code": "missing-input-monitoring", "reason": "reason", "remedy": "remedy"})
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
}
|