Skip to content

Commit

Permalink
feat: macos, audio, loopback (rustdesk#10025)
Browse files Browse the repository at this point in the history
Signed-off-by: fufesou <[email protected]>
  • Loading branch information
fufesou authored Nov 23, 2024
1 parent 02b046b commit 0973f51
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 21 deletions.
12 changes: 10 additions & 2 deletions .github/workflows/flutter-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ on:
env:
SCITER_RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503, also 1.78 has ABI change which causes our sciter version not working, https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
RUST_VERSION: "1.75" # sciter failed on m1 with 1.78 because of https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
MAC_RUST_VERSION: "1.81" # 1.81 is requred for macos, because of https://github.com/yury/cidre requires 1.81
CARGO_NDK_VERSION: "3.1.2"
SCITER_ARMV7_CMAKE_VERSION: "3.29.7"
SCITER_NASM_DEBVERSION: "2.14-1"
Expand Down Expand Up @@ -641,7 +642,7 @@ jobs:
target: aarch64-apple-darwin,
os: macos-latest,
# extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296
extra-build-args: "",
extra-build-args: "--screencapturekit",
arch: aarch64,
}
steps:
Expand Down Expand Up @@ -720,7 +721,7 @@ jobs:
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ env.RUST_VERSION }}
toolchain: ${{ env.MAC_RUST_VERSION }}
targets: ${{ matrix.job.target }}
components: "rustfmt"

Expand Down Expand Up @@ -767,6 +768,13 @@ jobs:
- name: Build rustdesk
run: |
if [ "${{ matrix.job.target }}" = "aarch64-apple-darwin" ]; then
MIN_MACOS_VERSION="12.3"
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET\=[0-9]*.[0-9]*/MACOSX_DEPLOYMENT_TARGET=${MIN_MACOS_VERSION}/" build.py
sed -i -e "s/platform :osx, '.*'/platform :osx, '${MIN_MACOS_VERSION}'/" flutter/macos/Podfile
sed -i -e "s/osx_minimum_system_version = \"[0-9]*.[0-9]*\"/osx_minimum_system_version = \"${MIN_MACOS_VERSION}\"/" Cargo.toml
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET = [0-9]*.[0-9]*;/MACOSX_DEPLOYMENT_TARGET = ${MIN_MACOS_VERSION};/" flutter/macos/Runner.xcodeproj/project.pbxproj
fi
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
- name: create unsigned dmg
Expand Down
22 changes: 18 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ unix-file-copy-paste = [
"dep:once_cell",
"clipboard/unix-file-copy-paste",
]
screencapturekit = ["cpal/screencapturekit"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down Expand Up @@ -77,7 +78,7 @@ fon = "0.6"
zip = "0.6"
shutdown_hooks = "0.1"
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
cpal = "0.15"
cpal = { git = "https://github.com/rustdesk-org/cpal", branch = "osx-screencapturekit" }
ringbuf = "0.3"

[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
Expand Down
9 changes: 9 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ def make_parser():
"--package",
type=str
)
if osx:
parser.add_argument(
'--screencapturekit',
action='store_true',
help='Enable feature screencapturekit'
)
return parser


Expand Down Expand Up @@ -274,6 +280,9 @@ def get_features(args):
features.append('flutter')
if args.unix_file_copy_paste:
features.append('unix-file-copy-paste')
if osx:
if args.screencapturekit:
features.append('screencapturekit')
print("features:", features)
return features

Expand Down
8 changes: 4 additions & 4 deletions flutter/lib/common/widgets/audio_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart';

const _kWindowsSystemSound = 'System Sound';
const _kSystemSound = 'System Sound';

typedef AudioINputSetDevice = void Function(String device);
typedef AudioInputBuilder = Widget Function(
Expand All @@ -21,7 +21,7 @@ class AudioInput extends StatelessWidget {
: super(key: key);

static String getDefault() {
if (isWindows) return translate('System Sound');
if (bind.mainAudioSupportLoopback()) return translate(_kSystemSound);
return '';
}

Expand Down Expand Up @@ -55,8 +55,8 @@ class AudioInput extends StatelessWidget {
static Future<Map<String, Object>> getDevicesInfo(
bool isCm, bool isVoiceCall) async {
List<String> devices = (await bind.mainGetSoundInputs()).toList();
if (isWindows) {
devices.insert(0, translate(_kWindowsSystemSound));
if (bind.mainAudioSupportLoopback()) {
devices.insert(0, translate(_kSystemSound));
}
String current = await getValue(isCm, isVoiceCall);
return {'devices': devices, 'current': current};
Expand Down
2 changes: 1 addition & 1 deletion src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ async fn check_software_update_() -> hbb_common::ResultType<()> {
let _ = crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, data);
}
}
*SOFTWARE_UPDATE_URL.lock().unwrap() = response_url;
*SOFTWARE_UPDATE_URL.lock().unwrap() = response_url;
}
Ok(())
}
Expand Down
17 changes: 10 additions & 7 deletions src/flutter_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,13 +774,6 @@ pub fn main_get_sound_inputs() -> Vec<String> {
vec![String::from("")]
}

pub fn main_get_default_sound_input() -> Option<String> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return get_default_sound_input();
#[cfg(any(target_os = "android", target_os = "ios"))]
None
}

pub fn main_get_login_device_info() -> SyncReturn<String> {
SyncReturn(get_login_device_info_json())
}
Expand Down Expand Up @@ -2317,6 +2310,16 @@ pub fn session_request_new_display_init_msgs(session_id: SessionID, display: usi
}
}

