Merge pull request #635 from SoLongAndThanksForAllThePizza/flutter_desktop
Added comments and connection page
This commit is contained in:
commit
f56dcc9e6c
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/mobile/pages/connection_page.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@ -42,7 +43,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> {
|
||||
|
||||
buildServerBoard(BuildContext context) {
|
||||
return Center(
|
||||
child: Text("waiting implementation"),
|
||||
child: ConnectionPage(key: null),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import 'remote_page.dart';
|
||||
import 'settings_page.dart';
|
||||
import 'scan_page.dart';
|
||||
|
||||
/// Connection page for connecting to a remote peer.
|
||||
class ConnectionPage extends StatefulWidget implements PageShape {
|
||||
ConnectionPage({Key? key}) : super(key: key);
|
||||
|
||||
@ -26,8 +27,12 @@ class ConnectionPage extends StatefulWidget implements PageShape {
|
||||
_ConnectionPageState createState() => _ConnectionPageState();
|
||||
}
|
||||
|
||||
/// State for the connection page.
|
||||
class _ConnectionPageState extends State<ConnectionPage> {
|
||||
/// Controller for the id input bar.
|
||||
final _idController = TextEditingController();
|
||||
|
||||
/// Update url. If it's not null, means an update is available.
|
||||
var _updateUrl = '';
|
||||
var _menuPos;
|
||||
|
||||
@ -60,11 +65,15 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Callback for the connect button.
|
||||
/// Connects to the selected peer.
|
||||
void onConnect() {
|
||||
var id = _idController.text.trim();
|
||||
connect(id);
|
||||
}
|
||||
|
||||
/// Connect to a peer with [id].
|
||||
/// If [isFileTransfer], starts a session only for file transfer.
|
||||
void connect(String id, {bool isFileTransfer = false}) async {
|
||||
if (id == '') return;
|
||||
id = id.replaceAll(' ', '');
|
||||
@ -94,6 +103,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
}
|
||||
}
|
||||
|
||||
/// UI for software update.
|
||||
/// If [_updateUrl] is not empty, shows a button to update the software.
|
||||
Widget getUpdateUI() {
|
||||
return _updateUrl.isEmpty
|
||||
? SizedBox(height: 0)
|
||||
@ -114,6 +125,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
color: Colors.white, fontWeight: FontWeight.bold))));
|
||||
}
|
||||
|
||||
/// UI for the search bar.
|
||||
/// Search for a peer and connect to it if the id exists.
|
||||
Widget getSearchBarUI() {
|
||||
var w = Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0.0),
|
||||
@ -187,6 +200,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Get the image for the current [platform].
|
||||
Widget getPlatformImage(String platform) {
|
||||
platform = platform.toLowerCase();
|
||||
if (platform == 'mac os')
|
||||
@ -195,6 +209,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
return Image.asset('assets/$platform.png', width: 24, height: 24);
|
||||
}
|
||||
|
||||
/// Get all the saved peers.
|
||||
Widget getPeers() {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final space = 8.0;
|
||||
@ -244,6 +259,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
return Wrap(children: cards, spacing: space, runSpacing: space);
|
||||
}
|
||||
|
||||
/// Show the peer menu and handle user's choice.
|
||||
/// User might remove the peer or send a file to the peer.
|
||||
void showPeerMenu(BuildContext context, String id) async {
|
||||
var value = await showMenu(
|
||||
context: context,
|
||||
|
@ -119,6 +119,7 @@ class FfiModel with ChangeNotifier {
|
||||
_permissions.clear();
|
||||
}
|
||||
|
||||
/// Bind the event listener to receive events from the Rust core.
|
||||
void updateEventListener(String peerId) {
|
||||
final void Function(Map<String, dynamic>) cb = (evt) {
|
||||
var name = evt['name'];
|
||||
@ -179,6 +180,7 @@ class FfiModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Handle the message box event based on [evt] and [id].
|
||||
void handleMsgBox(Map<String, dynamic> evt, String id) {
|
||||
var type = evt['type'];
|
||||
var title = evt['title'];
|
||||
@ -193,6 +195,7 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Show a message box with [type], [title] and [text].
|
||||
void showMsgBox(String type, String title, String text, bool hasRetry) {
|
||||
msgBox(type, title, text);
|
||||
_timer?.cancel();
|
||||
@ -207,6 +210,7 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle the peer info event based on [evt].
|
||||
void handlePeerInfo(Map<String, dynamic> evt) {
|
||||
SmartDialog.dismiss();
|
||||
_pi.version = evt['version'];
|
||||
@ -649,6 +653,7 @@ class CursorModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mouse button enum.
|
||||
enum MouseButtons { left, right, wheel }
|
||||
|
||||
extension ToString on MouseButtons {
|
||||
@ -664,6 +669,7 @@ extension ToString on MouseButtons {
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI class for communicating with the Rust core.
|
||||
class FFI {
|
||||
static var id = "";
|
||||
static var shift = false;
|
||||
@ -679,29 +685,35 @@ class FFI {
|
||||
static final chatModel = ChatModel();
|
||||
static final fileModel = FileModel();
|
||||
|
||||
/// Get the remote id for current client.
|
||||
static String getId() {
|
||||
return getByName('remote_id');
|
||||
}
|
||||
|
||||
/// Send a mouse tap event(down and up).
|
||||
static void tap(MouseButtons button) {
|
||||
sendMouse('down', button);
|
||||
sendMouse('up', button);
|
||||
}
|
||||
|
||||
/// Send scroll event with scroll distance [y].
|
||||
static void scroll(int y) {
|
||||
setByName('send_mouse',
|
||||
json.encode(modify({'type': 'wheel', 'y': y.toString()})));
|
||||
}
|
||||
|
||||
/// Reconnect to the remote peer.
|
||||
static void reconnect() {
|
||||
setByName('reconnect');
|
||||
FFI.ffiModel.clearPermissions();
|
||||
}
|
||||
|
||||
/// Reset key modifiers to false, including [shift], [ctrl], [alt] and [command].
|
||||
static void resetModifiers() {
|
||||
shift = ctrl = alt = command = false;
|
||||
}
|
||||
|
||||
/// Modify the given modifier map [evt] based on current modifier key status.
|
||||
static Map<String, String> modify(Map<String, String> evt) {
|
||||
if (ctrl) evt['ctrl'] = 'true';
|
||||
if (shift) evt['shift'] = 'true';
|
||||
@ -710,12 +722,16 @@ class FFI {
|
||||
return evt;
|
||||
}
|
||||
|
||||
/// Send mouse press event.
|
||||
static void sendMouse(String type, MouseButtons button) {
|
||||
if (!ffiModel.keyboard()) return;
|
||||
setByName('send_mouse',
|
||||
json.encode(modify({'type': type, 'buttons': button.value})));
|
||||
}
|
||||
|
||||
/// Send key stroke event.
|
||||
/// [down] indicates the key's state(down or up).
|
||||
/// [press] indicates a click event(down and up).
|
||||
static void inputKey(String name, {bool? down, bool? press}) {
|
||||
if (!ffiModel.keyboard()) return;
|
||||
setByName(
|
||||
@ -727,6 +743,7 @@ class FFI {
|
||||
})));
|
||||
}
|
||||
|
||||
/// Send mouse movement event with distance in [x] and [y].
|
||||
static void moveMouse(double x, double y) {
|
||||
if (!ffiModel.keyboard()) return;
|
||||
var x2 = x.toInt();
|
||||
@ -734,6 +751,7 @@ class FFI {
|
||||
setByName('send_mouse', json.encode(modify({'x': '$x2', 'y': '$y2'})));
|
||||
}
|
||||
|
||||
/// List the saved peers.
|
||||
static List<Peer> peers() {
|
||||
try {
|
||||
var str = getByName('peers');
|
||||
@ -750,6 +768,7 @@ class FFI {
|
||||
return [];
|
||||
}
|
||||
|
||||
/// Connect with the given [id]. Only transfer file if [isFileTransfer].
|
||||
static void connect(String id, {bool isFileTransfer = false}) {
|
||||
if (isFileTransfer) {
|
||||
setByName('connect_file_transfer', id);
|
||||
@ -772,6 +791,7 @@ class FFI {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Login with [password], choose if the client should [remember] it.
|
||||
static void login(String password, bool remember) {
|
||||
setByName(
|
||||
'login',
|
||||
@ -781,6 +801,7 @@ class FFI {
|
||||
}));
|
||||
}
|
||||
|
||||
/// Close the remote session.
|
||||
static void close() {
|
||||
chatModel.close();
|
||||
if (FFI.imageModel.image != null && !isWebDesktop) {
|
||||
@ -796,10 +817,13 @@ class FFI {
|
||||
resetModifiers();
|
||||
}
|
||||
|
||||
/// Send **get** command to the Rust core based on [name] and [arg].
|
||||
/// Return the result as a string.
|
||||
static String getByName(String name, [String arg = '']) {
|
||||
return PlatformFFI.getByName(name, arg);
|
||||
}
|
||||
|
||||
/// Send **set** command to the Rust core based on [name] and [value].
|
||||
static void setByName(String name, [String value = '']) {
|
||||
PlatformFFI.setByName(name, value);
|
||||
}
|
||||
@ -953,6 +977,7 @@ void initializeCursorAndCanvas() async {
|
||||
FFI.canvasModel.update(xCanvas, yCanvas, scale);
|
||||
}
|
||||
|
||||
/// Translate text based on the pre-defined dictionary.
|
||||
String translate(String name) {
|
||||
if (name.startsWith('Failed to') && name.contains(': ')) {
|
||||
return name.split(': ').map((x) => translate(x)).join(': ');
|
||||
|
@ -22,6 +22,8 @@ class RgbaFrame extends Struct {
|
||||
typedef F2 = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);
|
||||
typedef F3 = void Function(Pointer<Utf8>, Pointer<Utf8>);
|
||||
|
||||
/// FFI wrapper around the native Rust core.
|
||||
/// Hides the platform differences.
|
||||
class PlatformFFI {
|
||||
static Pointer<RgbaFrame>? _lastRgbaFrame;
|
||||
static String _dir = '';
|
||||
@ -36,6 +38,8 @@ class PlatformFFI {
|
||||
return packageInfo.version;
|
||||
}
|
||||
|
||||
/// Send **get** command to the Rust core based on [name] and [arg].
|
||||
/// Return the result as a string.
|
||||
static String getByName(String name, [String arg = '']) {
|
||||
if (_getByName == null) return '';
|
||||
var a = name.toNativeUtf8();
|
||||
@ -49,6 +53,7 @@ class PlatformFFI {
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Send **set** command to the Rust core based on [name] and [value].
|
||||
static void setByName(String name, [String value = '']) {
|
||||
if (_setByName == null) return;
|
||||
var a = name.toNativeUtf8();
|
||||
@ -58,6 +63,7 @@ class PlatformFFI {
|
||||
calloc.free(b);
|
||||
}
|
||||
|
||||
/// Init the FFI class, loads the native Rust core library.
|
||||
static Future<Null> init() async {
|
||||
isIOS = Platform.isIOS;
|
||||
isAndroid = Platform.isAndroid;
|
||||
@ -112,6 +118,7 @@ class PlatformFFI {
|
||||
version = await getVersion();
|
||||
}
|
||||
|
||||
/// Start listening to the Rust core's events and frames.
|
||||
static void _startListenEvent(RustdeskImpl rustdeskImpl) {
|
||||
() async {
|
||||
await for (final message in rustdeskImpl.startEventStream()) {
|
||||
|
187
src/client.rs
187
src/client.rs
@ -39,6 +39,7 @@ pub mod helper;
|
||||
pub use helper::LatencyController;
|
||||
pub const SEC30: Duration = Duration::from_secs(30);
|
||||
|
||||
/// Client of the remote desktop.
|
||||
pub struct Client;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
@ -106,6 +107,7 @@ impl Drop for OboePlayer {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Start a new connection.
|
||||
pub async fn start(
|
||||
peer: &str,
|
||||
key: &str,
|
||||
@ -125,6 +127,7 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a new connection.
|
||||
async fn _start(
|
||||
peer: &str,
|
||||
key: &str,
|
||||
@ -259,6 +262,7 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Connect to the peer.
|
||||
async fn connect(
|
||||
local_addr: SocketAddr,
|
||||
peer: SocketAddr,
|
||||
@ -345,6 +349,7 @@ impl Client {
|
||||
Ok((conn, direct))
|
||||
}
|
||||
|
||||
/// Establish secure connection with the server.
|
||||
async fn secure_connection(
|
||||
peer_id: &str,
|
||||
signed_id_pk: Vec<u8>,
|
||||
@ -422,6 +427,7 @@ impl Client {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Request a relay connection to the server.
|
||||
async fn request_relay(
|
||||
peer: &str,
|
||||
relay_server: String,
|
||||
@ -478,6 +484,7 @@ impl Client {
|
||||
Self::create_relay(peer, uuid, relay_server, key, conn_type).await
|
||||
}
|
||||
|
||||
/// Create a relay connection to the server.
|
||||
async fn create_relay(
|
||||
peer: &str,
|
||||
uuid: String,
|
||||
@ -505,6 +512,7 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
/// Audio handler for the [`Client`].
|
||||
#[derive(Default)]
|
||||
pub struct AudioHandler {
|
||||
audio_decoder: Option<(AudioDecoder, Vec<f32>)>,
|
||||
@ -522,6 +530,7 @@ pub struct AudioHandler {
|
||||
}
|
||||
|
||||
impl AudioHandler {
|
||||
/// Create a new audio handler.
|
||||
pub fn new(latency_controller: Arc<Mutex<LatencyController>>) -> Self {
|
||||
AudioHandler {
|
||||
latency_controller,
|
||||
@ -529,6 +538,7 @@ impl AudioHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the audio playback.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn start_audio(&mut self, format0: AudioFormat) -> ResultType<()> {
|
||||
use psimple::Simple;
|
||||
@ -558,6 +568,7 @@ impl AudioHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start the audio playback.
|
||||
#[cfg(target_os = "android")]
|
||||
fn start_audio(&mut self, format0: AudioFormat) -> ResultType<()> {
|
||||
self.oboe = Some(OboePlayer::new(
|
||||
@ -568,6 +579,7 @@ impl AudioHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start the audio playback.
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
fn start_audio(&mut self, format0: AudioFormat) -> ResultType<()> {
|
||||
let device = AUDIO_HOST
|
||||
@ -592,6 +604,7 @@ impl AudioHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle audio format and create an audio decoder.
|
||||
pub fn handle_format(&mut self, f: AudioFormat) {
|
||||
match AudioDecoder::new(f.sample_rate, if f.channels > 1 { Stereo } else { Mono }) {
|
||||
Ok(d) => {
|
||||
@ -606,6 +619,7 @@ impl AudioHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle audio frame and play it.
|
||||
pub fn handle_frame(&mut self, frame: AudioFrame) {
|
||||
if frame.timestamp != 0 {
|
||||
if self
|
||||
@ -673,6 +687,7 @@ impl AudioHandler {
|
||||
});
|
||||
}
|
||||
|
||||
/// Build audio output stream for current device.
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
fn build_output_stream<T: cpal::Sample>(
|
||||
&mut self,
|
||||
@ -708,6 +723,7 @@ impl AudioHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Video handler for the [`Client`].
|
||||
pub struct VideoHandler {
|
||||
decoder: Decoder,
|
||||
latency_controller: Arc<Mutex<LatencyController>>,
|
||||
@ -715,6 +731,7 @@ pub struct VideoHandler {
|
||||
}
|
||||
|
||||
impl VideoHandler {
|
||||
/// Create a new video handler.
|
||||
pub fn new(latency_controller: Arc<Mutex<LatencyController>>) -> Self {
|
||||
VideoHandler {
|
||||
decoder: Decoder::new(VideoCodecId::VP9, (num_cpus::get() / 2) as _).unwrap(),
|
||||
@ -723,8 +740,10 @@ impl VideoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a new video frame.
|
||||
pub fn handle_frame(&mut self, vf: VideoFrame) -> ResultType<bool> {
|
||||
if vf.timestamp != 0 {
|
||||
// Update the lantency controller with the latest timestamp.
|
||||
self.latency_controller
|
||||
.lock()
|
||||
.unwrap()
|
||||
@ -736,6 +755,7 @@ impl VideoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a VP9S frame.
|
||||
pub fn handle_vp9s(&mut self, vp9s: &VP9s) -> ResultType<bool> {
|
||||
let mut last_frame = Image::new();
|
||||
for vp9 in vp9s.frames.iter() {
|
||||
@ -756,11 +776,13 @@ impl VideoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the decoder.
|
||||
pub fn reset(&mut self) {
|
||||
self.decoder = Decoder::new(VideoCodecId::VP9, 1).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Login config handler for [`Client`].
|
||||
#[derive(Default)]
|
||||
pub struct LoginConfigHandler {
|
||||
id: String,
|
||||
@ -783,12 +805,24 @@ impl Deref for LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load [`PeerConfig`] from id.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id` - id of peer
|
||||
#[inline]
|
||||
pub fn load_config(id: &str) -> PeerConfig {
|
||||
PeerConfig::load(id)
|
||||
}
|
||||
|
||||
impl LoginConfigHandler {
|
||||
/// Initialize the login config handler.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id` - id of peer
|
||||
/// * `is_file_transfer` - Whether the connection is file transfer.
|
||||
/// * `is_port_forward` - Whether the connection is port forward.
|
||||
pub fn initialize(&mut self, id: String, is_file_transfer: bool, is_port_forward: bool) {
|
||||
self.id = id;
|
||||
self.is_file_transfer = is_file_transfer;
|
||||
@ -798,6 +832,8 @@ impl LoginConfigHandler {
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// Check if the client should auto login.
|
||||
/// Return password if the client should auto login, otherwise return empty string.
|
||||
pub fn should_auto_login(&self) -> String {
|
||||
let l = self.lock_after_session_end;
|
||||
let a = !self.get_option("auto-login").is_empty();
|
||||
@ -809,27 +845,49 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load [`PeerConfig`].
|
||||
fn load_config(&self) -> PeerConfig {
|
||||
load_config(&self.id)
|
||||
}
|
||||
|
||||
/// Save a [`PeerConfig`] into the handler.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - [`PeerConfig`] to save.
|
||||
pub fn save_config(&mut self, config: PeerConfig) {
|
||||
config.store(&self.id);
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// Set an option for handler's [`PeerConfig`].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `k` - key of option
|
||||
/// * `v` - value of option
|
||||
pub fn set_option(&mut self, k: String, v: String) {
|
||||
let mut config = self.load_config();
|
||||
config.options.insert(k, v);
|
||||
self.save_config(config);
|
||||
}
|
||||
|
||||
/// Save view style to the current config.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - The view style to be saved.
|
||||
pub fn save_view_style(&mut self, value: String) {
|
||||
let mut config = self.load_config();
|
||||
config.view_style = value;
|
||||
self.save_config(config);
|
||||
}
|
||||
|
||||
/// Toggle an option in the handler.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the option to toggle.
|
||||
pub fn toggle_option(&mut self, name: String) -> Option<Message> {
|
||||
let mut option = OptionMessage::default();
|
||||
let mut config = self.load_config();
|
||||
@ -905,6 +963,12 @@ impl LoginConfigHandler {
|
||||
Some(msg_out)
|
||||
}
|
||||
|
||||
/// Get [`OptionMessage`] of the current [`LoginConfigHandler`].
|
||||
/// Return `None` if there's no option, for example, when the session is only for file transfer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ignore_default` - If `true`, ignore the default value of the option.
|
||||
fn get_option_message(&self, ignore_default: bool) -> Option<OptionMessage> {
|
||||
if self.is_port_forward || self.is_file_transfer {
|
||||
return None;
|
||||
@ -958,6 +1022,13 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the image quality option.
|
||||
/// Return [`ImageQuality`] if the option is valid, otherwise return `None`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `q` - The image quality option.
|
||||
/// * `ignore_default` - Ignore the default value.
|
||||
fn get_image_quality_enum(&self, q: &str, ignore_default: bool) -> Option<ImageQuality> {
|
||||
if q == "low" {
|
||||
Some(ImageQuality::Low)
|
||||
@ -974,6 +1045,11 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the status of a toggle option.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the toggle option.
|
||||
pub fn get_toggle_option(&self, name: &str) -> bool {
|
||||
if name == "show-remote-cursor" {
|
||||
self.config.show_remote_cursor
|
||||
@ -992,6 +1068,7 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for refreshing video.
|
||||
pub fn refresh() -> Message {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_refresh_video(true);
|
||||
@ -1000,6 +1077,12 @@ impl LoginConfigHandler {
|
||||
msg_out
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for saving custom image quality.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `bitrate` - The given bitrate.
|
||||
/// * `quantizer` - The given quantizer.
|
||||
pub fn save_custom_image_quality(&mut self, bitrate: i32, quantizer: i32) -> Message {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_option(OptionMessage {
|
||||
@ -1015,6 +1098,11 @@ impl LoginConfigHandler {
|
||||
msg_out
|
||||
}
|
||||
|
||||
/// Save the given image quality to the config.
|
||||
/// Return a [`Message`] that contains image quality, or `None` if the image quality is not valid.
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - The image quality.
|
||||
pub fn save_image_quality(&mut self, value: String) -> Option<Message> {
|
||||
let mut res = None;
|
||||
if let Some(q) = self.get_image_quality_enum(&value, false) {
|
||||
@ -1041,6 +1129,8 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle login error.
|
||||
/// Return true if the password is wrong, return false if there's an actual error.
|
||||
pub fn handle_login_error(&mut self, err: &str, interface: &impl Interface) -> bool {
|
||||
if err == "Wrong Password" {
|
||||
self.password = Default::default();
|
||||
@ -1052,6 +1142,12 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get user name.
|
||||
/// Return the name of the given peer. If the peer has no name, return the name in the config.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pi` - peer info.
|
||||
pub fn get_username(&self, pi: &PeerInfo) -> String {
|
||||
return if pi.username.is_empty() {
|
||||
self.info.username.clone()
|
||||
@ -1060,6 +1156,12 @@ impl LoginConfigHandler {
|
||||
};
|
||||
}
|
||||
|
||||
/// Handle peer info.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `username` - The name of the peer.
|
||||
/// * `pi` - The peer info.
|
||||
pub fn handle_peer_info(&mut self, username: String, pi: PeerInfo) {
|
||||
if !pi.version.is_empty() {
|
||||
self.version = hbb_common::get_version_number(&pi.version);
|
||||
@ -1109,6 +1211,7 @@ impl LoginConfigHandler {
|
||||
serde_json::to_string::<HashMap<String, String>>(&x).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Create a [`Message`] for login.
|
||||
fn create_login_msg(&self, password: Vec<u8>) -> Message {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let my_id = Config::get_id_or(crate::common::MOBILE_INFO1.lock().unwrap().clone());
|
||||
@ -1141,6 +1244,7 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Media data.
|
||||
pub enum MediaData {
|
||||
VideoFrame(VideoFrame),
|
||||
AudioFrame(AudioFrame),
|
||||
@ -1150,6 +1254,12 @@ pub enum MediaData {
|
||||
|
||||
pub type MediaSender = mpsc::Sender<MediaData>;
|
||||
|
||||
/// Start video and audio thread.
|
||||
/// Return two [`MediaSender`], they should be given to the media producer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `video_callback` - The callback for video frame. Being called when a video frame is ready.
|
||||
pub fn start_video_audio_threads<F>(video_callback: F) -> (MediaSender, MediaSender)
|
||||
where
|
||||
F: 'static + FnMut(&[u8]) + Send,
|
||||
@ -1204,6 +1314,12 @@ where
|
||||
return (video_sender, audio_sender);
|
||||
}
|
||||
|
||||
/// Handle latency test.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `t` - The latency test message.
|
||||
/// * `peer` - The peer.
|
||||
pub async fn handle_test_delay(t: TestDelay, peer: &mut Stream) {
|
||||
if !t.from_client {
|
||||
let mut msg_out = Message::new();
|
||||
@ -1212,9 +1328,21 @@ pub async fn handle_test_delay(t: TestDelay, peer: &mut Stream) {
|
||||
}
|
||||
}
|
||||
|
||||
// mask = buttons << 3 | type
|
||||
// type, 1: down, 2: up, 3: wheel
|
||||
// buttons, 1: left, 2: right, 4: middle
|
||||
/// Send mouse data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mask` - Mouse event.
|
||||
/// * mask = buttons << 3 | type
|
||||
/// * type, 1: down, 2: up, 3: wheel
|
||||
/// * buttons, 1: left, 2: right, 4: middle
|
||||
/// * `x` - X coordinate.
|
||||
/// * `y` - Y coordinate.
|
||||
/// * `alt` - Whether the alt key is pressed.
|
||||
/// * `ctrl` - Whether the ctrl key is pressed.
|
||||
/// * `shift` - Whether the shift key is pressed.
|
||||
/// * `command` - Whether the command key is pressed.
|
||||
/// * `interface` - The interface for sending data.
|
||||
#[inline]
|
||||
pub fn send_mouse(
|
||||
mask: i32,
|
||||
@ -1249,6 +1377,11 @@ pub fn send_mouse(
|
||||
interface.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
/// Avtivate OS by sending mouse movement.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `interface` - The interface for sending data.
|
||||
fn activate_os(interface: &impl Interface) {
|
||||
send_mouse(0, 0, 0, false, false, false, false, interface);
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
@ -1267,12 +1400,26 @@ fn activate_os(interface: &impl Interface) {
|
||||
*/
|
||||
}
|
||||
|
||||
/// Input the OS's password.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `p` - The password.
|
||||
/// * `avtivate` - Whether to activate OS.
|
||||
/// * `interface` - The interface for sending data.
|
||||
pub fn input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
std::thread::spawn(move || {
|
||||
_input_os_password(p, activate, interface);
|
||||
});
|
||||
}
|
||||
|
||||
/// Input the OS's password.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `p` - The password.
|
||||
/// * `avtivate` - Whether to activate OS.
|
||||
/// * `interface` - The interface for sending data.
|
||||
fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
if activate {
|
||||
activate_os(&interface);
|
||||
@ -1289,6 +1436,15 @@ fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
interface.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
/// Handle hash message sent by peer.
|
||||
/// Hash will be used for login.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `lc` - Login config.
|
||||
/// * `hash` - Hash sent by peer.
|
||||
/// * `interface` - [`Interface`] for sending data.
|
||||
/// * `peer` - [`Stream`] for communicating with peer.
|
||||
pub async fn handle_hash(
|
||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
hash: Hash,
|
||||
@ -1312,11 +1468,26 @@ pub async fn handle_hash(
|
||||
lc.write().unwrap().hash = hash;
|
||||
}
|
||||
|
||||
/// Send login message to peer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `lc` - Login config.
|
||||
/// * `password` - Password.
|
||||
/// * `peer` - [`Stream`] for communicating with peer.
|
||||
async fn send_login(lc: Arc<RwLock<LoginConfigHandler>>, password: Vec<u8>, peer: &mut Stream) {
|
||||
let msg_out = lc.read().unwrap().create_login_msg(password);
|
||||
allow_err!(peer.send(&msg_out).await);
|
||||
}
|
||||
|
||||
/// Handle login request made from ui.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `lc` - Login config.
|
||||
/// * `password` - Password.
|
||||
/// * `remember` - Whether to remember password.
|
||||
/// * `peer` - [`Stream`] for communicating with peer.
|
||||
pub async fn handle_login_from_ui(
|
||||
lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
password: String,
|
||||
@ -1335,6 +1506,7 @@ pub async fn handle_login_from_ui(
|
||||
send_login(lc.clone(), hasher2.finalize()[..].into(), peer).await;
|
||||
}
|
||||
|
||||
/// Interface for client to send data and commands.
|
||||
#[async_trait]
|
||||
pub trait Interface: Send + Clone + 'static + Sized {
|
||||
fn send(&self, data: Data);
|
||||
@ -1346,6 +1518,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
|
||||
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream);
|
||||
}
|
||||
|
||||
/// Data used by the client interface.
|
||||
#[derive(Clone)]
|
||||
pub enum Data {
|
||||
Close,
|
||||
@ -1368,6 +1541,7 @@ pub enum Data {
|
||||
ResumeJob((i32, bool)),
|
||||
}
|
||||
|
||||
/// Keycode for key events.
|
||||
#[derive(Clone)]
|
||||
pub enum Key {
|
||||
ControlKey(ControlKey),
|
||||
@ -1498,6 +1672,13 @@ lazy_static::lazy_static! {
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
/// Check if the given message is an error and can be retried.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `msgtype` - The message type.
|
||||
/// * `title` - The title of the message.
|
||||
/// * `text` - The text of the message.
|
||||
#[inline]
|
||||
pub fn check_if_retry(msgtype: &str, title: &str, text: &str) -> bool {
|
||||
msgtype == "error"
|
||||
|
@ -8,8 +8,8 @@ use hbb_common::log;
|
||||
const MAX_LATENCY: i64 = 500;
|
||||
const MIN_LATENCY: i64 = 100;
|
||||
|
||||
// based on video frame time, fix audio latency relatively.
|
||||
// only works on audio, can't fix video latency.
|
||||
/// Latency controller for syncing audio with the video stream.
|
||||
/// Only sync the audio to video, not the other way around.
|
||||
#[derive(Debug)]
|
||||
pub struct LatencyController {
|
||||
last_video_remote_ts: i64, // generated on remote deivce
|
||||
@ -28,21 +28,23 @@ impl Default for LatencyController {
|
||||
}
|
||||
|
||||
impl LatencyController {
|
||||
/// Create a new latency controller.
|
||||
pub fn new() -> Arc<Mutex<LatencyController>> {
|
||||
Arc::new(Mutex::new(LatencyController::default()))
|
||||
}
|
||||
|
||||
// first, receive new video frame and update time
|
||||
/// Update the latency controller with the latest video timestamp.
|
||||
pub fn update_video(&mut self, timestamp: i64) {
|
||||
self.last_video_remote_ts = timestamp;
|
||||
self.update_time = Instant::now();
|
||||
}
|
||||
|
||||
// second, compute audio latency
|
||||
// set MAX and MIN, avoid fixing too frequently.
|
||||
/// Check if the audio should be played based on the current latency.
|
||||
pub fn check_audio(&mut self, timestamp: i64) -> bool {
|
||||
// Compute audio latency.
|
||||
let expected = self.update_time.elapsed().as_millis() as i64 + self.last_video_remote_ts;
|
||||
let latency = expected - timestamp;
|
||||
// Set MAX and MIN, avoid fixing too frequently.
|
||||
if self.allow_audio {
|
||||
if latency.abs() > MAX_LATENCY {
|
||||
log::debug!("LATENCY > {}ms cut off, latency:{}", MAX_LATENCY, latency);
|
||||
|
112
src/flutter.rs
112
src/flutter.rs
@ -4,8 +4,12 @@ use hbb_common::{
|
||||
allow_err,
|
||||
compress::decompress,
|
||||
config::{Config, LocalConfig},
|
||||
fs, log,
|
||||
fs::{can_enable_overwrite_detection, new_send_confirm, DigestCheckResult, get_string, transform_windows_path},
|
||||
fs,
|
||||
fs::{
|
||||
can_enable_overwrite_detection, get_string, new_send_confirm, transform_windows_path,
|
||||
DigestCheckResult,
|
||||
},
|
||||
log,
|
||||
message_proto::*,
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::ConnType,
|
||||
@ -36,6 +40,12 @@ pub struct Session {
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Create a new remote session with the given id.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id` - The id of the remote session.
|
||||
/// * `is_file_transfer` - If the session is used for file transfer.
|
||||
pub fn start(id: &str, is_file_transfer: bool) {
|
||||
LocalConfig::set_remote_id(id);
|
||||
Self::close();
|
||||
@ -52,10 +62,16 @@ impl Session {
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the current session instance.
|
||||
pub fn get() -> Arc<RwLock<Option<Session>>> {
|
||||
SESSION.clone()
|
||||
}
|
||||
|
||||
/// Get the option of the current session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the option to get. Currently only `remote_dir` is supported.
|
||||
pub fn get_option(name: &str) -> String {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
if name == "remote_dir" {
|
||||
@ -66,6 +82,12 @@ impl Session {
|
||||
"".to_owned()
|
||||
}
|
||||
|
||||
/// Set the option of the current session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the option to set. Currently only `remote_dir` is supported.
|
||||
/// * `value` - The value of the option to set.
|
||||
pub fn set_option(name: String, value: String) {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
let mut value = value;
|
||||
@ -76,18 +98,25 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Input the OS password.
|
||||
pub fn input_os_password(pass: String, activate: bool) {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
input_os_password(pass, activate, session.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Send message to the remote session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - The data to send. See [`Data`] for more details.
|
||||
fn send(data: Data) {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
session.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pop a event from the event queue.
|
||||
pub fn pop_event() -> Option<String> {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
session.events2ui.write().unwrap().pop_front()
|
||||
@ -96,6 +125,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle an option.
|
||||
pub fn toggle_option(name: &str) {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
let msg = session.lc.write().unwrap().toggle_option(name.to_owned());
|
||||
@ -105,10 +135,12 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a refresh command.
|
||||
pub fn refresh() {
|
||||
Self::send(Data::Message(LoginConfigHandler::refresh()));
|
||||
}
|
||||
|
||||
/// Get image quality.
|
||||
pub fn get_image_quality() -> String {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
session.lc.read().unwrap().image_quality.clone()
|
||||
@ -117,6 +149,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set image quality.
|
||||
pub fn set_image_quality(value: &str) {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
let msg = session
|
||||
@ -130,6 +163,12 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the status of a toggle option.
|
||||
/// Return `None` if the option is not found.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the option to get.
|
||||
pub fn get_toggle_option(name: &str) -> Option<bool> {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
Some(session.lc.write().unwrap().get_toggle_option(name))
|
||||
@ -138,15 +177,23 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Login.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `password` - The password to login.
|
||||
/// * `remember` - If the password should be remembered.
|
||||
pub fn login(password: &str, remember: bool) {
|
||||
Session::send(Data::Login((password.to_owned(), remember)));
|
||||
}
|
||||
|
||||
/// Close the session.
|
||||
pub fn close() {
|
||||
Session::send(Data::Close);
|
||||
SESSION.write().unwrap().take();
|
||||
}
|
||||
|
||||
/// Reconnect to the current session.
|
||||
pub fn reconnect() {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
if let Some(sender) = session.sender.read().unwrap().as_ref() {
|
||||
@ -159,6 +206,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get `remember` flag in [`LoginConfigHandler`].
|
||||
pub fn get_remember() -> bool {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
session.lc.read().unwrap().remember
|
||||
@ -167,6 +215,11 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send message over the current session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `msg` - The message to send.
|
||||
#[inline]
|
||||
pub fn send_msg(&self, msg: Message) {
|
||||
if let Some(sender) = self.sender.read().unwrap().as_ref() {
|
||||
@ -174,6 +227,11 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send chat message over the current session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - The message to send.
|
||||
pub fn send_chat(text: String) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_chat_message(ChatMessage {
|
||||
@ -185,6 +243,7 @@ impl Session {
|
||||
Self::send_msg_static(msg_out);
|
||||
}
|
||||
|
||||
/// Send file over the current session.
|
||||
pub fn send_files(
|
||||
id: i32,
|
||||
path: String,
|
||||
@ -198,6 +257,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirm file override.
|
||||
pub fn set_confirm_override_file(
|
||||
id: i32,
|
||||
file_num: i32,
|
||||
@ -225,6 +285,11 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Static method to send message over the current session.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `msg` - The message to send.
|
||||
#[inline]
|
||||
pub fn send_msg_static(msg: Message) {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
@ -232,6 +297,13 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Push an event to the event queue.
|
||||
/// An event is stored as json in the event queue.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the event.
|
||||
/// * `event` - Fields of the event content.
|
||||
fn push_event(&self, name: &str, event: Vec<(&str, &str)>) {
|
||||
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
|
||||
assert!(h.get("name").is_none());
|
||||
@ -242,11 +314,13 @@ impl Session {
|
||||
};
|
||||
}
|
||||
|
||||
/// Get platform of peer.
|
||||
#[inline]
|
||||
fn peer_platform(&self) -> String {
|
||||
self.lc.read().unwrap().info.platform.clone()
|
||||
}
|
||||
|
||||
/// Quick method for sending a ctrl_alt_del command.
|
||||
pub fn ctrl_alt_del() {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
if session.peer_platform() == "Windows" {
|
||||
@ -259,6 +333,11 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Switch the display.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `display` - The display to switch to.
|
||||
pub fn switch_display(display: i32) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_switch_display(SwitchDisplay {
|
||||
@ -270,6 +349,7 @@ impl Session {
|
||||
Self::send_msg_static(msg_out);
|
||||
}
|
||||
|
||||
/// Send lock screen command.
|
||||
pub fn lock_screen() {
|
||||
if let Some(session) = SESSION.read().unwrap().as_ref() {
|
||||
let k = Key::ControlKey(ControlKey::LockScreen);
|
||||
@ -277,6 +357,17 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send key input command.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - The name of the key.
|
||||
/// * `down` - Whether the key is down or up.
|
||||
/// * `press` - If the key is simply being pressed(Down+Up).
|
||||
/// * `alt` - If the alt key is also pressed.
|
||||
/// * `ctrl` - If the ctrl key is also pressed.
|
||||
/// * `shift` - If the shift key is also pressed.
|
||||
/// * `command` - If the command key is also pressed.
|
||||
pub fn input_key(
|
||||
name: &str,
|
||||
down: bool,
|
||||
@ -299,6 +390,12 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Input a string of text.
|
||||
/// String is parsed into individual key presses.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - The text to input.
|
||||
pub fn input_string(value: &str) {
|
||||
let mut key_event = KeyEvent::new();
|
||||
key_event.set_seq(value.to_owned());
|
||||
@ -499,6 +596,12 @@ struct Connection {
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Create a new connection.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `session` - The session to create a new connection for.
|
||||
/// * `is_file_transfer` - Whether the connection is for file transfer.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start(session: Session, is_file_transfer: bool) {
|
||||
let mut last_recv_time = Instant::now();
|
||||
@ -591,6 +694,10 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle message from peer.
|
||||
/// Return false if the connection should be closed.
|
||||
///
|
||||
/// The message is handled by [`Message`], see [`message::Union`] for possible types.
|
||||
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&data) {
|
||||
match msg_in.union {
|
||||
@ -1144,6 +1251,7 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse [`FileDirectory`] to json.
|
||||
pub fn make_fd_to_json(fd: FileDirectory) -> String {
|
||||
use serde_json::json;
|
||||
let mut fd_json = serde_json::Map::new();
|
||||
|
@ -47,6 +47,13 @@ pub fn start_rgba_stream(s: StreamSink<ZeroCopyBuffer<Vec<u8>>>) -> ResultType<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// FFI for **get** commands which are idempotent.
|
||||
/// Return result in c string.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - name of the command
|
||||
/// * `arg` - argument of the command
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *const c_char {
|
||||
let mut res = "".to_owned();
|
||||
@ -174,6 +181,12 @@ unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *co
|
||||
CString::from_vec_unchecked(res.into_bytes()).into_raw()
|
||||
}
|
||||
|
||||
/// FFI for **set** commands which are not idempotent.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - name of the command
|
||||
/// * `arg` - argument of the command
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) {
|
||||
let value: &CStr = CStr::from_ptr(value);
|
||||
|
Loading…
x
Reference in New Issue
Block a user