multi remote instances

This commit is contained in:
csf 2022-05-31 14:44:06 +08:00
parent 00ba7cad81
commit 18ad23435b
5 changed files with 182 additions and 194 deletions

View File

@ -404,7 +404,7 @@ class _RemotePageState extends State<RemotePage> with WindowListener {
icon: Icon(Icons.tv), icon: Icon(Icons.tv),
onPressed: () { onPressed: () {
setState(() => _showEdit = false); setState(() => _showEdit = false);
showOptions(); showOptions(widget.id);
}, },
) )
] + ] +
@ -972,8 +972,9 @@ RadioListTile<String> getRadio(String name, String toValue, String curValue,
); );
} }
void showOptions() { void showOptions(String id) async {
String quality = FFI.getByName('image_quality'); // String quality = FFI.getByName('image_quality');
String quality = await FFI.rustdeskImpl.getImageQuality(id: id) ?? 'balanced';
if (quality == '') quality = 'balanced'; if (quality == '') quality = 'balanced';
String viewStyle = FFI.getByName('peer_option', 'view-style'); String viewStyle = FFI.getByName('peer_option', 'view-style');
var displays = <Widget>[]; var displays = <Widget>[];

View File

@ -6,6 +6,7 @@ import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/generated_bridge.dart';
import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/server_model.dart';
@ -598,17 +599,17 @@ class CursorModel with ChangeNotifier {
final rgba = Uint8List.fromList(colors.map((s) => s as int).toList()); final rgba = Uint8List.fromList(colors.map((s) => s as int).toList());
var pid = FFI.id; var pid = FFI.id;
ui.decodeImageFromPixels(rgba, width, height, ui.PixelFormat.rgba8888, ui.decodeImageFromPixels(rgba, width, height, ui.PixelFormat.rgba8888,
(image) { (image) {
if (FFI.id != pid) return; if (FFI.id != pid) return;
_image = image; _image = image;
_images[id] = Tuple3(image, _hotx, _hoty); _images[id] = Tuple3(image, _hotx, _hoty);
try { try {
// my throw exception, because the listener maybe already dispose // my throw exception, because the listener maybe already dispose
notifyListeners(); notifyListeners();
} catch (e) { } catch (e) {
print('notify cursor: $e'); print('notify cursor: $e');
} }
}); });
} }
void updateCursorId(Map<String, dynamic> evt) { void updateCursorId(Map<String, dynamic> evt) {
@ -637,7 +638,8 @@ class CursorModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void updateDisplayOriginWithCursor(double x, double y, double xCursor, double yCursor) { void updateDisplayOriginWithCursor(
double x, double y, double xCursor, double yCursor) {
_displayOriginX = x; _displayOriginX = x;
_displayOriginY = y; _displayOriginY = y;
_x = xCursor; _x = xCursor;
@ -765,7 +767,7 @@ class FFI {
return peers return peers
.map((s) => s as List<dynamic>) .map((s) => s as List<dynamic>)
.map((s) => .map((s) =>
Peer.fromJson(s[0] as String, s[1] as Map<String, dynamic>)) Peer.fromJson(s[0] as String, s[1] as Map<String, dynamic>))
.toList(); .toList();
} catch (e) { } catch (e) {
print('peers(): $e'); print('peers(): $e');
@ -779,7 +781,11 @@ class FFI {
setByName('connect_file_transfer', id); setByName('connect_file_transfer', id);
} else { } else {
FFI.chatModel.resetClientMode(); FFI.chatModel.resetClientMode();
setByName('connect', id); // setByName('connect', id);
final stream =
FFI.rustdeskImpl.connect(id: id, isFileTransfer: isFileTransfer);
// listen stream ...
// every instance will bind a stream
} }
FFI.id = id; FFI.id = id;
} }
@ -833,6 +839,8 @@ class FFI {
PlatformFFI.setByName(name, value); PlatformFFI.setByName(name, value);
} }
static RustdeskImpl get rustdeskImpl => PlatformFFI.rustdeskImpl;
static handleMouse(Map<String, dynamic> evt) { static handleMouse(Map<String, dynamic> evt) {
var type = ''; var type = '';
var isMove = false; var isMove = false;

View File

@ -30,9 +30,12 @@ class PlatformFFI {
static String _homeDir = ''; static String _homeDir = '';
static F2? _getByName; static F2? _getByName;
static F3? _setByName; static F3? _setByName;
static late RustdeskImpl _rustdeskImpl;
static void Function(Map<String, dynamic>)? _eventCallback; static void Function(Map<String, dynamic>)? _eventCallback;
static void Function(Uint8List)? _rgbaCallback; static void Function(Uint8List)? _rgbaCallback;
static RustdeskImpl get rustdeskImpl => _rustdeskImpl;
static Future<String> getVersion() async { static Future<String> getVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform(); PackageInfo packageInfo = await PackageInfo.fromPlatform();
return packageInfo.version; return packageInfo.version;
@ -88,7 +91,8 @@ class PlatformFFI {
dylib.lookupFunction<Void Function(Pointer<Utf8>, Pointer<Utf8>), F3>( dylib.lookupFunction<Void Function(Pointer<Utf8>, Pointer<Utf8>), F3>(
'set_by_name'); 'set_by_name');
_dir = (await getApplicationDocumentsDirectory()).path; _dir = (await getApplicationDocumentsDirectory()).path;
_startListenEvent(RustdeskImpl(dylib)); _rustdeskImpl = RustdeskImpl(dylib);
_startListenEvent(_rustdeskImpl); // global event
try { try {
_homeDir = (await ExternalPath.getExternalStorageDirectories())[0]; _homeDir = (await ExternalPath.getExternalStorageDirectories())[0];
} catch (e) { } catch (e) {

View File

@ -26,17 +26,22 @@ use std::{
}; };
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref SESSION: Arc<RwLock<Option<Session>>> = Default::default(); // static ref SESSION: Arc<RwLock<Option<Session>>> = Default::default();
static ref SESSIONS: RwLock<HashMap<String,Session>> = Default::default();
pub static ref EVENT_STREAM: RwLock<Option<StreamSink<String>>> = Default::default(); // rust to dart event channel pub static ref EVENT_STREAM: RwLock<Option<StreamSink<String>>> = Default::default(); // rust to dart event channel
pub static ref RGBA_STREAM: RwLock<Option<StreamSink<ZeroCopyBuffer<Vec<u8>>>>> = Default::default(); // rust to dart rgba (big u8 list) channel pub static ref RGBA_STREAM: RwLock<Option<StreamSink<ZeroCopyBuffer<Vec<u8>>>>> = Default::default(); // rust to dart rgba (big u8 list) channel
} }
#[derive(Clone, Default)] pub fn get_session(id: &str) -> Option<&Session> {
SESSIONS.read().unwrap().get(id)
}
#[derive(Clone)]
pub struct Session { pub struct Session {
id: String, id: String,
sender: Arc<RwLock<Option<mpsc::UnboundedSender<Data>>>>, sender: Arc<RwLock<Option<mpsc::UnboundedSender<Data>>>>, // UI to rust
lc: Arc<RwLock<LoginConfigHandler>>, lc: Arc<RwLock<LoginConfigHandler>>,
events2ui: Arc<RwLock<VecDeque<String>>>, events2ui: Arc<RwLock<StreamSink<String>>>,
} }
impl Session { impl Session {
@ -46,40 +51,47 @@ impl Session {
/// ///
/// * `id` - The id of the remote session. /// * `id` - The id of the remote session.
/// * `is_file_transfer` - If the session is used for file transfer. /// * `is_file_transfer` - If the session is used for file transfer.
pub fn start(id: &str, is_file_transfer: bool) { pub fn start(id: &str, is_file_transfer: bool, events2ui: StreamSink<String>) {
LocalConfig::set_remote_id(id); LocalConfig::set_remote_id(&id);
Self::close(); // TODO check same id
let mut session = Session::default(); // TODO close
// Self::close();
let events2ui = Arc::new(RwLock::new(events2ui));
let mut session = Session {
id: id.to_owned(),
sender: Default::default(),
lc: Default::default(),
events2ui,
};
session session
.lc .lc
.write() .write()
.unwrap() .unwrap()
.initialize(id.to_owned(), false, false); .initialize(id.to_owned(), false, false);
session.id = id.to_owned(); SESSIONS
*SESSION.write().unwrap() = Some(session.clone()); .write()
.unwrap()
.insert(id.to_owned(), session.clone());
std::thread::spawn(move || { std::thread::spawn(move || {
Connection::start(session, is_file_transfer); Connection::start(session, is_file_transfer);
}); });
} }
/// Get the current session instance. /// Get the current session instance.
pub fn get() -> Arc<RwLock<Option<Session>>> { // pub fn get() -> Arc<RwLock<Option<Session>>> {
SESSION.clone() // SESSION.clone()
} // }
/// Get the option of the current session. /// Get the option of the current session.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `name` - The name of the option to get. Currently only `remote_dir` is supported. /// * `name` - The name of the option to get. Currently only `remote_dir` is supported.
pub fn get_option(name: &str) -> String { pub fn get_option(&self, name: &str) -> String {
if let Some(session) = SESSION.read().unwrap().as_ref() { if name == "remote_dir" {
if name == "remote_dir" { return self.lc.read().unwrap().get_remote_dir();
return session.lc.read().unwrap().get_remote_dir();
}
return session.lc.read().unwrap().get_option(name);
} }
"".to_owned() self.lc.read().unwrap().get_option(name)
} }
/// Set the option of the current session. /// Set the option of the current session.
@ -88,78 +100,59 @@ impl Session {
/// ///
/// * `name` - The name of the option to set. Currently only `remote_dir` is supported. /// * `name` - The name of the option to set. Currently only `remote_dir` is supported.
/// * `value` - The value of the option to set. /// * `value` - The value of the option to set.
pub fn set_option(name: String, value: String) { pub fn set_option(&self, name: String, value: String) {
if let Some(session) = SESSION.read().unwrap().as_ref() { let mut value = value;
let mut value = value; let lc = self.lc.write().unwrap();
if name == "remote_dir" { if name == "remote_dir" {
value = session.lc.write().unwrap().get_all_remote_dir(value); value = lc.get_all_remote_dir(value);
}
return session.lc.write().unwrap().set_option(name, value);
} }
lc.set_option(name, value);
} }
/// Input the OS password. /// Input the OS password.
pub fn input_os_password(pass: String, activate: bool) { pub fn input_os_password(&self, pass: String, activate: bool) {
if let Some(session) = SESSION.read().unwrap().as_ref() { input_os_password(pass, activate, self.clone());
input_os_password(pass, activate, session.clone());
}
} }
// impl Interface
/// Send message to the remote session. /// Send message to the remote session.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `data` - The data to send. See [`Data`] for more details. /// * `data` - The data to send. See [`Data`] for more details.
fn send(data: Data) { // fn send(data: Data) {
if let Some(session) = SESSION.read().unwrap().as_ref() { // if let Some(session) = SESSION.read().unwrap().as_ref() {
session.send(data); // 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()
} else {
None
}
}
/// Toggle an option. /// Toggle an option.
pub fn toggle_option(name: &str) { pub fn toggle_option(&self, name: &str) {
if let Some(session) = SESSION.read().unwrap().as_ref() { let msg = self.lc.write().unwrap().toggle_option(name.to_owned());
let msg = session.lc.write().unwrap().toggle_option(name.to_owned()); if let Some(msg) = msg {
if let Some(msg) = msg { self.send_msg(msg);
session.send_msg(msg);
}
} }
} }
/// Send a refresh command. /// Send a refresh command.
pub fn refresh() { pub fn refresh(&self) {
Self::send(Data::Message(LoginConfigHandler::refresh())); self.send(Data::Message(LoginConfigHandler::refresh()));
} }
/// Get image quality. /// Get image quality.
pub fn get_image_quality() -> String { pub fn get_image_quality(&self) -> String {
if let Some(session) = SESSION.read().unwrap().as_ref() { self.lc.read().unwrap().image_quality.clone()
session.lc.read().unwrap().image_quality.clone()
} else {
"".to_owned()
}
} }
/// Set image quality. /// Set image quality.
pub fn set_image_quality(value: &str) { pub fn set_image_quality(&self, value: &str) {
if let Some(session) = SESSION.read().unwrap().as_ref() { let msg = self
let msg = session .lc
.lc .write()
.write() .unwrap()
.unwrap() .save_image_quality(value.to_owned());
.save_image_quality(value.to_owned()); if let Some(msg) = msg {
if let Some(msg) = msg { self.send_msg(msg);
session.send_msg(msg);
}
} }
} }
@ -169,12 +162,8 @@ impl Session {
/// # Arguments /// # Arguments
/// ///
/// * `name` - The name of the option to get. /// * `name` - The name of the option to get.
pub fn get_toggle_option(name: &str) -> Option<bool> { pub fn get_toggle_option(&self, name: &str) -> bool {
if let Some(session) = SESSION.read().unwrap().as_ref() { self.lc.write().unwrap().get_toggle_option(name)
Some(session.lc.write().unwrap().get_toggle_option(name))
} else {
None
}
} }
/// Login. /// Login.
@ -183,36 +172,28 @@ impl Session {
/// ///
/// * `password` - The password to login. /// * `password` - The password to login.
/// * `remember` - If the password should be remembered. /// * `remember` - If the password should be remembered.
pub fn login(password: &str, remember: bool) { pub fn login(&self, password: &str, remember: bool) {
Session::send(Data::Login((password.to_owned(), remember))); self.send(Data::Login((password.to_owned(), remember)));
} }
/// Close the session. /// Close the session.
pub fn close() { pub fn close(&self) {
Session::send(Data::Close); self.send(Data::Close);
SESSION.write().unwrap().take(); let _ = SESSIONS.write().unwrap().remove(&self.id);
} }
/// Reconnect to the current session. /// Reconnect to the current session.
pub fn reconnect() { pub fn reconnect(&self) {
if let Some(session) = SESSION.read().unwrap().as_ref() { self.send(Data::Close);
if let Some(sender) = session.sender.read().unwrap().as_ref() { let session = self.clone();
sender.send(Data::Close).ok(); std::thread::spawn(move || {
} Connection::start(session, false);
let session = session.clone(); });
std::thread::spawn(move || {
Connection::start(session, false);
});
}
} }
/// Get `remember` flag in [`LoginConfigHandler`]. /// Get `remember` flag in [`LoginConfigHandler`].
pub fn get_remember() -> bool { pub fn get_remember(&self) -> bool {
if let Some(session) = SESSION.read().unwrap().as_ref() { self.lc.read().unwrap().remember
session.lc.read().unwrap().remember
} else {
false
}
} }
/// Send message over the current session. /// Send message over the current session.
@ -222,9 +203,7 @@ impl Session {
/// * `msg` - The message to send. /// * `msg` - The message to send.
#[inline] #[inline]
pub fn send_msg(&self, msg: Message) { pub fn send_msg(&self, msg: Message) {
if let Some(sender) = self.sender.read().unwrap().as_ref() { self.send(Data::Message(msg));
sender.send(Data::Message(msg)).ok();
}
} }
/// Send chat message over the current session. /// Send chat message over the current session.
@ -232,7 +211,7 @@ impl Session {
/// # Arguments /// # Arguments
/// ///
/// * `text` - The message to send. /// * `text` - The message to send.
pub fn send_chat(text: String) { pub fn send_chat(&self, text: String) {
let mut misc = Misc::new(); let mut misc = Misc::new();
misc.set_chat_message(ChatMessage { misc.set_chat_message(ChatMessage {
text, text,
@ -240,49 +219,46 @@ impl Session {
}); });
let mut msg_out = Message::new(); let mut msg_out = Message::new();
msg_out.set_misc(misc); msg_out.set_misc(misc);
Self::send_msg_static(msg_out); self.send_msg(msg_out);
} }
// file trait
/// Send file over the current session. /// Send file over the current session.
pub fn send_files( // pub fn send_files(
id: i32, // id: i32,
path: String, // path: String,
to: String, // to: String,
file_num: i32, // file_num: i32,
include_hidden: bool, // include_hidden: bool,
is_remote: bool, // is_remote: bool,
) { // ) {
if let Some(session) = SESSION.write().unwrap().as_mut() { // if let Some(session) = SESSION.write().unwrap().as_mut() {
session.send_files(id, path, to, file_num, include_hidden, is_remote); // session.send_files(id, path, to, file_num, include_hidden, is_remote);
} // }
} // }
// TODO into file trait
/// Confirm file override. /// Confirm file override.
pub fn set_confirm_override_file( pub fn set_confirm_override_file(
&self,
id: i32, id: i32,
file_num: i32, file_num: i32,
need_override: bool, need_override: bool,
remember: bool, remember: bool,
is_upload: bool, is_upload: bool,
) { ) {
if let Some(session) = SESSION.read().unwrap().as_ref() { log::info!(
if let Some(sender) = session.sender.read().unwrap().as_ref() { "confirm file transfer, job: {}, need_override: {}",
log::info!( id,
"confirm file transfer, job: {}, need_override: {}", need_override
id, );
need_override self.send(Data::SetConfirmOverrideFile((
); id,
sender file_num,
.send(Data::SetConfirmOverrideFile(( need_override,
id, remember,
file_num, is_upload,
need_override, )));
remember,
is_upload,
)))
.ok();
}
}
} }
/// Static method to send message over the current session. /// Static method to send message over the current session.
@ -290,12 +266,12 @@ impl Session {
/// # Arguments /// # Arguments
/// ///
/// * `msg` - The message to send. /// * `msg` - The message to send.
#[inline] // #[inline]
pub fn send_msg_static(msg: Message) { // pub fn send_msg_static(msg: Message) {
if let Some(session) = SESSION.read().unwrap().as_ref() { // if let Some(session) = SESSION.read().unwrap().as_ref() {
session.send_msg(msg); // session.send_msg(msg);
} // }
} // }
/// Push an event to the event queue. /// Push an event to the event queue.
/// An event is stored as json in the event queue. /// An event is stored as json in the event queue.
@ -309,9 +285,10 @@ impl Session {
assert!(h.get("name").is_none()); assert!(h.get("name").is_none());
h.insert("name", name); h.insert("name", name);
if let Some(s) = EVENT_STREAM.read().unwrap().as_ref() { self.events2ui
s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned())); .read()
}; .unwrap()
.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned()));
} }
/// Get platform of peer. /// Get platform of peer.
@ -321,15 +298,13 @@ impl Session {
} }
/// Quick method for sending a ctrl_alt_del command. /// Quick method for sending a ctrl_alt_del command.
pub fn ctrl_alt_del() { pub fn ctrl_alt_del(&self) {
if let Some(session) = SESSION.read().unwrap().as_ref() { if self.peer_platform() == "Windows" {
if session.peer_platform() == "Windows" { let k = Key::ControlKey(ControlKey::CtrlAltDel);
let k = Key::ControlKey(ControlKey::CtrlAltDel); self.key_down_or_up(1, k, false, false, false, false);
session.key_down_or_up(1, k, false, false, false, false); } else {
} else { let k = Key::ControlKey(ControlKey::Delete);
let k = Key::ControlKey(ControlKey::Delete); self.key_down_or_up(3, k, true, true, false, false);
session.key_down_or_up(3, k, true, true, false, false);
}
} }
} }
@ -338,7 +313,7 @@ impl Session {
/// # Arguments /// # Arguments
/// ///
/// * `display` - The display to switch to. /// * `display` - The display to switch to.
pub fn switch_display(display: i32) { pub fn switch_display(&self, display: i32) {
let mut misc = Misc::new(); let mut misc = Misc::new();
misc.set_switch_display(SwitchDisplay { misc.set_switch_display(SwitchDisplay {
display, display,
@ -346,15 +321,13 @@ impl Session {
}); });
let mut msg_out = Message::new(); let mut msg_out = Message::new();
msg_out.set_misc(misc); msg_out.set_misc(misc);
Self::send_msg_static(msg_out); self.send_msg(msg_out);
} }
/// Send lock screen command. /// Send lock screen command.
pub fn lock_screen() { pub fn lock_screen(&self) {
if let Some(session) = SESSION.read().unwrap().as_ref() { let k = Key::ControlKey(ControlKey::LockScreen);
let k = Key::ControlKey(ControlKey::LockScreen); self.key_down_or_up(1, k, false, false, false, false);
session.key_down_or_up(1, k, false, false, false, false);
}
} }
/// Send key input command. /// Send key input command.
@ -369,6 +342,7 @@ impl Session {
/// * `shift` - If the shift key is also pressed. /// * `shift` - If the shift key is also pressed.
/// * `command` - If the command key is also pressed. /// * `command` - If the command key is also pressed.
pub fn input_key( pub fn input_key(
&self,
name: &str, name: &str,
down: bool, down: bool,
press: bool, press: bool,
@ -377,15 +351,13 @@ impl Session {
shift: bool, shift: bool,
command: bool, command: bool,
) { ) {
if let Some(session) = SESSION.read().unwrap().as_ref() { let chars: Vec<char> = name.chars().collect();
let chars: Vec<char> = name.chars().collect(); if chars.len() == 1 {
if chars.len() == 1 { let key = Key::_Raw(chars[0] as _);
let key = Key::_Raw(chars[0] as _); self._input_key(key, down, press, alt, ctrl, shift, command);
session._input_key(key, down, press, alt, ctrl, shift, command); } else {
} else { if let Some(key) = KEY_MAP.get(name) {
if let Some(key) = KEY_MAP.get(name) { self._input_key(key.clone(), down, press, alt, ctrl, shift, command);
session._input_key(key.clone(), down, press, alt, ctrl, shift, command);
}
} }
} }
} }
@ -396,12 +368,12 @@ impl Session {
/// # Arguments /// # Arguments
/// ///
/// * `value` - The text to input. /// * `value` - The text to input.
pub fn input_string(value: &str) { pub fn input_string(&self, value: &str) {
let mut key_event = KeyEvent::new(); let mut key_event = KeyEvent::new();
key_event.set_seq(value.to_owned()); key_event.set_seq(value.to_owned());
let mut msg_out = Message::new(); let mut msg_out = Message::new();
msg_out.set_key_event(key_event); msg_out.set_key_event(key_event);
Self::send_msg_static(msg_out); self.send_msg(msg_out);
} }
fn _input_key( fn _input_key(
@ -425,6 +397,7 @@ impl Session {
} }
pub fn send_mouse( pub fn send_mouse(
&self,
mask: i32, mask: i32,
x: i32, x: i32,
y: i32, y: i32,
@ -433,9 +406,7 @@ impl Session {
shift: bool, shift: bool,
command: bool, command: bool,
) { ) {
if let Some(session) = SESSION.read().unwrap().as_ref() { send_mouse(mask, x, y, alt, ctrl, shift, command, self);
send_mouse(mask, x, y, alt, ctrl, shift, command, session);
}
} }
fn key_down_or_up( fn key_down_or_up(

View File

@ -1,6 +1,6 @@
use crate::client::file_trait::FileManager; use crate::client::file_trait::FileManager;
use crate::flutter::connection_manager::{self, get_clients_length, get_clients_state}; use crate::flutter::connection_manager::{self, get_clients_length, get_clients_state};
use crate::flutter::{self, make_fd_to_json, Session}; use crate::flutter::{self, get_session, make_fd_to_json, Session};
use crate::start_server; use crate::start_server;
use crate::ui_interface; use crate::ui_interface;
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer}; use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
@ -69,6 +69,15 @@ pub fn start_rgba_stream(s: StreamSink<ZeroCopyBuffer<Vec<u8>>>) -> ResultType<(
Ok(()) Ok(())
} }
pub fn connect(id: String, is_file_transfer: bool, events2ui: StreamSink<String>) {
Session::start(&id, is_file_transfer, events2ui);
}
pub fn get_image_quality(id: String) -> Option<String> {
let session = get_session(&id)?;
Some(session.get_image_quality())
}
/// FFI for **get** commands which are idempotent. /// FFI for **get** commands which are idempotent.
/// Return result in c string. /// Return result in c string.
/// ///
@ -100,11 +109,6 @@ unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *co
"remember" => { "remember" => {
res = Session::get_remember().to_string(); res = Session::get_remember().to_string();
} }
"event" => {
if let Some(e) = Session::pop_event() {
res = e;
}
}
"toggle_option" => { "toggle_option" => {
if let Ok(arg) = arg.to_str() { if let Ok(arg) = arg.to_str() {
if let Some(v) = Session::get_toggle_option(arg) { if let Some(v) = Session::get_toggle_option(arg) {