@desklink/host 0.5.3 → 0.5.6
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 +33 -19
- package/dist/bridge.js +3 -3
- package/dist/bridge.js.map +1 -1
- package/dist/engineProcess.d.ts +5 -0
- package/dist/engineProcess.d.ts.map +1 -1
- package/dist/engineProcess.js +6 -3
- package/dist/engineProcess.js.map +1 -1
- package/dist/protocol.d.ts +39 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/docs/BOUNDARY.md +4 -2
- package/docs/PROTOCOL.md +70 -7
- package/engine/Cargo.lock +6 -8
- package/engine/Cargo.toml +13 -1
- package/engine/src/audio.rs +398 -0
- package/engine/src/clipboard.rs +0 -34
- package/engine/src/convert.rs +0 -170
- package/engine/src/cursor.rs +0 -23
- package/engine/src/input.rs +0 -65
- package/engine/src/input_mac.rs +0 -11
- package/engine/src/keeper.rs +0 -137
- package/engine/src/keymap.rs +0 -107
- package/engine/src/keymap_mac.rs +0 -25
- package/engine/src/mac.rs +1 -28
- package/engine/src/main.rs +22 -0
- package/engine/src/peer.rs +297 -0
- package/engine/src/portal.rs +0 -18
- package/engine/src/protocol.rs +22 -162
- package/engine/src/session.rs +176 -3
- package/engine/src/win.rs +1 -0
- package/engine/src/x11.rs +0 -13
- package/engine/vendor/rtc-ice/Cargo.toml +137 -0
- package/engine/vendor/rtc-ice/DESKLINK-PATCH.md +111 -0
- package/engine/vendor/rtc-ice/LICENSE-APACHE +201 -0
- package/engine/vendor/rtc-ice/LICENSE-MIT +21 -0
- package/engine/vendor/rtc-ice/README.md +30 -0
- package/engine/vendor/rtc-ice/examples/ping_pong.rs +376 -0
- package/engine/vendor/rtc-ice/src/agent/agent_config.rs +126 -0
- package/engine/vendor/rtc-ice/src/agent/agent_proto.rs +172 -0
- package/engine/vendor/rtc-ice/src/agent/agent_selector.rs +647 -0
- package/engine/vendor/rtc-ice/src/agent/agent_stats.rs +292 -0
- package/engine/vendor/rtc-ice/src/agent/agent_test.rs +3594 -0
- package/engine/vendor/rtc-ice/src/agent/mod.rs +1753 -0
- package/engine/vendor/rtc-ice/src/attributes/control/control_test.rs +168 -0
- package/engine/vendor/rtc-ice/src/attributes/control/mod.rs +142 -0
- package/engine/vendor/rtc-ice/src/attributes/mod.rs +6 -0
- package/engine/vendor/rtc-ice/src/attributes/priority/mod.rs +37 -0
- package/engine/vendor/rtc-ice/src/attributes/priority/priority_test.rs +39 -0
- package/engine/vendor/rtc-ice/src/attributes/use_candidate/mod.rs +33 -0
- package/engine/vendor/rtc-ice/src/attributes/use_candidate/use_candidate_test.rs +19 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_host.rs +53 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_pair.rs +215 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_pair_test.rs +201 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_peer_reflexive.rs +49 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_relay.rs +53 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_relay_test.rs +114 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive.rs +53 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive_test.rs +91 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_test.rs +540 -0
- package/engine/vendor/rtc-ice/src/candidate/mod.rs +679 -0
- package/engine/vendor/rtc-ice/src/dl_trace.rs +49 -0
- package/engine/vendor/rtc-ice/src/lib.rs +88 -0
- package/engine/vendor/rtc-ice/src/mdns/mdns_test.rs +150 -0
- package/engine/vendor/rtc-ice/src/mdns/mod.rs +69 -0
- package/engine/vendor/rtc-ice/src/network_type/mod.rs +162 -0
- package/engine/vendor/rtc-ice/src/network_type/network_type_test.rs +95 -0
- package/engine/vendor/rtc-ice/src/rand/mod.rs +91 -0
- package/engine/vendor/rtc-ice/src/rand/rand_test.rs +115 -0
- package/engine/vendor/rtc-ice/src/state/mod.rs +106 -0
- package/engine/vendor/rtc-ice/src/state/state_test.rs +46 -0
- package/engine/vendor/rtc-ice/src/stats/mod.rs +166 -0
- package/engine/vendor/rtc-ice/src/tcp_type/mod.rs +47 -0
- package/engine/vendor/rtc-ice/src/tcp_type/tcp_type_test.rs +18 -0
- package/engine/vendor/rtc-ice/src/url/mod.rs +266 -0
- package/engine/vendor/rtc-ice/src/url/url_test.rs +142 -0
- package/package.json +8 -3
package/engine/src/input.rs
CHANGED
|
@@ -300,68 +300,3 @@ impl Drop for InputDevices {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
|
-
|
|
304
|
-
#[cfg(test)]
|
|
305
|
-
mod tests {
|
|
306
|
-
use super::*;
|
|
307
|
-
|
|
308
|
-
#[test]
|
|
309
|
-
fn button_numbers_map_to_the_expected_pointer_buttons() {
|
|
310
|
-
assert_eq!(Button::from_number(1), Button::Left);
|
|
311
|
-
assert_eq!(Button::from_number(2), Button::Middle);
|
|
312
|
-
assert_eq!(Button::from_number(3), Button::Right);
|
|
313
|
-
assert_eq!(Button::from_number(9), Button::Left);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
#[test]
|
|
317
|
-
fn held_state_releases_exactly_what_this_session_pressed() {
|
|
318
|
-
let mut held = HeldState::default();
|
|
319
|
-
held.key(29, true); // left ctrl
|
|
320
|
-
held.key(46, true); // c
|
|
321
|
-
held.button(Button::Left, true);
|
|
322
|
-
// A repeat press is the same press, not a second one, so the plan still
|
|
323
|
-
// releases it exactly once.
|
|
324
|
-
held.key(29, true);
|
|
325
|
-
|
|
326
|
-
let (buttons, keys) = held.release_plan();
|
|
327
|
-
assert_eq!(buttons, vec![Button::Left]);
|
|
328
|
-
assert_eq!(keys, vec![29, 46]);
|
|
329
|
-
assert!(
|
|
330
|
-
held.release_plan().1.is_empty(),
|
|
331
|
-
"releasing twice releases nothing"
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
#[test]
|
|
336
|
-
fn an_explicit_release_leaves_only_what_is_still_pressed() {
|
|
337
|
-
let mut held = HeldState::default();
|
|
338
|
-
held.key(29, true);
|
|
339
|
-
held.key(46, true);
|
|
340
|
-
held.key(46, false);
|
|
341
|
-
let (buttons, keys) = held.release_plan();
|
|
342
|
-
assert!(buttons.is_empty());
|
|
343
|
-
assert_eq!(keys, vec![29]);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/// Proving the backend is really available here is the point: if this host
|
|
347
|
-
/// cannot create the devices, the engine must say so rather than offer a
|
|
348
|
-
/// control surface that does nothing.
|
|
349
|
-
#[test]
|
|
350
|
-
fn this_host_can_create_and_destroy_the_virtual_devices() {
|
|
351
|
-
// This touches the real desktop when uinput is writable. Ordinary
|
|
352
|
-
// suites must never create devices on the owner's active session.
|
|
353
|
-
if std::env::var("DESKLINK_TEST_UINPUT").as_deref() != Ok("1") {
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
match InputDevices::create(2560, 1440) {
|
|
357
|
-
Ok(_devices) => {}
|
|
358
|
-
Err(error) => {
|
|
359
|
-
let unavailable = probe().unwrap_err();
|
|
360
|
-
assert!(
|
|
361
|
-
error.to_string().contains(&unavailable.reason),
|
|
362
|
-
"a creation failure must be the reported availability problem"
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
package/engine/src/input_mac.rs
CHANGED
|
@@ -299,14 +299,3 @@ impl InputDevices {
|
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
|
-
|
|
303
|
-
#[cfg(test)]
|
|
304
|
-
mod tests {
|
|
305
|
-
use super::*;
|
|
306
|
-
|
|
307
|
-
#[test]
|
|
308
|
-
fn command_key_uses_quartz_command_flag() {
|
|
309
|
-
assert_eq!(modifier_flag(keycode::LEFT_META), 0x100000);
|
|
310
|
-
assert_eq!(modifier_flag(0), 0);
|
|
311
|
-
}
|
|
312
|
-
}
|
package/engine/src/keeper.rs
CHANGED
|
@@ -483,140 +483,3 @@ fn fatal(out: &mut impl Write, error: &str) -> Result<()> {
|
|
|
483
483
|
write_line(out, &serde_json::json!({ "error": error }).to_string())?;
|
|
484
484
|
Err(anyhow!("{error}"))
|
|
485
485
|
}
|
|
486
|
-
|
|
487
|
-
#[cfg(test)]
|
|
488
|
-
mod tests {
|
|
489
|
-
use super::*;
|
|
490
|
-
|
|
491
|
-
#[test]
|
|
492
|
-
fn content_fills_the_screen_and_helpers_are_parked() {
|
|
493
|
-
assert_eq!(kiosk_geometry(1240, 760, 1280, 800), (0, 0, 1280, 800));
|
|
494
|
-
// Exactly at the limit is content; one pixel under is not.
|
|
495
|
-
assert_eq!(kiosk_geometry(120, 120, 1280, 800), (0, 0, 1280, 800));
|
|
496
|
-
assert_eq!(
|
|
497
|
-
kiosk_geometry(119, 800, 1280, 800),
|
|
498
|
-
(i32::from(PARKED_XY), i32::from(PARKED_XY), 119, 800)
|
|
499
|
-
);
|
|
500
|
-
assert_eq!(
|
|
501
|
-
kiosk_geometry(1000, 24, 1280, 800),
|
|
502
|
-
(i32::from(PARKED_XY), i32::from(PARKED_XY), 1000, 24)
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
#[test]
|
|
507
|
-
fn wm_class_reads_the_class_not_the_instance() {
|
|
508
|
-
assert_eq!(
|
|
509
|
-
decode_wm_class(b"google-chrome\0Google-chrome\0"),
|
|
510
|
-
Some(String::from("Google-chrome"))
|
|
511
|
-
);
|
|
512
|
-
assert_eq!(decode_wm_class(b"instance-only\0"), None);
|
|
513
|
-
assert_eq!(decode_wm_class(b""), None);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
#[test]
|
|
517
|
-
fn titles_come_from_either_name_property_and_blank_is_none() {
|
|
518
|
-
assert_eq!(
|
|
519
|
-
decode_title("Pricing — Acme Store"),
|
|
520
|
-
Some(String::from("Pricing — Acme Store"))
|
|
521
|
-
);
|
|
522
|
-
assert_eq!(
|
|
523
|
-
decode_title("PPF probe - Google Chrome"),
|
|
524
|
-
Some(String::from("PPF probe - Google Chrome"))
|
|
525
|
-
);
|
|
526
|
-
assert_eq!(decode_title(" "), None);
|
|
527
|
-
assert_eq!(decode_title(""), None);
|
|
528
|
-
assert_eq!(
|
|
529
|
-
decode_title(&latin1(b"Gr\xf6\xdfe")),
|
|
530
|
-
Some(String::from("Gr\u{00f6}\u{00df}e"))
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
#[test]
|
|
535
|
-
fn the_report_line_is_exactly_the_promised_shape() {
|
|
536
|
-
let line = window_line(&[KeeperWindow {
|
|
537
|
-
id: 0x0040_0001,
|
|
538
|
-
title: Some(String::from("PPF probe - Google Chrome")),
|
|
539
|
-
class: Some(String::from("Google-chrome")),
|
|
540
|
-
pid: Some(1003571),
|
|
541
|
-
width: 1280,
|
|
542
|
-
height: 800,
|
|
543
|
-
}]);
|
|
544
|
-
let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
|
|
545
|
-
assert_eq!(
|
|
546
|
-
parsed,
|
|
547
|
-
serde_json::json!({
|
|
548
|
-
"windows": [{
|
|
549
|
-
"id": 0x40_0001_u64,
|
|
550
|
-
"title": "PPF probe - Google Chrome",
|
|
551
|
-
"class": "Google-chrome",
|
|
552
|
-
"pid": 1003571,
|
|
553
|
-
"width": 1280,
|
|
554
|
-
"height": 800,
|
|
555
|
-
}]
|
|
556
|
-
})
|
|
557
|
-
);
|
|
558
|
-
assert_eq!(window_line(&[]), r#"{"windows":[]}"#);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
#[test]
|
|
562
|
-
fn missing_fields_are_null_not_absent() {
|
|
563
|
-
let line = window_line(&[KeeperWindow {
|
|
564
|
-
id: 7,
|
|
565
|
-
title: None,
|
|
566
|
-
class: None,
|
|
567
|
-
pid: None,
|
|
568
|
-
width: 100,
|
|
569
|
-
height: 100,
|
|
570
|
-
}]);
|
|
571
|
-
let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
|
|
572
|
-
let window = &parsed["windows"][0];
|
|
573
|
-
assert_eq!(window["title"], serde_json::Value::Null);
|
|
574
|
-
assert_eq!(window["class"], serde_json::Value::Null);
|
|
575
|
-
assert_eq!(window["pid"], serde_json::Value::Null);
|
|
576
|
-
assert!(window.get("width").is_some());
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
#[test]
|
|
580
|
-
fn an_unopenable_display_is_refused_in_one_error_line() {
|
|
581
|
-
let mut out = Vec::new();
|
|
582
|
-
assert!(keep(Some("not a display"), &mut out).is_err());
|
|
583
|
-
let line: serde_json::Value =
|
|
584
|
-
serde_json::from_str(&String::from_utf8(out).unwrap()).unwrap();
|
|
585
|
-
assert!(line["error"]
|
|
586
|
-
.as_str()
|
|
587
|
-
.is_some_and(|error| !error.is_empty()));
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
#[test]
|
|
591
|
-
fn a_refused_keeper_says_why_in_its_error_line() {
|
|
592
|
-
let mut out = Vec::new();
|
|
593
|
-
let error = fatal(&mut out, "another window manager").unwrap_err();
|
|
594
|
-
assert_eq!(error.to_string(), "another window manager");
|
|
595
|
-
assert_eq!(
|
|
596
|
-
String::from_utf8(out).unwrap(),
|
|
597
|
-
"{\"error\":\"another window manager\"}\n"
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
#[test]
|
|
602
|
-
fn the_display_flag_has_one_spelling() {
|
|
603
|
-
let flag = |args: &[&str]| {
|
|
604
|
-
display_argument(
|
|
605
|
-
&args
|
|
606
|
-
.iter()
|
|
607
|
-
.map(|arg| String::from(*arg))
|
|
608
|
-
.collect::<Vec<_>>(),
|
|
609
|
-
)
|
|
610
|
-
};
|
|
611
|
-
assert_eq!(flag(&["--display", ":42"]), Some(String::from(":42")));
|
|
612
|
-
assert_eq!(
|
|
613
|
-
flag(&["--display", ":42", "--x"]),
|
|
614
|
-
Some(String::from(":42"))
|
|
615
|
-
);
|
|
616
|
-
assert_eq!(flag(&[]), None);
|
|
617
|
-
assert_eq!(flag(&["--display"]), None);
|
|
618
|
-
// The undocumented `--display=:42` spelling is not a display: `keep`
|
|
619
|
-
// refuses it rather than guessing what was meant.
|
|
620
|
-
assert_eq!(flag(&["--display=:42"]), None);
|
|
621
|
-
}
|
|
622
|
-
}
|
package/engine/src/keymap.rs
CHANGED
|
@@ -254,110 +254,3 @@ impl Layout {
|
|
|
254
254
|
(plan, unreachable)
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
|
-
|
|
258
|
-
#[cfg(test)]
|
|
259
|
-
mod tests {
|
|
260
|
-
use super::*;
|
|
261
|
-
|
|
262
|
-
#[test]
|
|
263
|
-
fn a_layout_that_comes_from_the_environment_is_the_one_compiled() {
|
|
264
|
-
let names = LayoutNames::from_lookup(|key| match key {
|
|
265
|
-
"XKB_DEFAULT_LAYOUT" => Some(String::from("de")),
|
|
266
|
-
_ => None,
|
|
267
|
-
});
|
|
268
|
-
assert_eq!(names.identity(), "de");
|
|
269
|
-
|
|
270
|
-
let layout = Layout::from_names(&names).expect("the German keymap should compile");
|
|
271
|
-
let (plan, unreachable) = layout.plan_text("z");
|
|
272
|
-
assert!(
|
|
273
|
-
unreachable.is_empty(),
|
|
274
|
-
"z should be reachable: {unreachable:?}"
|
|
275
|
-
);
|
|
276
|
-
// QWERTZ puts z where a US layout puts y, so the compiled keymap is the
|
|
277
|
-
// environment's, not the library default.
|
|
278
|
-
assert_eq!(plan[0][0].code, 21, "z must sit on the y key position");
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
#[test]
|
|
282
|
-
fn a_session_with_no_layout_variable_reports_the_library_default() {
|
|
283
|
-
assert_eq!(LayoutNames::from_lookup(|_| None).identity(), "us");
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
#[test]
|
|
287
|
-
fn a_character_is_planned_as_a_real_key_event_on_the_compiled_layout() {
|
|
288
|
-
let layout = Layout::from_names(&LayoutNames::from_lookup(|_| None))
|
|
289
|
-
.expect("a keymap should compile");
|
|
290
|
-
let (plan, unreachable) = layout.plan_text("aA.\n");
|
|
291
|
-
assert!(
|
|
292
|
-
unreachable.is_empty(),
|
|
293
|
-
"ASCII should be reachable: {unreachable:?}"
|
|
294
|
-
);
|
|
295
|
-
assert_eq!(plan.len(), 4);
|
|
296
|
-
assert!(!plan[0][0].shift, "lower-case a needs no shift");
|
|
297
|
-
assert!(
|
|
298
|
-
plan[1][0].shift,
|
|
299
|
-
"upper-case A needs shift on any normal layout"
|
|
300
|
-
);
|
|
301
|
-
assert_eq!(plan[0][0].code, plan[1][0].code, "both letters share a key");
|
|
302
|
-
// The plan is evdev, but the real injector expects Moonlight/VK codes.
|
|
303
|
-
// Passing Linux N (49) straight through instead presses VK_1.
|
|
304
|
-
let native: Vec<_> = plan
|
|
305
|
-
.iter()
|
|
306
|
-
.flatten()
|
|
307
|
-
.map(|stroke| crate::input::native_keycode(stroke.code).unwrap())
|
|
308
|
-
.collect();
|
|
309
|
-
assert_eq!(native, [0x41, 0x41, 0xBE, 0x0D]);
|
|
310
|
-
assert_eq!(
|
|
311
|
-
crate::input::native_keycode(plan[1][0].modifiers()[0]).unwrap(),
|
|
312
|
-
0x10
|
|
313
|
-
);
|
|
314
|
-
assert_eq!(
|
|
315
|
-
crate::input::native_keycode(modifier_key("Control").unwrap()).unwrap(),
|
|
316
|
-
0x11
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
#[test]
|
|
321
|
-
fn named_keys_and_modifiers_resolve_to_linux_key_codes() {
|
|
322
|
-
assert_eq!(named_key("Enter").unwrap().code, keycode::ENTER);
|
|
323
|
-
assert_eq!(named_key("ArrowLeft").unwrap().code, keycode::LEFT);
|
|
324
|
-
for (name, code) in [
|
|
325
|
-
("Delete", keycode::DELETE), ("Home", keycode::HOME), ("End", keycode::END),
|
|
326
|
-
("PageUp", keycode::PAGE_UP), ("PageDown", keycode::PAGE_DOWN),
|
|
327
|
-
("F1", keycode::F1), ("F2", keycode::F2), ("F3", keycode::F3),
|
|
328
|
-
("F4", keycode::F4), ("F5", keycode::F5), ("F6", keycode::F6),
|
|
329
|
-
("F7", keycode::F7), ("F8", keycode::F8), ("F9", keycode::F9),
|
|
330
|
-
("F10", keycode::F10), ("F11", keycode::F11), ("F12", keycode::F12),
|
|
331
|
-
] {
|
|
332
|
-
assert_eq!(named_key(name).unwrap().code, code);
|
|
333
|
-
assert!(crate::input::native_keycode(code).is_ok(), "{name} must reach inputtino");
|
|
334
|
-
}
|
|
335
|
-
assert_eq!(modifier_key("Control"), Some(keycode::LEFT_CTRL));
|
|
336
|
-
assert_eq!(modifier_key("Alt"), Some(keycode::LEFT_ALT));
|
|
337
|
-
assert_eq!(modifier_key("Super"), Some(keycode::LEFT_META));
|
|
338
|
-
assert!(named_key("Frobnicate").is_none());
|
|
339
|
-
assert!(modifier_key("Hyper").is_none());
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
#[test]
|
|
343
|
-
fn text_this_layout_cannot_produce_is_reported_not_invented() {
|
|
344
|
-
let layout = Layout::from_environment().unwrap();
|
|
345
|
-
let (_, unreachable) = layout.plan_text("\u{1F600}");
|
|
346
|
-
assert_eq!(unreachable, "\u{1F600}");
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
#[test]
|
|
350
|
-
fn a_symbol_only_reachable_beyond_four_levels_is_refused() {
|
|
351
|
-
let names = LayoutNames::from_lookup(|key| match key {
|
|
352
|
-
"XKB_DEFAULT_LAYOUT" => Some(String::from("de")),
|
|
353
|
-
"XKB_DEFAULT_VARIANT" => Some(String::from("neo")),
|
|
354
|
-
_ => None,
|
|
355
|
-
});
|
|
356
|
-
let layout = Layout::from_names(&names).expect("the Neo keymap should compile");
|
|
357
|
-
assert!(
|
|
358
|
-
layout.keystroke_for_char('\u{a3}').is_none(),
|
|
359
|
-
"the pound sign sits on a level this engine cannot press"
|
|
360
|
-
);
|
|
361
|
-
assert!(layout.keystroke_for_char('6').is_some());
|
|
362
|
-
}
|
|
363
|
-
}
|
package/engine/src/keymap_mac.rs
CHANGED
|
@@ -182,28 +182,3 @@ fn us_ansi_key(character: char) -> Option<(i16, bool)> {
|
|
|
182
182
|
_ => return None,
|
|
183
183
|
})
|
|
184
184
|
}
|
|
185
|
-
|
|
186
|
-
#[cfg(test)]
|
|
187
|
-
mod tests {
|
|
188
|
-
use super::*;
|
|
189
|
-
|
|
190
|
-
#[test]
|
|
191
|
-
fn maps_common_ascii_to_ansi_virtual_keys() {
|
|
192
|
-
let layout = Layout::from_environment().unwrap();
|
|
193
|
-
let lower = layout.keystroke_for_char('a').unwrap();
|
|
194
|
-
let upper = layout.keystroke_for_char('A').unwrap();
|
|
195
|
-
assert_eq!(lower.code, 0);
|
|
196
|
-
assert_eq!(upper.code, lower.code);
|
|
197
|
-
assert!(upper.shift);
|
|
198
|
-
assert_eq!(layout.keystroke_for_char('!').unwrap().code, 18);
|
|
199
|
-
assert!(layout.keystroke_for_char('\u{1f600}').is_none());
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
#[test]
|
|
203
|
-
fn resolves_mac_named_keys_and_modifiers() {
|
|
204
|
-
assert_eq!(named_key("Return").unwrap().code, 36);
|
|
205
|
-
assert_eq!(named_key("ArrowLeft").unwrap().code, 123);
|
|
206
|
-
assert_eq!(modifier_key("Command"), Some(55));
|
|
207
|
-
assert!(named_key("Frobnicate").is_none());
|
|
208
|
-
}
|
|
209
|
-
}
|
package/engine/src/mac.rs
CHANGED
|
@@ -184,6 +184,7 @@ fn capabilities() -> Value {
|
|
|
184
184
|
"formats": ["bgra"],
|
|
185
185
|
"cursor": "embedded",
|
|
186
186
|
"cursor_modes": ["embedded"],
|
|
187
|
+
// ScreenCaptureKit audio is not wired up; macOS is out of scope.
|
|
187
188
|
"audio": false,
|
|
188
189
|
"displays": displays(),
|
|
189
190
|
"grant": if capture_granted { "granted" } else { "missing-screen-recording" },
|
|
@@ -717,31 +718,3 @@ pub fn run() -> i32 {
|
|
|
717
718
|
}
|
|
718
719
|
0
|
|
719
720
|
}
|
|
720
|
-
|
|
721
|
-
#[cfg(test)]
|
|
722
|
-
mod tests {
|
|
723
|
-
use super::*;
|
|
724
|
-
|
|
725
|
-
#[cfg(desklink_vpx)]
|
|
726
|
-
#[test]
|
|
727
|
-
fn session_capabilities_are_non_prompting_and_expose_mac_features() {
|
|
728
|
-
let value = crate::session::capabilities();
|
|
729
|
-
assert_eq!(value["protocol"], crate::protocol::PROTOCOL_VERSION);
|
|
730
|
-
assert_eq!(value["platform"], "macos");
|
|
731
|
-
assert_eq!(value["capture"]["mechanism"], "screencapturekit");
|
|
732
|
-
assert_eq!(value["encode"]["codecs"][0], "vp9", "VP9 is always offered");
|
|
733
|
-
// Every Mac this engine runs on has a hardware H.264 encoder.
|
|
734
|
-
assert_eq!(value["encode"]["codecs"][1], "h264");
|
|
735
|
-
assert_eq!(value["encode"]["h264"]["encoder"], "videotoolbox");
|
|
736
|
-
assert!(value["capture"]["displays"].is_array());
|
|
737
|
-
assert!(value["clipboard"]["read"].as_bool().unwrap());
|
|
738
|
-
assert!(matches!(
|
|
739
|
-
value["capture"]["grant"].as_str(),
|
|
740
|
-
Some("granted" | "missing-screen-recording")
|
|
741
|
-
));
|
|
742
|
-
assert!(matches!(
|
|
743
|
-
value["input"]["grant"].as_str(),
|
|
744
|
-
Some("granted" | "missing-accessibility")
|
|
745
|
-
));
|
|
746
|
-
}
|
|
747
|
-
}
|
package/engine/src/main.rs
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
//! process, owns its stdin/stdout, and carries the SDP/ICE the engine produces
|
|
7
7
|
//! over whatever authenticated channel it already has.
|
|
8
8
|
|
|
9
|
+
#[cfg(target_os = "linux")]
|
|
10
|
+
mod audio;
|
|
9
11
|
#[cfg(target_os = "linux")]
|
|
10
12
|
mod capture;
|
|
11
13
|
#[cfg(target_os = "macos")]
|
|
@@ -574,6 +576,15 @@ fn render_event(notice: session::Notice) -> Option<String> {
|
|
|
574
576
|
"session.frame.changed",
|
|
575
577
|
serde_json::json!({ "sessionId": session_id, "seq": seq, "damage": damage, "timestamp_us": timestamp_us }),
|
|
576
578
|
),
|
|
579
|
+
session::SessionEvent::Audio {
|
|
580
|
+
seq,
|
|
581
|
+
timestamp_us,
|
|
582
|
+
bytes,
|
|
583
|
+
gap_micros,
|
|
584
|
+
} => (
|
|
585
|
+
"session.audio.changed",
|
|
586
|
+
serde_json::json!({ "sessionId": session_id, "seq": seq, "timestamp_us": timestamp_us, "bytes": bytes, "gap_ms": gap_micros / 1000 }),
|
|
587
|
+
),
|
|
577
588
|
session::SessionEvent::Input { input } => (
|
|
578
589
|
"session.input",
|
|
579
590
|
serde_json::json!({ "sessionId": session_id, "input": input }),
|
|
@@ -658,6 +669,7 @@ async fn dispatch(
|
|
|
658
669
|
loopback_tcp: params.loopback_tcp,
|
|
659
670
|
agent_indicator: params.agent_indicator,
|
|
660
671
|
local_frames: params.local_frames,
|
|
672
|
+
audio: params.audio,
|
|
661
673
|
ice_servers: params
|
|
662
674
|
.ice_servers
|
|
663
675
|
.into_iter()
|
|
@@ -684,6 +696,7 @@ async fn dispatch(
|
|
|
684
696
|
"geometry": session.geometry(),
|
|
685
697
|
"cursor": session.cursor_info(),
|
|
686
698
|
"agentIndicator": session.agent_indicator(),
|
|
699
|
+
"audio": session.audio_info(),
|
|
687
700
|
});
|
|
688
701
|
*current = Some(session);
|
|
689
702
|
Ok(result)
|
|
@@ -787,6 +800,15 @@ async fn dispatch(
|
|
|
787
800
|
.frame(params.since, ¶ms.path, params.region)
|
|
788
801
|
.map_err(|error| ErrorBody::new(error.code, error.message))
|
|
789
802
|
}
|
|
803
|
+
"session.audio" => {
|
|
804
|
+
let session = require_session(current)?;
|
|
805
|
+
let params: protocol::AudioParams = serde_json::from_value(request.params.clone())
|
|
806
|
+
.map_err(|error| ErrorBody::new("malformed", error.to_string()))?;
|
|
807
|
+
check_session(session, ¶ms.session_id, None)?;
|
|
808
|
+
session
|
|
809
|
+
.audio(¶ms.path, params.max_bytes)
|
|
810
|
+
.map_err(|error| ErrorBody::new(error.code, error.message))
|
|
811
|
+
}
|
|
790
812
|
"session.feed" => {
|
|
791
813
|
let session = require_session(current)?;
|
|
792
814
|
let params: protocol::FeedParams = serde_json::from_value(request.params.clone())
|