Initial commit: RustDesk 1.4.7 macOS desktop port

- Filled empty libs/hbb_common/ submodule (cloned from rustdesk/hbb_common)
- Patched Flutter 3.44 / Dart 3.12 compatibility:
  * flutter/lib/generated_bridge.dart: asTypedList with cast<>, DartPort=Int64
  * flutter/lib/common.dart: DialogTheme->DialogThemeData, TabBarTheme->TabBarThemeData
  * flutter/pubspec.yaml: extended_text 14.0.0->15.0.2, google_fonts override 5.0.0
  * flutter/macos/Runner/Configs/Release.xcconfig: EXCLUDED_ARCHS=x86_64
- Build verified: cargo check + cargo build --features flutter + cargo build --release --features flutter
- Verified flutter build macos --debug and --release both produce working .app
- Verified .dmg installer (27MB arm64) created via hdiutil
- Build deps: Xcode 26.5, CocoaPods 1.16.2, Flutter 3.44, VCPKG arm64-osx
This commit is contained in:
xuwenwei
2026-06-08 21:42:20 +08:00
parent 3d8db09123
commit d3b6026bfd
840 changed files with 291780 additions and 1 deletions
+331
View File
@@ -0,0 +1,331 @@
use docopt::Docopt;
use hbb_common::{
env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV},
log,
};
use scrap::{
aom::{AomDecoder, AomEncoder, AomEncoderConfig},
codec::{EncoderApi, EncoderCfg},
Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig,
VpxVideoCodecId::{self, *},
STRIDE_ALIGN,
};
use std::{
io::Write,
time::{Duration, Instant},
};
// cargo run --package scrap --example benchmark --release --features hwcodec
const USAGE: &'static str = "
Codec benchmark.
Usage:
benchmark [--count=COUNT] [--quality=QUALITY] [--i444]
benchmark (-h | --help)
Options:
-h --help Show this screen.
--count=COUNT Capture frame count [default: 100].
--quality=QUALITY Video quality [default: 1.0].
--i444 I444.
";
#[derive(Debug, serde::Deserialize, Clone, Copy)]
struct Args {
flag_count: usize,
flag_quality: f32,
flag_i444: bool,
}
fn main() {
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info"));
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.deserialize())
.unwrap_or_else(|e| e.exit());
let quality = args.flag_quality;
let yuv_count = args.flag_count;
let mut index = 0;
let mut displays = Display::all().unwrap();
for i in 0..displays.len() {
if displays[i].is_primary() {
index = i;
break;
}
}
let d = displays.remove(index);
let mut c = Capturer::new(d).unwrap();
let width = c.width();
let height = c.height();
println!(
"benchmark {}x{} quality:{:?}, i444:{:?}",
width, height, quality, args.flag_i444
);
[VP8, VP9].map(|codec| {
test_vpx(
&mut c,
codec,
width,
height,
quality,
yuv_count,
if codec == VP8 { false } else { args.flag_i444 },
)
});
test_av1(&mut c, width, height, quality, yuv_count, args.flag_i444);
#[cfg(feature = "hwcodec")]
{
hw::test(&mut c, width, height, quality, yuv_count);
}
}
fn test_vpx(
c: &mut Capturer,
codec_id: VpxVideoCodecId,
width: usize,
height: usize,
quality: f32,
yuv_count: usize,
i444: bool,
) {
let config = EncoderCfg::VPX(VpxEncoderConfig {
width: width as _,
height: height as _,
quality,
codec: codec_id,
keyframe_interval: None,
});
let mut encoder = VpxEncoder::new(config, i444).unwrap();
let mut vpxs = vec![];
let start = Instant::now();
let mut size = 0;
let mut yuv = Vec::new();
let mut mid_data = Vec::new();
let mut counter = 0;
let mut time_sum = Duration::ZERO;
loop {
match c.frame(std::time::Duration::from_millis(30)) {
Ok(frame) => {
let tmp_timer = Instant::now();
let frame = frame.to(encoder.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
let yuv = frame.yuv().unwrap();
for ref frame in encoder
.encode(start.elapsed().as_millis() as _, &yuv, STRIDE_ALIGN)
.unwrap()
{
size += frame.data.len();
vpxs.push(frame.data.to_vec());
counter += 1;
print!("\r{codec_id:?} {}/{}", counter, yuv_count);
std::io::stdout().flush().ok();
}
for ref frame in encoder.flush().unwrap() {
size += frame.data.len();
vpxs.push(frame.data.to_vec());
counter += 1;
print!("\r{codec_id:?} {}/{}", counter, yuv_count);
std::io::stdout().flush().ok();
}
time_sum += tmp_timer.elapsed();
}
Err(e) => {
log::error!("{e:?}");
}
}
if counter >= yuv_count {
println!();
break;
}
}
assert_eq!(vpxs.len(), yuv_count);
println!(
"{:?} encode: {:?}, {} byte",
codec_id,
time_sum / yuv_count as _,
size / yuv_count
);
let mut decoder = VpxDecoder::new(VpxDecoderConfig { codec: codec_id }).unwrap();
let start = Instant::now();
for vpx in vpxs {
let _ = decoder.decode(&vpx);
let _ = decoder.flush();
}
println!(
"{:?} decode: {:?}",
codec_id,
start.elapsed() / yuv_count as _
);
}
fn test_av1(
c: &mut Capturer,
width: usize,
height: usize,
quality: f32,
yuv_count: usize,
i444: bool,
) {
let config = EncoderCfg::AOM(AomEncoderConfig {
width: width as _,
height: height as _,
quality,
keyframe_interval: None,
});
let mut encoder = AomEncoder::new(config, i444).unwrap();
let start = Instant::now();
let mut size = 0;
let mut av1s: Vec<Vec<u8>> = vec![];
let mut yuv = Vec::new();
let mut mid_data = Vec::new();
let mut counter = 0;
let mut time_sum = Duration::ZERO;
loop {
match c.frame(std::time::Duration::from_millis(30)) {
Ok(frame) => {
let tmp_timer = Instant::now();
let frame = frame.to(encoder.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
let yuv = frame.yuv().unwrap();
for ref frame in encoder
.encode(start.elapsed().as_millis() as _, &yuv, STRIDE_ALIGN)
.unwrap()
{
size += frame.data.len();
av1s.push(frame.data.to_vec());
counter += 1;
print!("\rAV1 {}/{}", counter, yuv_count);
std::io::stdout().flush().ok();
}
time_sum += tmp_timer.elapsed();
}
Err(e) => {
log::error!("{e:?}");
}
}
if counter >= yuv_count {
println!();
break;
}
}
assert_eq!(av1s.len(), yuv_count);
println!(
"AV1 encode: {:?}, {} byte",
time_sum / yuv_count as _,
size / yuv_count
);
let mut decoder = AomDecoder::new().unwrap();
let start = Instant::now();
for av1 in av1s {
let _ = decoder.decode(&av1);
let _ = decoder.flush();
}
println!("AV1 decode: {:?}", start.elapsed() / yuv_count as _);
}
#[cfg(feature = "hwcodec")]
mod hw {
use hwcodec::ffmpeg_ram::CodecInfo;
use scrap::{
hwcodec::{HwRamDecoder, HwRamEncoder, HwRamEncoderConfig},
CodecFormat,
};
use super::*;
pub fn test(c: &mut Capturer, width: usize, height: usize, quality: f32, yuv_count: usize) {
let mut h264s = Vec::new();
let mut h265s = Vec::new();
if let Some(info) = HwRamEncoder::try_get(CodecFormat::H264) {
test_encoder(width, height, quality, info, c, yuv_count, &mut h264s);
}
if let Some(info) = HwRamEncoder::try_get(CodecFormat::H265) {
test_encoder(width, height, quality, info, c, yuv_count, &mut h265s);
}
test_decoder(CodecFormat::H264, &h264s);
test_decoder(CodecFormat::H265, &h265s);
}
fn test_encoder(
width: usize,
height: usize,
quality: f32,
info: CodecInfo,
c: &mut Capturer,
yuv_count: usize,
h26xs: &mut Vec<Vec<u8>>,
) {
let mut encoder = HwRamEncoder::new(
EncoderCfg::HWRAM(HwRamEncoderConfig {
name: info.name.clone(),
mc_name: None,
width,
height,
quality,
keyframe_interval: None,
}),
false,
)
.unwrap();
let mut size = 0;
let mut yuv = Vec::new();
let mut mid_data = Vec::new();
let mut counter = 0;
let mut time_sum = Duration::ZERO;
let start = std::time::Instant::now();
loop {
match c.frame(std::time::Duration::from_millis(30)) {
Ok(frame) => {
let tmp_timer = Instant::now();
let frame = frame.to(encoder.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
let yuv = frame.yuv().unwrap();
for ref frame in encoder
.encode(&yuv, start.elapsed().as_millis() as _)
.unwrap()
{
size += frame.data.len();
h26xs.push(frame.data.to_vec());
counter += 1;
print!("\r{:?} {}/{}", info.name, counter, yuv_count);
std::io::stdout().flush().ok();
}
time_sum += tmp_timer.elapsed();
}
Err(e) => {
log::error!("{e:?}");
}
}
if counter >= yuv_count {
println!();
break;
}
}
println!(
"{}: {:?}, {} byte",
info.name,
time_sum / yuv_count as u32,
size / yuv_count,
);
}
fn test_decoder(format: CodecFormat, h26xs: &Vec<Vec<u8>>) {
let mut decoder = HwRamDecoder::new(format).unwrap();
let start = Instant::now();
let mut cnt = 0;
for h26x in h26xs {
let _ = decoder.decode(h26x).unwrap();
cnt += 1;
}
let device = format!("{:?}", decoder.info.hwdevice).to_lowercase();
let device = device.split("_").last().unwrap();
println!(
"{} {}: {:?}",
decoder.info.name,
device,
start.elapsed() / cnt
);
}
}
+124
View File
@@ -0,0 +1,124 @@
extern crate repng;
extern crate scrap;
use scrap::Display;
#[cfg(windows)]
use scrap::{CapturerMag, TraitCapturer};
#[cfg(windows)]
use std::fs::File;
fn main() {
let n = Display::all().unwrap().len();
for _i in 0..n {
#[cfg(windows)]
record(_i);
}
}
#[cfg(windows)]
fn get_display(i: usize) -> Display {
Display::all().unwrap().remove(i)
}
#[cfg(windows)]
fn record(i: usize) {
use std::time::Duration;
use scrap::{Frame, TraitPixelBuffer};
for d in Display::all().unwrap() {
println!("{:?} {} {}", d.origin(), d.width(), d.height());
}
let display = get_display(i);
let (w, h) = (display.width(), display.height());
{
let mut capture_mag = CapturerMag::new(display.origin(), display.width(), display.height())
.expect("Couldn't begin capture.");
let wnd_cls = "";
let wnd_name = "RustDeskPrivacyWindow";
if false == capture_mag.exclude(wnd_cls, wnd_name).unwrap() {
println!("No window found for cls {} name {}", wnd_cls, wnd_name);
} else {
println!("Filter window for cls {} name {}", wnd_cls, wnd_name);
}
let frame = capture_mag.frame(Duration::from_millis(0)).unwrap();
let Frame::PixelBuffer(frame) = frame else {
return;
};
let frame = frame.data();
println!("Capture data len: {}, Saving...", frame.len());
let mut bitflipped = Vec::with_capacity(w * h * 4);
let stride = frame.len() / h;
for y in 0..h {
for x in 0..w {
let i = stride * y + 4 * x;
bitflipped.extend_from_slice(&[frame[i + 2], frame[i + 1], frame[i], 255]);
}
}
// Save the image.
let name = format!("capture_mag_{}_1.png", i);
repng::encode(
File::create(name.clone()).unwrap(),
w as u32,
h as u32,
&bitflipped,
)
.unwrap();
println!("Image saved to `{}`.", name);
}
{
let mut capture_mag = CapturerMag::new(display.origin(), display.width(), display.height())
.expect("Couldn't begin capture.");
let wnd_cls = "";
let wnd_title = "RustDeskPrivacyWindow";
if false == capture_mag.exclude(wnd_cls, wnd_title).unwrap() {
println!("No window found for cls {} title {}", wnd_cls, wnd_title);
} else {
println!("Filter window for cls {} title {}", wnd_cls, wnd_title);
}
let frame = capture_mag.frame(Duration::from_millis(0)).unwrap();
let Frame::PixelBuffer(frame) = frame else {
return;
};
println!("Capture data len: {}, Saving...", frame.data().len());
let mut raw = Vec::new();
unsafe {
scrap::ARGBToRAW(
frame.data().as_ptr(),
frame.stride()[0] as _,
(&mut raw).as_mut_ptr(),
(w * 3) as _,
w as _,
h as _,
)
};
let mut bitflipped = Vec::with_capacity(w * h * 4);
let stride = raw.len() / h;
for y in 0..h {
for x in 0..w {
let i = stride * y + 3 * x;
bitflipped.extend_from_slice(&[raw[i], raw[i + 1], raw[i + 2], 255]);
}
}
let name = format!("capture_mag_{}_2.png", i);
repng::encode(
File::create(name.clone()).unwrap(),
w as u32,
h as u32,
&bitflipped,
)
.unwrap();
println!("Image saved to `{}`.", name);
}
}
+58
View File
@@ -0,0 +1,58 @@
use std::time::Duration;
use scrap::{Frame, TraitPixelBuffer};
extern crate scrap;
fn main() {
use scrap::{Capturer, Display, TraitCapturer};
use std::io::ErrorKind::WouldBlock;
use std::io::Write;
use std::process::{Command, Stdio};
let d = Display::primary().unwrap();
let (w, h) = (d.width(), d.height());
let child = Command::new("ffplay")
.args(&[
"-f",
"rawvideo",
"-pixel_format",
"bgr0",
"-video_size",
&format!("{}x{}", w, h),
"-framerate",
"60",
"-",
])
.stdin(Stdio::piped())
.spawn()
.expect("This example requires ffplay.");
let mut capturer = Capturer::new(d).unwrap();
let mut out = child.stdin.unwrap();
loop {
match capturer.frame(Duration::from_millis(0)) {
Ok(frame) => {
// Write the frame, removing end-of-row padding.
let Frame::PixelBuffer(frame) = frame else {
return;
};
let stride = frame.stride()[0];
let rowlen = 4 * w;
for row in frame.data().chunks(stride) {
let row = &row[..rowlen];
out.write_all(row).unwrap();
}
}
Err(ref e) if e.kind() == WouldBlock => {
// Wait for the frame.
}
Err(_) => {
// We're done here.
break;
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
extern crate scrap;
use scrap::Display;
fn main() {
let displays = Display::all().unwrap();
for (i, display) in displays.iter().enumerate() {
println!(
"Display {} [{}x{}]",
i + 1,
display.width(),
display.height()
);
}
}
+160
View File
@@ -0,0 +1,160 @@
extern crate docopt;
extern crate quest;
extern crate repng;
extern crate scrap;
extern crate serde;
extern crate webm;
use std::fs::{File, OpenOptions};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::{io, thread};
use docopt::Docopt;
use scrap::codec::{EncoderApi, EncoderCfg};
use webm::mux;
use webm::mux::Track;
use scrap::vpxcodec as vpx_encode;
use scrap::{Capturer, Display, TraitCapturer, STRIDE_ALIGN};
const USAGE: &'static str = "
Simple WebM screen capture.
Usage:
record-screen <path> [--time=<s>] [--fps=<fps>] [--quality=<quality>] [--ba=<kbps>] [--codec CODEC]
record-screen (-h | --help)
Options:
-h --help Show this screen.
--time=<s> Recording duration in seconds.
--fps=<fps> Frames per second [default: 30].
--quality=<quality> Video quality [default: 1.0].
--ba=<kbps> Audio bitrate in kilobits per second [default: 96].
--codec CODEC Configure the codec used. [default: vp9]
Valid values: vp8, vp9.
";
#[derive(Debug, serde::Deserialize)]
struct Args {
arg_path: PathBuf,
flag_codec: Codec,
flag_time: Option<u64>,
flag_fps: u64,
flag_quality: f32,
}
#[derive(Debug, serde::Deserialize)]
enum Codec {
Vp8,
Vp9,
}
fn main() -> io::Result<()> {
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.deserialize())
.unwrap_or_else(|e| e.exit());
let duration = args.flag_time.map(Duration::from_secs);
let d = Display::primary().unwrap();
let (width, height) = (d.width() as u32, d.height() as u32);
// Setup the multiplexer.
let out = match {
OpenOptions::new()
.write(true)
.create_new(true)
.open(&args.arg_path)
} {
Ok(file) => file,
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {
if loop {
quest::ask("Overwrite the existing file? [y/N] ");
if let Some(b) = quest::yesno(false)? {
break b;
}
} {
File::create(&args.arg_path)?
} else {
return Ok(());
}
}
Err(e) => return Err(e.into()),
};
let mut webm =
mux::Segment::new(mux::Writer::new(out)).expect("Could not initialize the multiplexer.");
let (vpx_codec, mux_codec) = match args.flag_codec {
Codec::Vp8 => (vpx_encode::VpxVideoCodecId::VP8, mux::VideoCodecId::VP8),
Codec::Vp9 => (vpx_encode::VpxVideoCodecId::VP9, mux::VideoCodecId::VP9),
};
let mut vt = webm.add_video_track(width, height, None, mux_codec);
// Setup the encoder.
let quality = args.flag_quality;
let mut vpx = vpx_encode::VpxEncoder::new(
EncoderCfg::VPX(vpx_encode::VpxEncoderConfig {
width,
height,
quality,
codec: vpx_codec,
keyframe_interval: None,
}),
false,
)
.unwrap();
// Start recording.
let start = Instant::now();
let stop = Arc::new(AtomicBool::new(false));
thread::spawn({
let stop = stop.clone();
move || {
let _ = quest::ask("Recording! Press ⏎ to stop.");
let _ = quest::text();
stop.store(true, Ordering::Release);
}
});
let spf = Duration::from_nanos(1_000_000_000 / args.flag_fps);
// Capturer object is expensive, avoiding to create it frequently.
let mut c = Capturer::new(d).unwrap();
let mut yuv = Vec::new();
let mut mid_data = Vec::new();
while !stop.load(Ordering::Acquire) {
let now = Instant::now();
let time = now - start;
if Some(true) == duration.map(|d| time > d) {
break;
}
if let Ok(frame) = c.frame(Duration::from_millis(0)) {
let ms = time.as_secs() * 1000 + time.subsec_millis() as u64;
frame.to(vpx.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
for frame in vpx.encode(ms as i64, &yuv, STRIDE_ALIGN).unwrap() {
vt.add_frame(frame.data, frame.pts as u64 * 1_000_000, frame.key);
}
}
let dt = now.elapsed();
if dt < spf {
thread::sleep(spf - dt);
}
}
// End things.
let _ = webm.finalize(None);
Ok(())
}
+141
View File
@@ -0,0 +1,141 @@
extern crate repng;
extern crate scrap;
use std::fs::File;
use std::io::ErrorKind::WouldBlock;
use std::thread;
use std::time::Duration;
use scrap::{Capturer, Display, Frame, TraitCapturer, TraitPixelBuffer};
fn main() {
let n = Display::all().unwrap().len();
for i in 0..n {
record(i);
}
}
fn get_display(i: usize) -> Display {
Display::all().unwrap().remove(i)
}
fn record(i: usize) {
let one_second = Duration::new(1, 0);
let one_frame = one_second / 60;
for d in Display::all().unwrap() {
println!("{:?} {} {}", d.origin(), d.width(), d.height());
}
let display = get_display(i);
let mut capturer = Capturer::new(display).expect("Couldn't begin capture.");
let (w, h) = (capturer.width(), capturer.height());
loop {
// Wait until there's a frame.
let frame = match capturer.frame(Duration::from_millis(0)) {
Ok(frame) => frame,
Err(error) => {
if error.kind() == WouldBlock {
// Keep spinning.
thread::sleep(one_frame);
continue;
} else {
panic!("Error: {}", error);
}
}
};
let Frame::PixelBuffer(frame) = frame else {
return;
};
let buffer = frame.data();
println!("Captured data len: {}, Saving...", buffer.len());
// Flip the BGRA image into a RGBA image.
let mut bitflipped = Vec::with_capacity(w * h * 4);
let stride = buffer.len() / h;
for y in 0..h {
for x in 0..w {
let i = stride * y + 4 * x;
bitflipped.extend_from_slice(&[buffer[i + 2], buffer[i + 1], buffer[i], 255]);
}
}
// Save the image.
let name = format!("screenshot{}_1.png", i);
repng::encode(
File::create(name.clone()).unwrap(),
w as u32,
h as u32,
&bitflipped,
)
.unwrap();
println!("Image saved to `{}`.", name);
break;
}
drop(capturer);
let display = get_display(i);
let mut capturer = Capturer::new(display).expect("Couldn't begin capture.");
let (w, h) = (capturer.width(), capturer.height());
loop {
// Wait until there's a frame.
let frame = match capturer.frame(Duration::from_millis(0)) {
Ok(frame) => frame,
Err(error) => {
if error.kind() == WouldBlock {
// Keep spinning.
thread::sleep(one_frame);
continue;
} else {
panic!("Error: {}", error);
}
}
};
let Frame::PixelBuffer(frame) = frame else {
return;
};
let buffer = frame.data();
println!("Captured data len: {}, Saving...", buffer.len());
let mut raw = Vec::new();
unsafe {
scrap::ARGBToRAW(
buffer.as_ptr(),
frame.stride()[0] as _,
(&mut raw).as_mut_ptr(),
(w * 3) as _,
w as _,
h as _,
)
};
let mut bitflipped = Vec::with_capacity(w * h * 4);
let stride = raw.len() / h;
for y in 0..h {
for x in 0..w {
let i = stride * y + 3 * x;
bitflipped.extend_from_slice(&[raw[i], raw[i + 1], raw[i + 2], 255]);
}
}
let name = format!("screenshot{}_2.png", i);
repng::encode(
File::create(name.clone()).unwrap(),
w as u32,
h as u32,
&bitflipped,
)
.unwrap();
println!("Image saved to `{}`.", name);
break;
}
}