pub fn main_audio_support_loopback() -> SyncReturn<bool> {
#[cfg(target_os = "windows")]
let is_surpport = true;
#[cfg(feature = "screencapturekit")]
let is_surpport = crate::audio_service::is_screen_capture_kit_available();
#[cfg(not(any(target_os = "windows", feature = "screencapturekit")))]
let is_surpport = false;
SyncReturn(is_surpport)
}

#[cfg(target_os = "android")]
pub mod server_side {
use hbb_common::{config, log};
Expand Down
51 changes: 49 additions & 2 deletions src/server/audio_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ mod pa_impl {
}
}

#[inline]
#[cfg(feature = "screencapturekit")]
pub fn is_screen_capture_kit_available() -> bool {
cpal::available_hosts()
.iter()
.any(|host| *host == cpal::HostId::ScreenCaptureKit)
}

#[cfg(not(any(target_os = "linux", target_os = "android")))]
mod cpal_impl {
use self::service::{Reset, ServiceSwap};
Expand All @@ -170,6 +178,11 @@ mod cpal_impl {
static ref INPUT_BUFFER: Arc<Mutex<std::collections::VecDeque<f32>>> = Default::default();
}

#[cfg(feature = "screencapturekit")]
lazy_static::lazy_static! {
static ref HOST_SCREEN_CAPTURE_KIT: Result<Host, cpal::HostUnavailable> = cpal::host_from_id(cpal::HostId::ScreenCaptureKit);
}

#[derive(Default)]
pub struct State {
stream: Option<(Box<dyn StreamTrait>, Arc<Message>)>,
Expand Down Expand Up @@ -246,6 +259,27 @@ mod cpal_impl {
send_f32(&data, encoder, sp);
}

#[cfg(feature = "screencapturekit")]
fn get_device() -> ResultType<(Device, SupportedStreamConfig)> {
let audio_input = super::get_audio_input();
if !audio_input.is_empty() {
return get_audio_input(&audio_input);
}
if !is_screen_capture_kit_available() {
return get_audio_input("");
}
let device = HOST_SCREEN_CAPTURE_KIT
.as_ref()?
.default_input_device()
.with_context(|| "Failed to get default input device for loopback")?;
let format = device
.default_input_config()
.map_err(|e| anyhow!(e))
.with_context(|| "Failed to get input output format")?;
log::info!("Default input format: {:?}", format);
Ok((device, format))
}

#[cfg(windows)]
fn get_device() -> ResultType<(Device, SupportedStreamConfig)> {
let audio_input = super::get_audio_input();
Expand All @@ -267,15 +301,28 @@ mod cpal_impl {
Ok((device, format))
}

#[cfg(not(windows))]
#[cfg(not(any(windows, feature = "screencapturekit")))]
fn get_device() -> ResultType<(Device, SupportedStreamConfig)> {
let audio_input = super::get_audio_input();
get_audio_input(&audio_input)
}

fn get_audio_input(audio_input: &str) -> ResultType<(Device, SupportedStreamConfig)> {
let mut device = None;
if !audio_input.is_empty() {
#[cfg(feature = "screencapturekit")]
if !audio_input.is_empty() && is_screen_capture_kit_available() {
for d in HOST_SCREEN_CAPTURE_KIT
.as_ref()?
.devices()
.with_context(|| "Failed to get audio devices")?
{
if d.name().unwrap_or("".to_owned()) == audio_input {
device = Some(d);
break;
}
}
}
if device.is_none() && !audio_input.is_empty() {
for d in HOST
.devices()
.with_context(|| "Failed to get audio devices")?
Expand Down
2 changes: 2 additions & 0 deletions src/ui_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ pub fn get_sound_inputs() -> Vec<String> {
fn get_sound_inputs_() -> Vec<String> {
let mut out = Vec::new();
use cpal::traits::{DeviceTrait, HostTrait};
// Do not use `cpal::host_from_id(cpal::HostId::ScreenCaptureKit)` for feature = "screencapturekit"
// Because we explicitly handle the "System Sound" device.
let host = cpal::default_host();
if let Ok(devices) = host.devices() {
for device in devices {
Expand Down

0 comments on commit 0973f51

Please sign in to comment.