Merge pull request #1741 from fufesou/test_fix_wayland_bak

Test fix wayland
This commit is contained in:
RustDesk 2022-10-18 08:53:38 +08:00 committed by GitHub
commit e23fa8c806
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 731 additions and 326 deletions

View File

@ -144,7 +144,9 @@ Description: A remote control software.
file.close() file.close()
def build_flutter_deb(version): def build_flutter_deb(version):
os.system('cargo build --features flutter --lib --release') os.system('cargo build --features default,flutter --lib --release')
# workaround ffigen
os.system('sed -i "s/ffi.NativeFunction<ffi.Bool Function(DartPort/ffi.NativeFunction<ffi.Uint8 Function(DartPort/g" flutter/lib/generated_bridge.dart')
os.chdir('flutter') os.chdir('flutter')
os.system('dpkg-deb -R rustdesk.deb tmpdeb') os.system('dpkg-deb -R rustdesk.deb tmpdeb')
os.system('flutter build linux --release') os.system('flutter build linux --release')
@ -159,8 +161,6 @@ def build_flutter_deb(version):
'cp -r build/linux/x64/release/bundle/* tmpdeb/usr/lib/rustdesk/') 'cp -r build/linux/x64/release/bundle/* tmpdeb/usr/lib/rustdesk/')
os.system( os.system(
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/') 'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
os.system(
'cp ../res/rustdesk.service.user tmpdeb/usr/share/rustdesk/files/systemd/')
os.system( os.system(
'cp ../res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png') 'cp ../res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png')
os.system( os.system(
@ -175,7 +175,6 @@ def build_flutter_deb(version):
generate_control_file(version) generate_control_file(version)
os.system('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/') os.system('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service') md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service.user')
os.system('dpkg-deb -b tmpdeb rustdesk.deb;') os.system('dpkg-deb -b tmpdeb rustdesk.deb;')
os.system('/bin/rm -rf tmpdeb/') os.system('/bin/rm -rf tmpdeb/')
@ -329,8 +328,6 @@ def main():
os.system('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/') os.system('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
os.system( os.system(
'cp res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/') 'cp res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
os.system(
'cp res/rustdesk.service.user tmpdeb/usr/share/rustdesk/files/systemd/')
os.system( os.system(
'cp res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png') 'cp res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png')
os.system( os.system(
@ -340,9 +337,9 @@ def main():
os.system('cp -a res/DEBIAN/* tmpdeb/DEBIAN/') os.system('cp -a res/DEBIAN/* tmpdeb/DEBIAN/')
os.system('strip tmpdeb/usr/bin/rustdesk') os.system('strip tmpdeb/usr/bin/rustdesk')
os.system('mkdir -p tmpdeb/usr/lib/rustdesk') os.system('mkdir -p tmpdeb/usr/lib/rustdesk')
os.system('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/lib/rustdesk/')
os.system('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/') os.system('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service') md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service.user')
md5_file('usr/lib/rustdesk/libsciter-gtk.so') md5_file('usr/lib/rustdesk/libsciter-gtk.so')
os.system('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/') os.system('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version) os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)

View File

@ -18,6 +18,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:window_size/window_size.dart' as window_size; import 'package:window_size/window_size.dart' as window_size;
import 'package:url_launcher/url_launcher.dart';
import 'common/widgets/overlay.dart'; import 'common/widgets/overlay.dart';
import 'mobile/pages/file_manager_page.dart'; import 'mobile/pages/file_manager_page.dart';
@ -618,8 +619,8 @@ class CustomAlertDialog extends StatelessWidget {
} }
} }
void msgBox( void msgBox(String type, String title, String text, String link,
String type, String title, String text, OverlayDialogManager dialogManager, OverlayDialogManager dialogManager,
{bool? hasCancel}) { {bool? hasCancel}) {
dialogManager.dismissAll(); dialogManager.dismissAll();
List<Widget> buttons = []; List<Widget> buttons = [];
@ -636,6 +637,12 @@ void msgBox(
dialogManager.dismissAll(); dialogManager.dismissAll();
} }
jumplink() {
if (link.startsWith('http')) {
launchUrl(Uri.parse(link));
}
}
if (type != "connecting" && type != "success" && !type.contains("nook")) { if (type != "connecting" && type != "success" && !type.contains("nook")) {
hasOk = true; hasOk = true;
buttons.insert(0, msgBoxButton(translate('OK'), submit)); buttons.insert(0, msgBoxButton(translate('OK'), submit));
@ -654,9 +661,13 @@ void msgBox(
dialogManager.dismissAll(); dialogManager.dismissAll();
})); }));
} }
if (link.isNotEmpty) {
buttons.insert(0, msgBoxButton(translate('JumpLink'), jumplink));
}
dialogManager.show((setState, close) => CustomAlertDialog( dialogManager.show((setState, close) => CustomAlertDialog(
title: _msgBoxTitle(title), title: _msgBoxTitle(title),
content: Text(translate(text), style: const TextStyle(fontSize: 15)), content: SelectableText(translate(text),
style: const TextStyle(fontSize: 15)),
actions: buttons, actions: buttons,
onSubmit: hasOk ? submit : null, onSubmit: hasOk ? submit : null,
onCancel: hasCancel == true ? cancel : null, onCancel: hasCancel == true ? cancel : null,

View File

@ -414,7 +414,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
final root = await bind.mainIsRoot(); final root = await bind.mainIsRoot();
final release = await bind.mainIsRelease(); final release = await bind.mainIsRelease();
if (Platform.isWindows && release && !installed && !root) { if (Platform.isWindows && release && !installed && !root) {
msgBox('custom-elevation-nocancel', 'Prompt', 'elevation_prompt', msgBox('custom-elevation-nocancel', 'Prompt', 'elevation_prompt', '',
gFFI.dialogManager); gFFI.dialogManager);
} }
}); });

View File

@ -6,7 +6,7 @@ import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
void clientClose(OverlayDialogManager dialogManager) { void clientClose(OverlayDialogManager dialogManager) {
msgBox('', 'Close', 'Are you sure to close the connection?', dialogManager); msgBox('', 'Close', 'Are you sure to close the connection?', '', dialogManager);
} }
void showSuccess() { void showSuccess() {

View File

@ -222,26 +222,27 @@ class FfiModel with ChangeNotifier {
handleMsgBox(Map<String, dynamic> evt, String id) { handleMsgBox(Map<String, dynamic> evt, String id) {
if (parent.target == null) return; if (parent.target == null) return;
final dialogManager = parent.target!.dialogManager; final dialogManager = parent.target!.dialogManager;
var type = evt['type']; final type = evt['type'];
var title = evt['title']; final title = evt['title'];
var text = evt['text']; final text = evt['text'];
final link = evt['link'];
if (type == 're-input-password') { if (type == 're-input-password') {
wrongPasswordDialog(id, dialogManager); wrongPasswordDialog(id, dialogManager);
} else if (type == 'input-password') { } else if (type == 'input-password') {
enterPasswordDialog(id, dialogManager); enterPasswordDialog(id, dialogManager);
} else if (type == 'restarting') { } else if (type == 'restarting') {
showMsgBox(id, type, title, text, false, dialogManager, hasCancel: false); showMsgBox(id, type, title, text, link, false, dialogManager, hasCancel: false);
} else { } else {
var hasRetry = evt['hasRetry'] == 'true'; var hasRetry = evt['hasRetry'] == 'true';
showMsgBox(id, type, title, text, hasRetry, dialogManager); showMsgBox(id, type, title, text, link, hasRetry, dialogManager);
} }
} }
/// Show a message box with [type], [title] and [text]. /// Show a message box with [type], [title] and [text].
showMsgBox(String id, String type, String title, String text, bool hasRetry, showMsgBox(String id, String type, String title, String text, String link,
OverlayDialogManager dialogManager, bool hasRetry, OverlayDialogManager dialogManager,
{bool? hasCancel}) { {bool? hasCancel}) {
msgBox(type, title, text, dialogManager, hasCancel: hasCancel); msgBox(type, title, text, link, dialogManager, hasCancel: hasCancel);
_timer?.cancel(); _timer?.cancel();
if (hasRetry) { if (hasRetry) {
_timer = Timer(Duration(seconds: _reconnects), () { _timer = Timer(Duration(seconds: _reconnects), () {

View File

@ -8,7 +8,7 @@ use tfc::{traits::*, Context as TFC_Context, Key as TFC_Key};
pub struct Enigo { pub struct Enigo {
xdo: EnigoXdo, xdo: EnigoXdo,
is_x11: bool, is_x11: bool,
tfc: TFC_Context, tfc: Option<TFC_Context>,
uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>, uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
uinput_mouse: Option<Box<dyn MouseControllable + Send>>, uinput_mouse: Option<Box<dyn MouseControllable + Send>>,
} }
@ -35,20 +35,22 @@ impl Enigo {
} }
fn tfc_key_down_or_up(&mut self, key: Key, down: bool, up: bool) -> bool { fn tfc_key_down_or_up(&mut self, key: Key, down: bool, up: bool) -> bool {
match &mut self.tfc {
None => false,
Some(tfc) => {
if let Key::Layout(chr) = key { if let Key::Layout(chr) = key {
if down { if down {
if let Err(_) = self.tfc.unicode_char_down(chr) { if let Err(_) = tfc.unicode_char_down(chr) {
return false; return false;
} }
} }
if up { if up {
if let Err(_) = self.tfc.unicode_char_up(chr) { if let Err(_) = tfc.unicode_char_up(chr) {
return false; return false;
} }
} }
return true; return true;
} }
let key = match convert_to_tfc_key(key) { let key = match convert_to_tfc_key(key) {
Some(key) => key, Some(key) => key,
None => { None => {
@ -57,24 +59,31 @@ impl Enigo {
}; };
if down { if down {
if let Err(_) = self.tfc.key_down(key) { if let Err(_) = tfc.key_down(key) {
return false; return false;
} }
}; };
if up { if up {
if let Err(_) = self.tfc.key_up(key) { if let Err(_) = tfc.key_up(key) {
return false; return false;
} }
}; };
return true; return true;
} }
} }
}
}
impl Default for Enigo { impl Default for Enigo {
fn default() -> Self { fn default() -> Self {
let is_x11 = "x11" == hbb_common::platform::linux::get_display_server();
Self { Self {
is_x11: "x11" == hbb_common::platform::linux::get_display_server(), is_x11,
tfc: TFC_Context::new().expect("kbd context error"), tfc: if is_x11 {
Some(TFC_Context::new().expect("kbd context error"))
} else {
None
},
uinput_keyboard: None, uinput_keyboard: None,
uinput_mouse: None, uinput_mouse: None,
xdo: EnigoXdo::default(), xdo: EnigoXdo::default(),

View File

@ -506,6 +506,19 @@ message AudioFrame {
int64 timestamp = 2; int64 timestamp = 2;
} }
// Notify peer to show message box.
message MessageBox {
// Message type. Refer to flutter/lib/commom.dart/msgBox().
string msgtype = 1;
string title = 2;
// English
string text = 3;
// If not empty, msgbox provides a button to following the link.
// The link here can't be directly http url.
// It must be the key of http url configed in peer side or "rustdesk://*" (jump in app).
string link = 4;
}
message BackNotification { message BackNotification {
// no need to consider block input by someone else // no need to consider block input by someone else
enum BlockInputState { enum BlockInputState {
@ -581,5 +594,6 @@ message Message {
FileResponse file_response = 18; FileResponse file_response = 18;
Misc misc = 19; Misc misc = 19;
Cliprdr cliprdr = 20; Cliprdr cliprdr = 20;
MessageBox message_box = 21;
} }
} }

View File

@ -53,11 +53,19 @@ lazy_static::lazy_static! {
static ref HW_CODEC_CONFIG: Arc<RwLock<HwCodecConfig>> = Arc::new(RwLock::new(HwCodecConfig::load())); static ref HW_CODEC_CONFIG: Arc<RwLock<HwCodecConfig>> = Arc::new(RwLock::new(HwCodecConfig::load()));
} }
// #[cfg(any(target_os = "android", target_os = "ios"))]
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref APP_DIR: Arc<RwLock<String>> = Default::default(); pub static ref APP_DIR: Arc<RwLock<String>> = Default::default();
pub static ref APP_HOME_DIR: Arc<RwLock<String>> = Default::default(); pub static ref APP_HOME_DIR: Arc<RwLock<String>> = Default::default();
} }
// #[cfg(any(target_os = "android", target_os = "ios"))]
lazy_static::lazy_static! {
pub static ref HELPER_URL: HashMap<&'static str, &'static str> = HashMap::from([
("rustdesk docs home", "https://rustdesk.com/docs/en/"),
("rustdesk docs x11-required", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
]);
}
const CHARS: &'static [char] = &[ const CHARS: &'static [char] = &[
'2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',

View File

@ -1,7 +1,44 @@
use crate::ResultType; use crate::ResultType;
lazy_static::lazy_static! {
pub static ref DISTRO: Disto = Disto::new();
}
pub struct Disto {
pub name: String,
pub version_id: String,
}
impl Disto {
fn new() -> Self {
let name = run_cmds("awk -F'=' '/^NAME=/ {print $2}' /etc/os-release".to_owned())
.unwrap_or_default()
.trim()
.trim_matches('"')
.to_string();
let version_id =
run_cmds("awk -F'=' '/^VERSION_ID=/ {print $2}' /etc/os-release".to_owned())
.unwrap_or_default()
.trim()
.trim_matches('"')
.to_string();
Self { name, version_id }
}
}
pub fn get_display_server() -> String { pub fn get_display_server() -> String {
let session = get_value_of_seat0(0); let mut session = get_values_of_seat0([0].to_vec())[0].clone();
if session.is_empty() {
// loginctl has not given the expected output. try something else.
if let Ok(sid) = std::env::var("XDG_SESSION_ID") {
// could also execute "cat /proc/self/sessionid"
session = sid.to_owned();
}
if session.is_empty() {
session = run_cmds("cat /proc/self/sessionid".to_owned()).unwrap_or_default();
}
}
get_display_server_of_session(&session) get_display_server_of_session(&session)
} }
@ -51,15 +88,16 @@ fn get_display_server_of_session(session: &str) -> String {
} }
} }
pub fn get_value_of_seat0(i: usize) -> String { pub fn get_values_of_seat0(indices: Vec<usize>) -> Vec<String> {
if let Ok(output) = run_loginctl(None) { if let Ok(output) = run_loginctl(None) {
for line in String::from_utf8_lossy(&output.stdout).lines() { for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.contains("seat0") { if line.contains("seat0") {
if let Some(sid) = line.split_whitespace().nth(0) { if let Some(sid) = line.split_whitespace().nth(0) {
if is_active(sid) { if is_active(sid) {
if let Some(uid) = line.split_whitespace().nth(i) { return indices
return uid.to_owned(); .into_iter()
} .map(|idx| line.split_whitespace().nth(idx).unwrap_or("").to_owned())
.collect::<Vec<String>>();
} }
} }
} }
@ -72,20 +110,19 @@ pub fn get_value_of_seat0(i: usize) -> String {
if let Some(sid) = line.split_whitespace().nth(0) { if let Some(sid) = line.split_whitespace().nth(0) {
let d = get_display_server_of_session(sid); let d = get_display_server_of_session(sid);
if is_active(sid) && d != "tty" { if is_active(sid) && d != "tty" {
if let Some(uid) = line.split_whitespace().nth(i) { return indices
return uid.to_owned(); .into_iter()
} .map(|idx| line.split_whitespace().nth(idx).unwrap_or("").to_owned())
.collect::<Vec<String>>();
} }
} }
} }
} }
// loginctl has not given the expected output. try something else. return indices
if let Ok(sid) = std::env::var("XDG_SESSION_ID") { // could also execute "cat /proc/self/sessionid" .iter()
return sid.to_owned(); .map(|_x| "".to_owned())
} .collect::<Vec<String>>();
return "".to_owned();
} }
fn is_active(sid: &str) -> bool { fn is_active(sid: &str) -> bool {

View File

@ -12,6 +12,7 @@ cfg_if! {
mod x11; mod x11;
pub use self::linux::*; pub use self::linux::*;
pub use self::x11::Frame; pub use self::x11::Frame;
pub use self::wayland::set_map_err;
} else { } else {
mod x11; mod x11;
pub use self::x11::*; pub use self::x11::*;

View File

@ -1,12 +1,24 @@
use crate::common::{x11::Frame, TraitCapturer}; use crate::common::{x11::Frame, TraitCapturer};
use crate::wayland::{capturable::*, *}; use crate::wayland::{capturable::*, *};
use std::{io, time::Duration}; use std::{io, sync::RwLock, time::Duration};
pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>); pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>);
lazy_static::lazy_static! {
static ref MAP_ERR: RwLock<Option<fn(err: String)-> io::Error>> = Default::default();
}
pub fn set_map_err(f: fn(err: String) -> io::Error) {
*MAP_ERR.write().unwrap() = Some(f);
}
fn map_err<E: ToString>(err: E) -> io::Error { fn map_err<E: ToString>(err: E) -> io::Error {
if let Some(f) = *MAP_ERR.read().unwrap() {
f(err.to_string())
} else {
io::Error::new(io::ErrorKind::Other, err.to_string()) io::Error::new(io::ErrorKind::Other, err.to_string())
} }
}
impl Capturer { impl Capturer {
pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> { pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> {

View File

@ -19,14 +19,5 @@ if [ "$1" = configure ]; then
systemctl daemon-reload systemctl daemon-reload
systemctl enable rustdesk systemctl enable rustdesk
systemctl start rustdesk systemctl start rustdesk
cp /usr/share/rustdesk/files/systemd/rustdesk.service.user /usr/lib/systemd/user/rustdesk.service
ubuntuVersion=$(grep -oP 'VERSION_ID="\K[\d]+' /etc/os-release | bc -l)
waylandSupportVersion=21
if [ "$ubuntuVersion" -ge "$waylandSupportVersion" ]
then
curUser=$(who | awk '{print $1}' | head -1)
systemctl --machine=${curUser}@.host --user daemon-reload
fi
fi fi
fi fi

View File

@ -7,13 +7,6 @@ case $1 in
INITSYS=$(ls -al /proc/1/exe | awk -F' ' '{print $NF}' | awk -F'/' '{print $NF}') INITSYS=$(ls -al /proc/1/exe | awk -F' ' '{print $NF}' | awk -F'/' '{print $NF}')
if [ "systemd" == "${INITSYS}" ]; then if [ "systemd" == "${INITSYS}" ]; then
service rustdesk stop || true service rustdesk stop || true
serverUser=$(ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1)
if [ "$serverUser" != "" ] && [ "$serverUser" != "root" ]
then
systemctl --machine=${serverUser}@.host --user stop rustdesk || true
fi
sleep 1 sleep 1
rm -rf /usr/bin/libsciter-gtk.so rm -rf /usr/bin/libsciter-gtk.so
fi fi

View File

@ -11,7 +11,9 @@ case $1 in
systemctl stop rustdesk || true systemctl stop rustdesk || true
systemctl disable rustdesk || true systemctl disable rustdesk || true
rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service || true
# workaround temp dev build between 1.1.9 and 1.2.0
serverUser=$(ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1) serverUser=$(ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1)
ubuntuVersion=$(grep -oP 'VERSION_ID="\K[\d]+' /etc/os-release | bc -l) ubuntuVersion=$(grep -oP 'VERSION_ID="\K[\d]+' /etc/os-release | bc -l)
waylandSupportVersion=21 waylandSupportVersion=21
@ -19,8 +21,7 @@ case $1 in
then then
systemctl --machine=${serverUser}@.host --user stop rustdesk || true systemctl --machine=${serverUser}@.host --user stop rustdesk || true
fi fi
rm /usr/lib/systemd/user/rustdesk.service >/dev/null 2>/dev/null || true
rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service /usr/lib/systemd/user/rustdesk.service || true
fi fi
;; ;;
esac esac

View File

@ -1,15 +0,0 @@
[Unit]
Description=RustDesk user service (--server)
[Service]
Type=simple
ExecStart=/usr/bin/rustdesk --server
PIDFile=/run/rustdesk.user.pid
KillMode=mixed
TimeoutStopSec=30
LimitNOFILE=100000
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target

View File

@ -48,7 +48,10 @@ pub use super::lang::*;
pub mod file_trait; pub mod file_trait;
pub mod helper; pub mod helper;
pub mod io_loop; pub mod io_loop;
use crate::ui_session_interface::global_save_keyboard_mode; use crate::{
server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED},
ui_session_interface::global_save_keyboard_mode,
};
pub static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true);
pub static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true);
pub static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true);
@ -1263,10 +1266,14 @@ impl LoginConfigHandler {
pub fn handle_login_error(&mut self, err: &str, interface: &impl Interface) -> bool { pub fn handle_login_error(&mut self, err: &str, interface: &impl Interface) -> bool {
if err == "Wrong Password" { if err == "Wrong Password" {
self.password = Default::default(); self.password = Default::default();
interface.msgbox("re-input-password", err, "Do you want to enter again?"); interface.msgbox("re-input-password", err, "Do you want to enter again?", "");
true true
} else { } else {
interface.msgbox("error", "Login Error", err); if err.contains(SCRAP_X11_REQUIRED) {
interface.msgbox("error", "Login Error", err, SCRAP_X11_REF_URL);
} else {
interface.msgbox("error", "Login Error", err, "");
}
false false
} }
} }
@ -1636,7 +1643,7 @@ pub async fn handle_hash(
if password.is_empty() { if password.is_empty() {
// login without password, the remote side can click accept // login without password, the remote side can click accept
send_login(lc.clone(), Vec::new(), peer).await; send_login(lc.clone(), Vec::new(), peer).await;
interface.msgbox("input-password", "Password Required", ""); interface.msgbox("input-password", "Password Required", "", "");
} else { } else {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(&password); hasher.update(&password);
@ -1689,7 +1696,7 @@ pub async fn handle_login_from_ui(
pub trait Interface: Send + Clone + 'static + Sized { pub trait Interface: Send + Clone + 'static + Sized {
/// Send message data to remote peer. /// Send message data to remote peer.
fn send(&self, data: Data); fn send(&self, data: Data);
fn msgbox(&self, msgtype: &str, title: &str, text: &str); fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str);
fn handle_login_error(&mut self, err: &str) -> bool; fn handle_login_error(&mut self, err: &str) -> bool;
fn handle_peer_info(&mut self, pi: PeerInfo); fn handle_peer_info(&mut self, pi: PeerInfo);
fn set_force_relay(&mut self, direct: bool, received: bool); fn set_force_relay(&mut self, direct: bool, received: bool);
@ -1697,7 +1704,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
fn is_port_forward(&self) -> bool; fn is_port_forward(&self) -> bool;
fn is_rdp(&self) -> bool; fn is_rdp(&self) -> bool;
fn on_error(&self, err: &str) { fn on_error(&self, err: &str) {
self.msgbox("error", "Error", err); self.msgbox("error", "Error", err, "");
} }
fn is_force_relay(&self) -> bool; fn is_force_relay(&self) -> bool;
async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream); async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream);

View File

@ -119,7 +119,7 @@ impl<T: InvokeUiSession> Remote<T> {
Err(err) => { Err(err) => {
log::error!("Connection closed: {}", err); log::error!("Connection closed: {}", err);
self.handler.set_force_relay(direct, received); self.handler.set_force_relay(direct, received);
self.handler.msgbox("error", "Connection Error", &err.to_string()); self.handler.msgbox("error", "Connection Error", &err.to_string(), "");
break; break;
} }
Ok(ref bytes) => { Ok(ref bytes) => {
@ -134,10 +134,10 @@ impl<T: InvokeUiSession> Remote<T> {
} else { } else {
if self.handler.is_restarting_remote_device() { if self.handler.is_restarting_remote_device() {
log::info!("Restart remote device"); log::info!("Restart remote device");
self.handler.msgbox("restarting", "Restarting Remote Device", "remote_restarting_tip"); self.handler.msgbox("restarting", "Restarting Remote Device", "remote_restarting_tip", "");
} else { } else {
log::info!("Reset by the peer"); log::info!("Reset by the peer");
self.handler.msgbox("error", "Connection Error", "Reset by the peer"); self.handler.msgbox("error", "Connection Error", "Reset by the peer", "");
} }
break; break;
} }
@ -162,12 +162,12 @@ impl<T: InvokeUiSession> Remote<T> {
} }
_ = self.timer.tick() => { _ = self.timer.tick() => {
if last_recv_time.elapsed() >= SEC30 { if last_recv_time.elapsed() >= SEC30 {
self.handler.msgbox("error", "Connection Error", "Timeout"); self.handler.msgbox("error", "Connection Error", "Timeout", "");
break; break;
} }
if !self.read_jobs.is_empty() { if !self.read_jobs.is_empty() {
if let Err(err) = fs::handle_read_jobs(&mut self.read_jobs, &mut peer).await { if let Err(err) = fs::handle_read_jobs(&mut self.read_jobs, &mut peer).await {
self.handler.msgbox("error", "Connection Error", &err.to_string()); self.handler.msgbox("error", "Connection Error", &err.to_string(), "");
break; break;
} }
self.update_jobs_status(); self.update_jobs_status();
@ -191,7 +191,7 @@ impl<T: InvokeUiSession> Remote<T> {
} }
Err(err) => { Err(err) => {
self.handler self.handler
.msgbox("error", "Connection Error", &err.to_string()); .msgbox("error", "Connection Error", &err.to_string(), "");
} }
} }
if let Some(stop) = stop_clipboard { if let Some(stop) = stop_clipboard {
@ -971,7 +971,7 @@ impl<T: InvokeUiSession> Remote<T> {
} }
} }
Some(misc::Union::CloseReason(c)) => { Some(misc::Union::CloseReason(c)) => {
self.handler.msgbox("error", "Connection Error", &c); self.handler.msgbox("error", "Connection Error", &c, "");
return false; return false;
} }
Some(misc::Union::BackNotification(notification)) => { Some(misc::Union::BackNotification(notification)) => {
@ -981,8 +981,12 @@ impl<T: InvokeUiSession> Remote<T> {
} }
Some(misc::Union::Uac(uac)) => { Some(misc::Union::Uac(uac)) => {
if uac { if uac {
self.handler self.handler.msgbox(
.msgbox("custom-uac-nocancel", "Warning", "uac_warning"); "custom-uac-nocancel",
"Warning",
"uac_warning",
"",
);
} }
} }
Some(misc::Union::ForegroundWindowElevated(elevated)) => { Some(misc::Union::ForegroundWindowElevated(elevated)) => {
@ -991,6 +995,7 @@ impl<T: InvokeUiSession> Remote<T> {
"custom-elevated-foreground-nocancel", "custom-elevated-foreground-nocancel",
"Warning", "Warning",
"elevated_foreground_window_warning", "elevated_foreground_window_warning",
"",
); );
} }
} }
@ -1012,6 +1017,19 @@ impl<T: InvokeUiSession> Remote<T> {
} }
_ => {} _ => {}
}, },
Some(message::Union::MessageBox(msgbox)) => {
let mut link = msgbox.link;
if !link.starts_with("rustdesk://") {
if let Some(v) = hbb_common::config::HELPER_URL.get(&link as &str) {
link = v.to_string();
} else {
log::warn!("Message box ignore link {} for security", &link);
link = "".to_string();
}
}
self.handler
.msgbox(&msgbox.msgtype, &msgbox.title, &msgbox.text, &link);
}
_ => {} _ => {}
} }
} }
@ -1053,7 +1071,7 @@ impl<T: InvokeUiSession> Remote<T> {
} }
back_notification::BlockInputState::BlkOnFailed => { back_notification::BlockInputState::BlkOnFailed => {
self.handler self.handler
.msgbox("custom-error", "Block user input", "Failed"); .msgbox("custom-error", "Block user input", "Failed", "");
self.update_block_input_state(false); self.update_block_input_state(false);
} }
back_notification::BlockInputState::BlkOffSucceeded => { back_notification::BlockInputState::BlkOffSucceeded => {
@ -1061,7 +1079,7 @@ impl<T: InvokeUiSession> Remote<T> {
} }
back_notification::BlockInputState::BlkOffFailed => { back_notification::BlockInputState::BlkOffFailed => {
self.handler self.handler
.msgbox("custom-error", "Unblock user input", "Failed"); .msgbox("custom-error", "Unblock user input", "Failed", "");
} }
_ => {} _ => {}
} }
@ -1086,51 +1104,52 @@ impl<T: InvokeUiSession> Remote<T> {
"error", "error",
"Connecting...", "Connecting...",
"Someone turns on privacy mode, exit", "Someone turns on privacy mode, exit",
"",
); );
return false; return false;
} }
back_notification::PrivacyModeState::PrvNotSupported => { back_notification::PrivacyModeState::PrvNotSupported => {
self.handler self.handler
.msgbox("custom-error", "Privacy mode", "Unsupported"); .msgbox("custom-error", "Privacy mode", "Unsupported", "");
self.update_privacy_mode(false); self.update_privacy_mode(false);
} }
back_notification::PrivacyModeState::PrvOnSucceeded => { back_notification::PrivacyModeState::PrvOnSucceeded => {
self.handler self.handler
.msgbox("custom-nocancel", "Privacy mode", "In privacy mode"); .msgbox("custom-nocancel", "Privacy mode", "In privacy mode", "");
self.update_privacy_mode(true); self.update_privacy_mode(true);
} }
back_notification::PrivacyModeState::PrvOnFailedDenied => { back_notification::PrivacyModeState::PrvOnFailedDenied => {
self.handler self.handler
.msgbox("custom-error", "Privacy mode", "Peer denied"); .msgbox("custom-error", "Privacy mode", "Peer denied", "");
self.update_privacy_mode(false); self.update_privacy_mode(false);
} }
back_notification::PrivacyModeState::PrvOnFailedPlugin => { back_notification::PrivacyModeState::PrvOnFailedPlugin => {
self.handler self.handler
.msgbox("custom-error", "Privacy mode", "Please install plugins"); .msgbox("custom-error", "Privacy mode", "Please install plugins", "");
self.update_privacy_mode(false); self.update_privacy_mode(false);
} }
back_notification::PrivacyModeState::PrvOnFailed => { back_notification::PrivacyModeState::PrvOnFailed => {
self.handler self.handler
.msgbox("custom-error", "Privacy mode", "Failed"); .msgbox("custom-error", "Privacy mode", "Failed", "");
self.update_privacy_mode(false); self.update_privacy_mode(false);
} }
back_notification::PrivacyModeState::PrvOffSucceeded => { back_notification::PrivacyModeState::PrvOffSucceeded => {
self.handler self.handler
.msgbox("custom-nocancel", "Privacy mode", "Out privacy mode"); .msgbox("custom-nocancel", "Privacy mode", "Out privacy mode", "");
self.update_privacy_mode(false); self.update_privacy_mode(false);
} }
back_notification::PrivacyModeState::PrvOffByPeer => { back_notification::PrivacyModeState::PrvOffByPeer => {
self.handler self.handler
.msgbox("custom-error", "Privacy mode", "Peer exit"); .msgbox("custom-error", "Privacy mode", "Peer exit", "");
self.update_privacy_mode(false); self.update_privacy_mode(false);
} }
back_notification::PrivacyModeState::PrvOffFailed => { back_notification::PrivacyModeState::PrvOffFailed => {
self.handler self.handler
.msgbox("custom-error", "Privacy mode", "Failed to turn off"); .msgbox("custom-error", "Privacy mode", "Failed to turn off", "");
} }
back_notification::PrivacyModeState::PrvOffUnknown => { back_notification::PrivacyModeState::PrvOffUnknown => {
self.handler self.handler
.msgbox("custom-error", "Privacy mode", "Turned off"); .msgbox("custom-error", "Privacy mode", "Turned off", "");
// log::error!("Privacy mode is turned off with unknown reason"); // log::error!("Privacy mode is turned off with unknown reason");
self.update_privacy_mode(false); self.update_privacy_mode(false);
} }

View File

@ -1,4 +1,8 @@
use std::sync::{Arc, Mutex}; use std::{
collections::HashMap,
future::Future,
sync::{Arc, Mutex},
};
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use arboard::Clipboard as ClipboardContext; pub use arboard::Clipboard as ClipboardContext;
@ -18,6 +22,8 @@ use hbb_common::{
// #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))] // #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all}; use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future<Output = ()>;
pub const CLIPBOARD_NAME: &'static str = "clipboard"; pub const CLIPBOARD_NAME: &'static str = "clipboard";
pub const CLIPBOARD_INTERVAL: u64 = 333; pub const CLIPBOARD_INTERVAL: u64 = 333;
@ -31,6 +37,18 @@ lazy_static::lazy_static! {
pub static ref DEVICE_NAME: Arc<Mutex<String>> = Default::default(); pub static ref DEVICE_NAME: Arc<Mutex<String>> = Default::default();
} }
pub fn global_init() -> bool {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
crate::server::wayland::set_wayland_scrap_map_err();
}
}
true
}
pub fn global_clean() {}
#[inline] #[inline]
pub fn valid_for_numlock(evt: &KeyEvent) -> bool { pub fn valid_for_numlock(evt: &KeyEvent) -> bool {
if let Some(key_event::Union::ControlKey(ck)) = evt.union { if let Some(key_event::Union::ControlKey(ck)) = evt.union {

View File

@ -310,7 +310,7 @@ impl InvokeUiSession for FlutterHandler {
); );
} }
fn msgbox(&self, msgtype: &str, title: &str, text: &str, retry: bool) { fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
let has_retry = if retry { "true" } else { "" }; let has_retry = if retry { "true" } else { "" };
self.push_event( self.push_event(
"msgbox", "msgbox",
@ -318,6 +318,7 @@ impl InvokeUiSession for FlutterHandler {
("type", msgtype), ("type", msgtype),
("title", title), ("title", title),
("text", text), ("text", text),
("link", link),
("hasRetry", has_retry), ("hasRetry", has_retry),
], ],
); );

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", "自定义"), ("Custom", "自定义"),
("Full Access", "完全访问"), ("Full Access", "完全访问"),
("Screen Share", "仅共享屏幕"), ("Screen Share", "仅共享屏幕"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland 需要 Ubuntu 21.04 或更高版本。"),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更高版本的 linux 发行版。 请尝试 X11 桌面或更改您的操作系统。"),
("JumpLink", "查看"),
("Please Select the screen to be shared(Operate on the peer side).", "请选择要分享的画面(对端操作)。"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland vyžaduje Ubuntu 21.04 nebo vyšší verzi."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vyžaduje vyšší verzi linuxové distribuce. Zkuste prosím X11 desktop nebo změňte OS."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Vyberte prosím obrazovku, kterou chcete sdílet (Ovládejte na straně protějšku)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland kræver Ubuntu 21.04 eller nyere version."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland kræver en højere version af linux distro. Prøv venligst X11 desktop eller skift dit OS."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Vælg venligst den skærm, der skal deles (Betjen på peer-siden)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland erfordert Ubuntu 21.04 oder eine höhere Version."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland erfordert eine höhere Version der Linux-Distribution. Bitte versuchen Sie den X11-Desktop oder ändern Sie Ihr Betriebssystem."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den Bildschirm aus, der freigegeben werden soll (auf der Peer-Seite arbeiten)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -33,5 +33,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevation_prompt", "Running software without privilege elevation may cause problems when remote users operate certain windows."), ("elevation_prompt", "Running software without privilege elevation may cause problems when remote users operate certain windows."),
("uac_warning", "Temporarily denied access due to elevation request, please wait for the remote user to accept the UAC dialog. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."), ("uac_warning", "Temporarily denied access due to elevation request, please wait for the remote user to accept the UAC dialog. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."),
("elevated_foreground_window_warning", "Temporarily unable to use the mouse and keyboard, because the current window of the remote desktop requires higher privilege to operate, you can request the remote user to minimize the current window. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."), ("elevated_foreground_window_warning", "Temporarily unable to use the mouse and keyboard, because the current window of the remote desktop requires higher privilege to operate, you can request the remote user to minimize the current window. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."),
("JumpLink", "View"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland postulas Ubuntu 21.04 aŭ pli altan version."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland postulas pli altan version de linuksa distro. Bonvolu provi X11-labortablon aŭ ŝanĝi vian OS."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Bonvolu Elekti la ekranon por esti dividita (Funkciu ĉe la sama flanko)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland requiere Ubuntu 21.04 o una versión superior."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland requiere una versión superior de la distribución de Linux. Pruebe el escritorio X11 o cambie su sistema operativo."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Seleccione la pantalla que se compartirá (Operar en el lado del compañero)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland nécessite Ubuntu 21.04 ou une version supérieure."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland nécessite une version supérieure de la distribution Linux. Veuillez essayer le bureau X11 ou changer votre système d'exploitation."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (opérer du côté pair)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "A Waylandhoz Ubuntu 21.04 vagy újabb verzió szükséges."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "A Wayland a Linux disztró magasabb verzióját igényli. Próbálja ki az X11 desktopot, vagy változtassa meg az operációs rendszert."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Kérjük, válassza ki a megosztani kívánt képernyőt (a társoldalon működjön)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland membutuhkan Ubuntu 21.04 atau versi yang lebih tinggi."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland membutuhkan versi distro linux yang lebih tinggi. Silakan coba desktop X11 atau ubah OS Anda."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Silakan Pilih layar yang akan dibagikan (Operasi di sisi rekan)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland richiede Ubuntu 21.04 o versione successiva."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland richiede una versione superiore della distribuzione Linux. Prova X11 desktop o cambia il tuo sistema operativo."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato peer)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland には、Ubuntu 21.04 以降のバージョンが必要です。"),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland には、より高いバージョンの Linux ディストリビューションが必要です。 X11 デスクトップを試すか、OS を変更してください。"),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "共有する画面を選択してください(ピア側で操作)。"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland는 Ubuntu 21.04 이상 버전이 필요합니다."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland에는 더 높은 버전의 Linux 배포판이 필요합니다. X11 데스크탑을 시도하거나 OS를 변경하십시오."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "공유할 화면을 선택하십시오(피어 측에서 작동)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland Ubuntu 21.04 немесе одан жоғары нұсқасын қажет етеді."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland linux дистрибутивінің жоғарырақ нұсқасын қажет етеді. X11 жұмыс үстелін қолданып көріңіз немесе операциялық жүйеңізді өзгертіңіз."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Бөлісетін экранды таңдаңыз (бірдей жағынан жұмыс жасаңыз)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland wymaga Ubuntu 21.04 lub nowszego."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland wymaga wyższej wersji dystrybucji Linuksa. Wypróbuj pulpit X11 lub zmień system operacyjny."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Wybierz ekran do udostępnienia (działaj po stronie równorzędnej)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland requer Ubuntu 21.04 ou versão superior."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland requer uma versão superior da distribuição linux. Por favor, tente o desktop X11 ou mude seu sistema operacional."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Por favor, selecione a tela a ser compartilhada (operar no lado do peer)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", ""),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", ""),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland требует Ubuntu 21.04 или более позднюю версию."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Для Wayland требуется более поздняя версия дистрибутива Linux. Пожалуйста, попробуйте рабочий стол X11 или смените ОС."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Пожалуйста, выберите экран для совместного использования (работайте на одноранговой стороне)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland vyžaduje Ubuntu 21.04 alebo vyššiu verziu."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vyžaduje vyššiu verziu linuxovej distribúcie. Skúste X11 desktop alebo zmeňte OS."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Vyberte obrazovku, ktorú chcete zdieľať (Ovládajte na strane partnera)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", ""),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", ""),
("JumpLink", ""),
("Please Select the screen to be shared(Operate on the peer side).", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland, Ubuntu 21.04 veya daha yüksek bir sürüm gerektirir."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland, linux dağıtımının daha yüksek bir sürümünü gerektirir. Lütfen X11 masaüstünü deneyin veya işletim sisteminizi değiştirin."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Lütfen paylaşılacak ekranı seçiniz (Ekran tarafında çalıştırın)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", "自定義"), ("Custom", "自定義"),
("Full Access", "完全訪問"), ("Full Access", "完全訪問"),
("Screen Share", "僅共享屏幕"), ("Screen Share", "僅共享屏幕"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland 需要 Ubuntu 21.04 或更高版本。"),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更高版本的 linux 發行版。 請嘗試 X11 桌面或更改您的操作系統。"),
("JumpLink", "查看"),
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的畫面(在對端操作)。"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland потребує Ubuntu 21.04 або новішої версії."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Для Wayland потрібна новіша версія дистрибутива Linux. Будь ласка, спробуйте робочий стіл X11 або змініть свою ОС."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Будь ласка, виберіть екран, до якого потрібно надати доступ (працюйте на стороні однорангового пристрою)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -377,5 +377,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", ""), ("Custom", ""),
("Full Access", ""), ("Full Access", ""),
("Screen Share", ""), ("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland yêu cầu phiên bản Ubuntu 21.04 trở lên."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland yêu cầu phiên bản distro linux cao hơn. Vui lòng thử máy tính để bàn X11 hoặc thay đổi hệ điều hành của bạn."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Vui lòng Chọn màn hình để chia sẻ (Hoạt động ở phía ngang hàng)."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -6,21 +6,32 @@ use librustdesk::*;
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
fn main() { fn main() {
if !common::global_init() {
return;
}
common::test_rendezvous_server(); common::test_rendezvous_server();
common::test_nat_type(); common::test_nat_type();
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
crate::common::check_software_update(); crate::common::check_software_update();
common::global_clean();
} }
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))] #[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
fn main() { fn main() {
if !common::global_init() {
return;
}
if let Some(args) = crate::core_main::core_main().as_mut() { if let Some(args) = crate::core_main::core_main().as_mut() {
ui::start(args); ui::start(args);
} }
common::global_clean();
} }
#[cfg(feature = "cli")] #[cfg(feature = "cli")]
fn main() { fn main() {
if !common::global_init() {
return;
}
use hbb_common::log; use hbb_common::log;
use clap::App; use clap::App;
let args = format!( let args = format!(
@ -64,4 +75,5 @@ fn main() {
let token = LocalConfig::get_option("access_token"); let token = LocalConfig::get_option("access_token");
cli::start_one_port_forward(options[0].clone(), port, remote_host, remote_port, key, token); cli::start_one_port_forward(options[0].clone(), port, remote_host, remote_port, key, token);
} }
common::global_clean();
} }

View File

@ -161,45 +161,6 @@ fn start_uinput_service() {
}); });
} }
fn try_start_user_service(username: &str) {
if username == "" || username == "root" {
return;
}
if let Ok(mut cur_username) =
run_cmds("ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1".to_owned())
{
cur_username = cur_username.trim().to_owned();
if cur_username != "root" && cur_username != username {
let _ = run_cmds(format!(
"systemctl --machine={}@.host --user stop rustdesk",
&cur_username
));
} else if cur_username == username {
return;
}
}
let _ = run_cmds(format!(
"systemctl --machine={}@.host --user start rustdesk",
username
));
}
fn try_stop_user_service() {
if let Ok(mut username) =
run_cmds("ps -ef | grep -E 'rustdesk +--server' | awk '{print $1}' | head -1".to_owned())
{
username = username.trim().to_owned();
if username != "root" {
let _ = run_cmds(format!(
"systemctl --machine={}@.host --user stop rustdesk",
&username
));
}
}
}
fn stop_server(server: &mut Option<std::process::Child>) { fn stop_server(server: &mut Option<std::process::Child>) {
if let Some(mut ps) = server.take() { if let Some(mut ps) = server.take() {
allow_err!(ps.kill()); allow_err!(ps.kill());
@ -214,38 +175,10 @@ fn stop_server(server: &mut Option<std::process::Child>) {
} }
} }
pub fn start_os_service() { fn set_x11_env(uid: &str) {
start_uinput_service();
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
let mut uid = "".to_owned();
let mut server: Option<std::process::Child> = None;
if let Err(err) = ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
}) {
println!("Failed to set Ctrl-C handler: {}", err);
}
let mut cm0 = false;
let mut last_restart = std::time::Instant::now();
while running.load(Ordering::SeqCst) {
let username = get_active_username();
let is_wayland = current_is_wayland();
if username == "root" || !is_wayland {
// try stop user service
try_stop_user_service();
// try start subprocess "--server"
let cm = get_cm();
let tmp = get_active_userid();
let mut start_new = false;
if tmp != uid && !tmp.is_empty() {
uid = tmp;
log::info!("uid of seat0: {}", uid); log::info!("uid of seat0: {}", uid);
let gdm = format!("/run/user/{}/gdm/Xauthority", uid); let gdm = format!("/run/user/{}/gdm/Xauthority", uid);
let mut auth = get_env_tries("XAUTHORITY", &uid, 10); let mut auth = get_env_tries("XAUTHORITY", uid, 10);
if auth.is_empty() { if auth.is_empty() {
auth = if std::path::Path::new(&gdm).exists() { auth = if std::path::Path::new(&gdm).exists() {
gdm gdm
@ -263,7 +196,7 @@ pub fn start_os_service() {
} }
}; };
} }
let mut d = get_env("DISPLAY", &uid); let mut d = get_env("DISPLAY", uid);
if d.is_empty() { if d.is_empty() {
d = get_display(); d = get_display();
} }
@ -275,13 +208,36 @@ pub fn start_os_service() {
log::info!("XAUTHORITY: {}", auth); log::info!("XAUTHORITY: {}", auth);
std::env::set_var("XAUTHORITY", auth); std::env::set_var("XAUTHORITY", auth);
std::env::set_var("DISPLAY", d); std::env::set_var("DISPLAY", d);
}
fn stop_rustdesk_servers() {
let _ = run_cmds(format!(
r##"ps -ef | grep -E 'rustdesk +--server' | awk '{{printf("kill -9 %d\n", $2)}}' | bash"##,
));
}
fn should_start_server(
try_x11: bool,
uid: &mut String,
cur_uid: String,
cm0: &mut bool,
last_restart: &mut std::time::Instant,
server: &mut Option<std::process::Child>,
) -> bool {
let cm = get_cm();
let mut start_new = false;
if cur_uid != *uid && !cur_uid.is_empty() {
*uid = cur_uid;
if try_x11 {
set_x11_env(&uid);
}
if let Some(ps) = server.as_mut() { if let Some(ps) = server.as_mut() {
allow_err!(ps.kill()); allow_err!(ps.kill());
std::thread::sleep(std::time::Duration::from_millis(30)); std::thread::sleep(std::time::Duration::from_millis(30));
last_restart = std::time::Instant::now(); *last_restart = std::time::Instant::now();
} }
} else if !cm } else if !cm
&& ((cm0 && last_restart.elapsed().as_secs() > 60) && ((*cm0 && last_restart.elapsed().as_secs() > 60)
|| last_restart.elapsed().as_secs() > 3600) || last_restart.elapsed().as_secs() > 3600)
{ {
// restart server if new connections all closed, or every one hour, // restart server if new connections all closed, or every one hour,
@ -290,14 +246,14 @@ pub fn start_os_service() {
if let Some(ps) = server.as_mut() { if let Some(ps) = server.as_mut() {
allow_err!(ps.kill()); allow_err!(ps.kill());
std::thread::sleep(std::time::Duration::from_millis(30)); std::thread::sleep(std::time::Duration::from_millis(30));
last_restart = std::time::Instant::now(); *last_restart = std::time::Instant::now();
log::info!("restart server"); log::info!("restart server");
} }
} }
if let Some(ps) = server.as_mut() { if let Some(ps) = server.as_mut() {
match ps.try_wait() { match ps.try_wait() {
Ok(Some(_)) => { Ok(Some(_)) => {
server = None; *server = None;
start_new = true; start_new = true;
} }
_ => {} _ => {}
@ -305,7 +261,46 @@ pub fn start_os_service() {
} else { } else {
start_new = true; start_new = true;
} }
if start_new { *cm0 = cm;
start_new
}
pub fn start_os_service() {
stop_rustdesk_servers();
start_uinput_service();
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
let mut uid = "".to_owned();
let mut server: Option<std::process::Child> = None;
let mut user_server: Option<std::process::Child> = None;
if let Err(err) = ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
}) {
println!("Failed to set Ctrl-C handler: {}", err);
}
let mut cm0 = false;
let mut last_restart = std::time::Instant::now();
while running.load(Ordering::SeqCst) {
let (cur_uid, cur_user) = get_active_user_id_name();
let is_wayland = current_is_wayland();
if cur_user == "root" || !is_wayland {
stop_server(&mut user_server);
// try start subprocess "--server"
if should_start_server(
true,
&mut uid,
cur_uid,
&mut cm0,
&mut last_restart,
&mut server,
) {
// to-do: stop_server(&mut user_server); may not stop child correctly
// stop_rustdesk_servers() is just a temp solution here.
stop_rustdesk_servers();
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
match crate::run_me(vec!["--server"]) { match crate::run_me(vec!["--server"]) {
Ok(ps) => server = Some(ps), Ok(ps) => server = Some(ps),
Err(err) => { Err(err) => {
@ -313,31 +308,55 @@ pub fn start_os_service() {
} }
} }
} }
cm0 = cm; } else if cur_user != "" {
} else if username != "" { if cur_user != "gdm" {
if username != "gdm" {
// try kill subprocess "--server" // try kill subprocess "--server"
stop_server(&mut server); stop_server(&mut server);
// try start user service // try start subprocess "--server"
try_start_user_service(&username); if should_start_server(
false,
&mut uid,
cur_uid.clone(),
&mut cm0,
&mut last_restart,
&mut user_server,
) {
stop_rustdesk_servers();
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
match run_as_user("--server", Some((cur_uid, cur_user))) {
Ok(ps) => user_server = ps,
Err(err) => {
log::error!("Failed to start server: {}", err);
}
}
}
} }
} else { } else {
try_stop_user_service(); stop_rustdesk_servers();
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
stop_server(&mut user_server);
stop_server(&mut server); stop_server(&mut server);
} }
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL)); std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
} }
try_stop_user_service(); if let Some(ps) = user_server.take().as_mut() {
allow_err!(ps.kill());
}
if let Some(ps) = server.take().as_mut() { if let Some(ps) = server.take().as_mut() {
allow_err!(ps.kill()); allow_err!(ps.kill());
} }
log::info!("Exit"); log::info!("Exit");
} }
pub fn get_active_user_id_name() -> (String, String) {
let vec_id_name = get_values_of_seat0([1, 2].to_vec());
(vec_id_name[0].clone(), vec_id_name[1].clone())
}
pub fn get_active_userid() -> String { pub fn get_active_userid() -> String {
get_value_of_seat0(1) get_values_of_seat0([1].to_vec())[0].clone()
} }
fn get_cm() -> bool { fn get_cm() -> bool {
@ -514,7 +533,7 @@ fn _get_display_manager() -> String {
} }
pub fn get_active_username() -> String { pub fn get_active_username() -> String {
get_value_of_seat0(2) get_values_of_seat0([2].to_vec())[0].clone()
} }
pub fn get_active_user_home() -> Option<PathBuf> { pub fn get_active_user_home() -> Option<PathBuf> {
@ -546,12 +565,17 @@ fn is_opensuse() -> bool {
false false
} }
pub fn run_as_user(arg: &str) -> ResultType<Option<std::process::Child>> { pub fn run_as_user(
let uid = get_active_userid(); arg: &str,
user: Option<(String, String)>,
) -> ResultType<Option<std::process::Child>> {
let (uid, username) = match user {
Some(id_name) => id_name,
None => get_active_user_id_name(),
};
let cmd = std::env::current_exe()?; let cmd = std::env::current_exe()?;
let xdg = &format!("XDG_RUNTIME_DIR=/run/user/{}", uid) as &str; let xdg = &format!("XDG_RUNTIME_DIR=/run/user/{}", uid) as &str;
let username = &get_active_username(); let mut args = vec![xdg, "-u", &username, cmd.to_str().unwrap_or(""), arg];
let mut args = vec![xdg, "-u", username, cmd.to_str().unwrap_or(""), arg];
// -E required for opensuse // -E required for opensuse
if is_opensuse() { if is_opensuse() {
args.insert(0, "-E"); args.insert(0, "-E");

View File

@ -75,13 +75,13 @@ pub async fn listen(
let interface = interface.clone(); let interface = interface.clone();
tokio::spawn(async move { tokio::spawn(async move {
if let Err(err) = run_forward(forward, stream).await { if let Err(err) = run_forward(forward, stream).await {
interface.msgbox("error", "Error", &err.to_string()); interface.msgbox("error", "Error", &err.to_string(), "");
} }
log::info!("connection from {:?} closed", addr); log::info!("connection from {:?} closed", addr);
}); });
} }
Err(err) => { Err(err) => {
interface.msgbox("error", "Error", &err.to_string()); interface.msgbox("error", "Error", &err.to_string(), "");
} }
_ => {} _ => {}
} }

View File

@ -27,7 +27,7 @@ cfg_if::cfg_if! {
if #[cfg(not(any(target_os = "android", target_os = "ios")))] { if #[cfg(not(any(target_os = "android", target_os = "ios")))] {
mod clipboard_service; mod clipboard_service;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
mod wayland; pub(crate) mod wayland;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub mod uinput; pub mod uinput;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]

View File

@ -666,7 +666,6 @@ impl Connection {
#[allow(unused_mut)] #[allow(unused_mut)]
let mut username = crate::platform::get_active_username(); let mut username = crate::platform::get_active_username();
let mut res = LoginResponse::new(); let mut res = LoginResponse::new();
let mut pi = PeerInfo { let mut pi = PeerInfo {
username: username.clone(), username: username.clone(),
conn_id: self.inner.id, conn_id: self.inner.id,
@ -743,9 +742,13 @@ impl Connection {
res.set_peer_info(pi); res.set_peer_info(pi);
} else { } else {
try_activate_screen(); try_activate_screen();
if let Some(msg_out) = super::video_service::is_inited_msg() {
self.send(msg_out).await;
}
match super::video_service::get_displays().await { match super::video_service::get_displays().await {
Err(err) => { Err(err) => {
res.set_error(format!("X11 error: {}", err)); res.set_error(format!("{}", err));
} }
Ok((current, displays)) => { Ok((current, displays)) => {
pi.displays = displays.into(); pi.displays = displays.into();
@ -1511,7 +1514,14 @@ async fn start_ipc(
if crate::platform::is_root() { if crate::platform::is_root() {
let mut res = Ok(None); let mut res = Ok(None);
for _ in 0..10 { for _ in 0..10 {
#[cfg(not(target_os = "linux"))]
{
res = crate::platform::run_as_user("--cm"); res = crate::platform::run_as_user("--cm");
}
#[cfg(target_os = "linux")]
{
res = crate::platform::run_as_user("--cm", None);
}
if res.is_ok() { if res.is_ok() {
break; break;
} }

View File

@ -190,7 +190,8 @@ pub async fn set_uinput() -> ResultType<()> {
let mouse = super::uinput::client::UInputMouse::new().await?; let mouse = super::uinput::client::UInputMouse::new().await?;
log::info!("UInput mouse created"); log::info!("UInput mouse created");
let mut en = ENIGO.lock().unwrap(); let xxx = ENIGO.lock();
let mut en = xxx.unwrap();
en.set_uinput_keyboard(Some(Box::new(keyboard))); en.set_uinput_keyboard(Some(Box::new(keyboard)));
en.set_uinput_mouse(Some(Box::new(mouse))); en.set_uinput_mouse(Some(Box::new(mouse)));
Ok(()) Ok(())

View File

@ -39,6 +39,12 @@ use std::{
#[cfg(windows)] #[cfg(windows)]
use virtual_display; use virtual_display;
pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version.";
pub const SCRAP_OTHER_VERSION_OR_X11_REQUIRED: &str =
"Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.";
pub const SCRAP_X11_REQUIRED: &str = "x11 expected";
pub const SCRAP_X11_REF_URL: &str = "https://rustdesk.com/docs/en/manual/linux/#x11-required";
pub const NAME: &'static str = "video"; pub const NAME: &'static str = "video";
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -379,6 +385,10 @@ fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)] #[cfg(windows)]
ensure_close_virtual_device()?; ensure_close_virtual_device()?;
// ensure_inited() is needed because release_resouce() may be called.
#[cfg(target_os = "linux")]
super::wayland::ensure_inited()?;
let mut c = get_capturer(true)?; let mut c = get_capturer(true)?;
let mut video_qos = VIDEO_QOS.lock().unwrap(); let mut video_qos = VIDEO_QOS.lock().unwrap();
@ -458,6 +468,8 @@ fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)] #[cfg(windows)]
start_uac_elevation_check(); start_uac_elevation_check();
let mut would_block_count = 0u32;
while sp.ok() { while sp.ok() {
#[cfg(windows)] #[cfg(windows)]
check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?; check_uac_switch(c.privacy_mode_id, c._captuerer_privacy_mode_id)?;
@ -547,8 +559,7 @@ fn run(sp: GenericService) -> ResultType<()> {
}; };
match res { match res {
Err(ref e) if e.kind() == WouldBlock => Err(ref e) if e.kind() == WouldBlock => {
{
#[cfg(windows)] #[cfg(windows)]
if try_gdi > 0 && !c.is_gdi() { if try_gdi > 0 && !c.is_gdi() {
if try_gdi > 3 { if try_gdi > 3 {
@ -558,6 +569,19 @@ fn run(sp: GenericService) -> ResultType<()> {
} }
try_gdi += 1; try_gdi += 1;
} }
would_block_count += 1;
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
if would_block_count >= 100 {
// For now, the user should choose and agree screen sharing agiain.
// to-do: Remember choice, attendless...
super::wayland::release_resouce();
bail!("Wayland capturer none 100 times, try restart captuere");
}
}
}
} }
Err(err) => { Err(err) => {
if check_display_changed(c.ndisplay, c.current, c.width, c.height) { if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
@ -575,7 +599,9 @@ fn run(sp: GenericService) -> ResultType<()> {
return Err(err.into()); return Err(err.into());
} }
_ => {} _ => {
would_block_count = 0;
}
} }
let mut fetched_conn_ids = HashSet::new(); let mut fetched_conn_ids = HashSet::new();
@ -727,6 +753,14 @@ pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
(*lock, displays) (*lock, displays)
} }
pub fn is_inited_msg() -> Option<Message> {
#[cfg(target_os = "linux")]
if !scrap::is_x11() {
return super::wayland::is_inited();
}
None
}
pub async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> { pub async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {

View File

@ -1,10 +1,63 @@
use super::*; use super::*;
use hbb_common::allow_err; use hbb_common::{allow_err, platform::linux::DISTRO};
use scrap::{Capturer, Display, Frame, TraitCapturer}; use scrap::{set_map_err, Capturer, Display, Frame, TraitCapturer};
use std::io::Result; use std::io;
use super::video_service::{
SCRAP_OTHER_VERSION_OR_X11_REQUIRED, SCRAP_UBUNTU_HIGHER_REQUIRED, SCRAP_X11_REQUIRED,
};
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref CAP_DISPLAY_INFO: RwLock<u64> = RwLock::new(0); static ref CAP_DISPLAY_INFO: RwLock<u64> = RwLock::new(0);
static ref LOG_SCRAP_COUNT: Mutex<u32> = Mutex::new(0);
}
pub fn set_wayland_scrap_map_err() {
set_map_err(map_err_scrap);
}
fn map_err_scrap(err: String) -> io::Error {
// REMOVE ME ===================================== uncomment to handle error
// // to-do: Handle error better, do not restart server
// if err.starts_with("Did not receive a reply") {
// log::error!("Fatal pipewire error, {}", &err);
// std::process::exit(-1);
// }
log::error!(
"REMOVE ME ===================================== wayland scrap error {}",
&err
);
if DISTRO.name.to_uppercase() == "Ubuntu".to_uppercase() {
if DISTRO.version_id < "21".to_owned() {
io::Error::new(io::ErrorKind::Other, SCRAP_UBUNTU_HIGHER_REQUIRED)
} else {
try_log(&err);
io::Error::new(io::ErrorKind::Other, err)
}
} else {
try_log(&err);
if err.contains("org.freedesktop.portal")
|| err.contains("pipewire")
|| err.contains("dbus")
{
io::Error::new(io::ErrorKind::Other, SCRAP_OTHER_VERSION_OR_X11_REQUIRED)
} else {
io::Error::new(io::ErrorKind::Other, SCRAP_X11_REQUIRED)
}
}
}
fn try_log(err: &String) {
let mut lock_count = LOG_SCRAP_COUNT.lock().unwrap();
if *lock_count >= 1000000 {
return;
}
if *lock_count % 10000 == 0 {
log::error!("Failed scrap {}", err);
}
*lock_count += 1;
} }
struct CapturerPtr(*mut Capturer); struct CapturerPtr(*mut Capturer);
@ -16,7 +69,7 @@ impl Clone for CapturerPtr {
} }
impl TraitCapturer for CapturerPtr { impl TraitCapturer for CapturerPtr {
fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>> { fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
unsafe { (*self.0).frame(timeout) } unsafe { (*self.0).frame(timeout) }
} }
@ -36,6 +89,32 @@ struct CapDisplayInfo {
capturer: CapturerPtr, capturer: CapturerPtr,
} }
#[tokio::main(flavor = "current_thread")]
pub(super) async fn ensure_inited() -> ResultType<()> {
check_init().await
}
pub(super) fn is_inited() -> Option<Message> {
if scrap::is_x11() {
None
} else {
if *CAP_DISPLAY_INFO.read().unwrap() == 0 {
let mut msg_out = Message::new();
let res = MessageBox {
msgtype: "nook-nocancel-hasclose".to_owned(),
title: "Wayland".to_owned(),
text: "Please Select the screen to be shared(Operate on the peer side).".to_owned(),
link: "".to_owned(),
..Default::default()
};
msg_out.set_message_box(res);
Some(msg_out)
} else {
None
}
}
}
async fn check_init() -> ResultType<()> { async fn check_init() -> ResultType<()> {
if !scrap::is_x11() { if !scrap::is_x11() {
let mut minx = 0; let mut minx = 0;
@ -162,6 +241,21 @@ pub(super) fn get_display_num() -> ResultType<usize> {
} }
} }
pub(super) fn release_resouce() {
if scrap::is_x11() {
return;
}
let mut write_lock = CAP_DISPLAY_INFO.write().unwrap();
if *write_lock != 0 {
let cap_display_info: *mut CapDisplayInfo = *write_lock as _;
unsafe {
let box_capturer = Box::from_raw((*cap_display_info).capturer.0);
let box_cap_display_info = Box::from_raw(cap_display_info);
*write_lock = 0;
}
}
}
pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> { pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> {
if scrap::is_x11() { if scrap::is_x11() {
bail!("Do not call this function if not wayland"); bail!("Do not call this function if not wayland");
@ -187,3 +281,14 @@ pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> {
bail!("Failed to get capturer display info"); bail!("Failed to get capturer display info");
} }
} }
pub fn common_get_error() -> String {
if DISTRO.name.to_uppercase() == "Ubuntu".to_uppercase() {
if DISTRO.version_id < "21".to_owned() {
return "".to_owned();
}
} else {
// to-do: check other distros
}
return "".to_owned();
}

View File

@ -76,10 +76,15 @@ class AddressBook: Reactor.Component
event click $(#add-id) (_, __) { event click $(#add-id) (_, __) {
var me = this; var me = this;
msgbox("custom-add-id", translate("Add ID"), <div .form> msgbox(
"custom-add-id",
translate("Add ID"),
<div .form>
<div>{translate("whitelist_sep")}</div> <div>{translate("whitelist_sep")}</div>
<textarea .outline-focus spellcheck="false" name="text" style="overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;"></textarea> <textarea .outline-focus spellcheck="false" name="text" style="overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;"></textarea>
</div>, function(res=null) { </div>,
"",
function(res=null) {
if (!res) return; if (!res) return;
var value = (res.text || "").trim(); var value = (res.text || "").trim();
var values = value.split(/[\s,;\n]+/g); var values = value.split(/[\s,;\n]+/g);
@ -97,7 +102,8 @@ class AddressBook: Reactor.Component
} }
updateAb(); updateAb();
me.update(); me.update();
}, 300); },
300);
} }
event click $(#add-tag) (_, __) { event click $(#add-tag) (_, __) {
@ -105,7 +111,7 @@ class AddressBook: Reactor.Component
msgbox("custom-add-tag", translate("Add Tag"), <div .form> msgbox("custom-add-tag", translate("Add Tag"), <div .form>
<div>{translate("whitelist_sep")}</div> <div>{translate("whitelist_sep")}</div>
<textarea .outline-focus spellcheck="false" name="text" style="overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;"></textarea> <textarea .outline-focus spellcheck="false" name="text" style="overflow: scroll-indicator; width:*; height: 160px; font-size: 1.2em; padding: 0.5em;"></textarea>
</div>, function(res=null) { </div>, "", function(res=null) {
if (!res) return; if (!res) return;
var value = (res.text || "").trim(); var value = (res.text || "").trim();
var values = value.split(/[\s,;\n]+/g); var values = value.split(/[\s,;\n]+/g);
@ -483,7 +489,7 @@ class SessionList: Reactor.Component {
msgbox("custom-rename", "Rename", "<div .form> \ msgbox("custom-rename", "Rename", "<div .form> \
<div><input|text name='name' .outline-focus style='width: *; height: 23px', value='" + old_name + "' /></div> \ <div><input|text name='name' .outline-focus style='width: *; height: 23px', value='" + old_name + "' /></div> \
</div> \ </div> \
", function(res=null) { ", "", function(res=null) {
if (!res) return; if (!res) return;
var name = (res.name || "").trim(); var name = (res.name || "").trim();
if (name != old_name) { if (name != old_name) {
@ -506,7 +512,7 @@ class SessionList: Reactor.Component {
} }
} }
if (!peer) return; if (!peer) return;
msgbox("custom-edit-tag", "Edit Tag", <SelectTags tags={peer.tags || []} />, function(res=null) { msgbox("custom-edit-tag", "Edit Tag", <SelectTags tags={peer.tags || []} />, "", function(res=null) {
if (!res) return; if (!res) return;
peer.tags = selectTags.tags; peer.tags = selectTags.tags;
updateAb(); updateAb();
@ -738,7 +744,7 @@ function editRdpPort() {
<div><span>{translate('Port')}:</span>{port}</div> <div><span>{translate('Port')}:</span>{port}</div>
<div><span>{translate('Username')}:</span><input|text name="username" value={name0} /></div> <div><span>{translate('Username')}:</span><input|text name="username" value={name0} /></div>
<div><span>{translate('Password')}:</span><PasswordComponent value={pass0} /></div> <div><span>{translate('Password')}:</span><PasswordComponent value={pass0} /></div>
</div>, function(res=null) { </div>, "", function(res=null) {
if (!res) return; if (!res) return;
var p = (res.port || '').trim(); var p = (res.port || '').trim();
if (p != p0) { if (p != p0) {

View File

@ -232,7 +232,7 @@ class ChatBox: Reactor.Component {
/******************** start of msgbox ****************************************/ /******************** start of msgbox ****************************************/
var remember_password = false; var remember_password = false;
function msgbox(type, title, content, callback=null, height=180, width=500, hasRetry=false, contentStyle="") { function msgbox(type, title, content, link="", callback=null, height=180, width=500, hasRetry=false, contentStyle="") {
$(body).scrollTo(0, 0); $(body).scrollTo(0, 0);
if (!type) { if (!type) {
closeMsgbox(); closeMsgbox();
@ -264,21 +264,21 @@ function msgbox(type, title, content, callback=null, height=180, width=500, hasR
} else if (type.indexOf("custom") < 0 && !is_port_forward && !callback) { } else if (type.indexOf("custom") < 0 && !is_port_forward && !callback) {
callback = function() { view.close(); } callback = function() { view.close(); }
} }
$(#msgbox).content(<MsgboxComponent width={width} height={height} auto_login={auto_login} type={type} title={title} content={content} remember={remember} callback={callback} contentStyle={contentStyle} hasRetry={hasRetry} />); $(#msgbox).content(<MsgboxComponent width={width} height={height} auto_login={auto_login} type={type} title={title} content={content} link={link} remember={remember} callback={callback} contentStyle={contentStyle} hasRetry={hasRetry} />);
} }
function connecting() { function connecting() {
handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait."); handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait.");
} }
handler.msgbox = function(type, title, text, hasRetry=false) { handler.msgbox = function(type, title, text, link = "", hasRetry=false) {
// crash somehow (when input wrong password), even with small time, for example, 1ms // crash somehow (when input wrong password), even with small time, for example, 1ms
self.timer(60ms, function() { msgbox(type, title, text, null, 180, 500, hasRetry); }); self.timer(60ms, function() { msgbox(type, title, text, link, null, 180, 500, hasRetry); });
} }
var reconnectTimeout = 1000; var reconnectTimeout = 1000;
handler.msgbox_retry = function(type, title, text, hasRetry) { handler.msgbox_retry = function(type, title, text, link, hasRetry) {
handler.msgbox(type, title, text, hasRetry); handler.msgbox(type, title, text, link, hasRetry);
if (hasRetry) { if (hasRetry) {
self.timer(0, retryConnect); self.timer(0, retryConnect);
self.timer(reconnectTimeout, retryConnect); self.timer(reconnectTimeout, retryConnect);

View File

@ -535,7 +535,7 @@ class FolderView : Reactor.Component {
msgbox("custom", translate("Create Folder"), "<div .form> \ msgbox("custom", translate("Create Folder"), "<div .form> \
<div>" + translate("Please enter the folder name") + ":</div> \ <div>" + translate("Please enter the folder name") + ":</div> \
<div><input|text(name) .outline-focus /></div> \ <div><input|text(name) .outline-focus /></div> \
</div>", function(res=null) { </div>", "", function(res=null) {
if (!res) return; if (!res) return;
if (!res.name) return; if (!res.name) return;
var name = res.name.trim(); var name = res.name.trim();
@ -716,7 +716,7 @@ function confirmDelete(id ,path, is_remote) {
msgbox("custom-skip", "Confirm Delete", "<div .form> \ msgbox("custom-skip", "Confirm Delete", "<div .form> \
<div>" + translate('Are you sure you want to delete this file?') + "</div> \ <div>" + translate('Are you sure you want to delete this file?') + "</div> \
<div.ellipsis style=\"font-weight: bold;\">" + path + "</div> \ <div.ellipsis style=\"font-weight: bold;\">" + path + "</div> \
</div>", function(res=null) { </div>", "", function(res=null) {
if (!res) { if (!res) {
file_transfer.job_table.updateJobStatus(id, -1, "cancel"); file_transfer.job_table.updateJobStatus(id, -1, "cancel");
file_transfer.job_table.cancelDeletePolling(); file_transfer.job_table.cancelDeletePolling();
@ -746,7 +746,7 @@ handler.confirmDeleteFiles = function(id, i, name) {
<div>" + translate('Are you sure you want to delete this file?') + "</div> \ <div>" + translate('Are you sure you want to delete this file?') + "</div> \
<div.ellipsis style=\"font-weight: bold;\" .text>" + file_path + "</div> \ <div.ellipsis style=\"font-weight: bold;\" .text>" + file_path + "</div> \
<div><button|checkbox(remember) {ts}>" + translate('Do this for all conflicts') + "</button></div> \ <div><button|checkbox(remember) {ts}>" + translate('Do this for all conflicts') + "</button></div> \
</div>", function(res=null) { </div>", "", function(res=null) {
if (!res) { if (!res) {
jt.updateJobStatus(id, i - 1, "cancel"); jt.updateJobStatus(id, i - 1, "cancel");
file_transfer.job_table.cancelDeletePolling(); file_transfer.job_table.cancelDeletePolling();
@ -778,7 +778,7 @@ handler.overrideFileConfirm = function(id, file_num, to, is_upload) {
<div>" + translate('This file exists, skip or overwrite this file?') + "</div> \ <div>" + translate('This file exists, skip or overwrite this file?') + "</div> \
<div.ellipsis style=\"font-weight: bold;\" .text>" + to + "</div> \ <div.ellipsis style=\"font-weight: bold;\" .text>" + to + "</div> \
<div><button|checkbox(remember) {ts}>" + translate('Do this for all conflicts') + "</button></div> \ <div><button|checkbox(remember) {ts}>" + translate('Do this for all conflicts') + "</button></div> \
</div>", function(res=null) { </div>", "", function(res=null) {
if (!res) { if (!res) {
jt.updateJobStatus(id, -1, "cancel"); jt.updateJobStatus(id, -1, "cancel");
handler.cancel_job(id); handler.cancel_job(id);

View File

@ -78,7 +78,7 @@ class EditOsPassword: Reactor.Component {
function editOSPassword(login=false) { function editOSPassword(login=false) {
var p0 = handler.get_option('os-password'); var p0 = handler.get_option('os-password');
msgbox("custom-os-password", 'OS Password', p0, function(res=null) { msgbox("custom-os-password", 'OS Password', p0, "", function(res=null) {
if (!res) return; if (!res) return;
var a0 = handler.get_option('auto-login') != ''; var a0 = handler.get_option('auto-login') != '';
var p = (res.password || '').trim(); var p = (res.password || '').trim();
@ -320,7 +320,7 @@ class Header: Reactor.Component {
var self = this; var self = this;
msgbox("custom", "Note", <div .form> msgbox("custom", "Note", <div .form>
<textarea .outline-focus spellcheck="false" name="text" novalue="input note here" style="overflow: scroll-indicator; width:*; height: 140px; font-size: 1.2em; padding: 0.5em;">{self.conn_note}</textarea> <textarea .outline-focus spellcheck="false" name="text" novalue="input note here" style="overflow: scroll-indicator; width:*; height: 140px; font-size: 1.2em; padding: 0.5em;">{self.conn_note}</textarea>
</div>, function(res=null) { </div>, "", function(res=null) {
if (!res) return; if (!res) return;
if (!res.text) return; if (!res.text) return;
self.conn_note = res.text; self.conn_note = res.text;
@ -333,9 +333,15 @@ class Header: Reactor.Component {
} }
event click $(#restart_remote_device) { event click $(#restart_remote_device) {
msgbox("restart-confirmation", translate("Restart Remote Device"), translate("Are you sure you want to restart") + " " + pi.username + "@" + pi.hostname + "(" + get_id() + ") ?", function(res=null) { msgbox(
"restart-confirmation",
translate("Restart Remote Device"),
translate("Are you sure you want to restart") + " " + pi.username + "@" + pi.hostname + "(" + get_id() + ") ?",
"",
function(res=null) {
if (res != null) handler.restart_remote_device(); if (res != null) handler.restart_remote_device();
}); }
);
} }
event click $(#lock-screen) { event click $(#lock-screen) {
@ -400,7 +406,7 @@ function handle_custom_image_quality() {
var bitrate = (tmp[0] || 50); var bitrate = (tmp[0] || 50);
msgbox("custom", "Custom Image Quality", "<div .form> \ msgbox("custom", "Custom Image Quality", "<div .form> \
<div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% Bitrate</div> \ <div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% Bitrate</div> \
</div>", function(res=null) { </div>", "", function(res=null) {
if (!res) return; if (!res) return;
if (!res.bitrate) return; if (!res.bitrate) return;
handler.save_custom_image_quality(res.bitrate); handler.save_custom_image_quality(res.bitrate);
@ -489,7 +495,7 @@ handler.updatePrivacyMode = updatePrivacyMode;
function togglePrivacyMode(privacy_id) { function togglePrivacyMode(privacy_id) {
var supported = handler.is_privacy_mode_supported(); var supported = handler.is_privacy_mode_supported();
if (!supported) { if (!supported) {
msgbox("nocancel", translate("Privacy mode"), translate("Unsupported"), function() { }); msgbox("nocancel", translate("Privacy mode"), translate("Unsupported"), "", function() { });
} else { } else {
handler.toggle_option(privacy_id); handler.toggle_option(privacy_id);
} }

View File

@ -247,7 +247,7 @@ class Enhancements: Reactor.Component {
<div> <button #select_directory .link>{translate('Change')}</button> </div> <div> <button #select_directory .link>{translate('Change')}</button> </div>
</div> </div>
</div> </div>
, function(res=null) { , "", function(res=null) {
if (!res) return; if (!res) return;
handler.set_option("enable-record-session", res.enable_record_session ? '' : 'N'); handler.set_option("enable-record-session", res.enable_record_session ? '' : 'N');
handler.set_option("allow-auto-record-incoming", res.auto_record_incoming ? 'Y' : ''); handler.set_option("allow-auto-record-incoming", res.auto_record_incoming ? 'Y' : '');
@ -369,7 +369,7 @@ class MyIdMenu: Reactor.Component {
<br />" + handler.get_license() + " \ <br />" + handler.get_license() + " \
<p style='font-weight: bold'>Made with heart in this chaotic world!</p>\ <p style='font-weight: bold'>Made with heart in this chaotic world!</p>\
</div>\ </div>\
</div>", function(el) { </div>", "", function(el) {
if (el && el.attributes) { if (el && el.attributes) {
handler.open_url(el.attributes['url']); handler.open_url(el.attributes['url']);
}; };
@ -389,7 +389,7 @@ class MyIdMenu: Reactor.Component {
<div>" + translate("whitelist_sep") + "</div> \ <div>" + translate("whitelist_sep") + "</div> \
<textarea .outline-focus spellcheck=\"false\" name=\"text\" novalue=\"0.0.0.0\" style=\"overflow: scroll-indicator; width:*; height: 140px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\ <textarea .outline-focus spellcheck=\"false\" name=\"text\" novalue=\"0.0.0.0\" style=\"overflow: scroll-indicator; width:*; height: 140px; font-size: 1.2em; padding: 0.5em;\">" + old_value + "</textarea>\
</div> \ </div> \
", function(res=null) { ", "", function(res=null) {
if (!res) return; if (!res) return;
var value = (res.text || "").trim(); var value = (res.text || "").trim();
if (value) { if (value) {
@ -417,7 +417,7 @@ class MyIdMenu: Reactor.Component {
<div><span>" + translate("API Server") + ": </span><input|text name='api' value='" + old_api + "' /></div> \ <div><span>" + translate("API Server") + ": </span><input|text name='api' value='" + old_api + "' /></div> \
<div><span>" + translate("Key") + ": </span><input|text name='key' value='" + old_key + "' /></div> \ <div><span>" + translate("Key") + ": </span><input|text name='key' value='" + old_key + "' /></div> \
</div> \ </div> \
", function(res=null) { ", "", function(res=null) {
if (!res) return; if (!res) return;
var id = (res.id || "").trim(); var id = (res.id || "").trim();
var relay = (res.relay || "").trim(); var relay = (res.relay || "").trim();
@ -453,7 +453,7 @@ class MyIdMenu: Reactor.Component {
<div><span>{translate("Username")}:</span><input|text name='username' value={old_username} /></div> <div><span>{translate("Username")}:</span><input|text name='username' value={old_username} /></div>
<div><span>{translate("Password")}:</span><PasswordComponent value={old_password} /></div> <div><span>{translate("Password")}:</span><PasswordComponent value={old_password} /></div>
</div> </div>
, function(res=null) { , "", function(res=null) {
if (!res) return; if (!res) return;
var proxy = (res.proxy || "").trim(); var proxy = (res.proxy || "").trim();
var username = (res.username || "").trim(); var username = (res.username || "").trim();
@ -474,7 +474,7 @@ class MyIdMenu: Reactor.Component {
<div>" + translate('id_change_tip') + " </div> \ <div>" + translate('id_change_tip') + " </div> \
<div><span style='width: 100px; display:inline-block'>ID: </span><input|text .outline-focus style='width: 250px' name='id' /></div> \ <div><span style='width: 100px; display:inline-block'>ID: </span><input|text .outline-focus style='width: 250px' name='id' /></div> \
</div> \ </div> \
", function(res=null, show_progress) { ", "", function(res=null, show_progress) {
if (!res) return; if (!res) return;
show_progress(); show_progress();
var id = (res.id || "").trim(); var id = (res.id || "").trim();
@ -520,7 +520,7 @@ function editDirectAccessPort() {
<input|text name='port' novalue={21118} />; <input|text name='port' novalue={21118} />;
msgbox("custom-direct-access-port", translate('Direct IP Access Settings'), <div .form .set-password> msgbox("custom-direct-access-port", translate('Direct IP Access Settings'), <div .form .set-password>
<div><span style="width: 60px;">{translate('Port')}:</span>{port}</div> <div><span style="width: 60px;">{translate('Port')}:</span>{port}</div>
</div>, function(res=null) { </div>, "", function(res=null) {
if (!res) return; if (!res) return;
var p = (res.port || '').trim(); var p = (res.port || '').trim();
if (p) { if (p) {
@ -934,7 +934,7 @@ class PasswordArea: Reactor.Component {
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus " + value_field + " /></div> \ <div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus " + value_field + " /></div> \
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) " + value_field + " /></div> \ <div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) " + value_field + " /></div> \
</div> \ </div> \
", function(res=null) { ", "", function(res=null) {
if (!res) return; if (!res) return;
var p0 = (res.password || "").trim(); var p0 = (res.password || "").trim();
var p1 = (res.confirmation || "").trim(); var p1 = (res.confirmation || "").trim();
@ -1161,7 +1161,7 @@ function login() {
msgbox("custom-login", translate('Login'), <div .form .set-password> msgbox("custom-login", translate('Login'), <div .form .set-password>
<div><span>{translate('Username')}:</span><input|text name="username" value={name0} .outline-focus /></div> <div><span>{translate('Username')}:</span><input|text name="username" value={name0} .outline-focus /></div>
<div><span>{translate('Password')}:</span><PasswordComponent value={pass0} /></div> <div><span>{translate('Password')}:</span><PasswordComponent value={pass0} /></div>
</div>, function(res=null, show_progress) { </div>, "", function(res=null, show_progress) {
if (!res) return; if (!res) return;
show_progress(); show_progress();
var name = (res.username || '').trim(); var name = (res.username || '').trim();

View File

@ -22,6 +22,7 @@ class MsgboxComponent: Reactor.Component {
this.type = params.type; this.type = params.type;
this.title = params.title; this.title = params.title;
this.content = params.content; this.content = params.content;
this.link = params.link;
this.remember = params.remember; this.remember = params.remember;
this.callback = params.callback; this.callback = params.callback;
this.hasRetry = params.hasRetry; this.hasRetry = params.hasRetry;
@ -93,6 +94,7 @@ class MsgboxComponent: Reactor.Component {
var content = this.getContent(); var content = this.getContent();
var hasCancel = this.type.indexOf("error") < 0 && this.type.indexOf("nocancel") < 0 && this.type != "restarting"; var hasCancel = this.type.indexOf("error") < 0 && this.type.indexOf("nocancel") < 0 && this.type != "restarting";
var hasOk = this.type != "connecting" && this.type != "success" && this.type.indexOf("nook") < 0; var hasOk = this.type != "connecting" && this.type != "success" && this.type.indexOf("nook") < 0;
var hasLink = this.link != "";
var hasClose = this.type.indexOf("hasclose") >= 0; var hasClose = this.type.indexOf("hasclose") >= 0;
var show_progress = this.type == "connecting"; var show_progress = this.type == "connecting";
var me = this; var me = this;
@ -121,6 +123,7 @@ class MsgboxComponent: Reactor.Component {
{hasCancel || this.hasRetry ? <button .button #cancel .outline>{translate(this.hasRetry ? "OK" : "Cancel")}</button> : ""} {hasCancel || this.hasRetry ? <button .button #cancel .outline>{translate(this.hasRetry ? "OK" : "Cancel")}</button> : ""}
{this.hasSkip() ? <button .button #skip .outline>{translate('Skip')}</button> : ""} {this.hasSkip() ? <button .button #skip .outline>{translate('Skip')}</button> : ""}
{hasOk || this.hasRetry ? <button .button #submit>{translate(this.hasRetry ? "Retry" : "OK")}</button> : ""} {hasOk || this.hasRetry ? <button .button #submit>{translate(this.hasRetry ? "Retry" : "OK")}</button> : ""}
{hasLink ? <button .button #jumplink .outline>{translate('JumpLink')}</button> : ""}
{hasClose ? <button .button #cancel .outline>{translate('Close')}</button> : ""} {hasClose ? <button .button #cancel .outline>{translate('Close')}</button> : ""}
</div> </div>
</div> </div>
@ -156,6 +159,12 @@ class MsgboxComponent: Reactor.Component {
if (this.close) this.close(); if (this.close) this.close();
} }
event click $(button#jumplink) {
if (this.link.indexOf("http") == 0) {
Sciter.launch(this.link);
}
}
event click $(button#submit) { event click $(button#submit) {
if (this.type == "error") { if (this.type == "error") {
if (this.hasRetry) { if (this.hasRetry) {

View File

@ -238,8 +238,8 @@ impl InvokeUiSession for SciterHandler {
self.call("updatePi", &make_args!(pi_sciter)); self.call("updatePi", &make_args!(pi_sciter));
} }
fn msgbox(&self, msgtype: &str, title: &str, text: &str, retry: bool) { fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
self.call2("msgbox_retry", &make_args!(msgtype, title, text, retry)); self.call2("msgbox_retry", &make_args!(msgtype, title, text, link, retry));
} }
fn new_message(&self, msg: String) { fn new_message(&self, msg: String) {

View File

@ -547,7 +547,7 @@ pub fn get_error() -> String {
{ {
let dtype = crate::platform::linux::get_display_server(); let dtype = crate::platform::linux::get_display_server();
if "wayland" == dtype { if "wayland" == dtype {
return "".to_owned(); return crate::server::wayland::common_get_error();
} }
if dtype != "x11" { if dtype != "x11" {
return format!( return format!(

View File

@ -1088,7 +1088,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64); fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64);
fn adapt_size(&self); fn adapt_size(&self);
fn on_rgba(&self, data: &[u8]); fn on_rgba(&self, data: &[u8]);
fn msgbox(&self, msgtype: &str, title: &str, text: &str, retry: bool); fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool);
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
fn clipboard(&self, content: String); fn clipboard(&self, content: String);
} }
@ -1137,9 +1137,9 @@ impl<T: InvokeUiSession> Interface for Session<T> {
self.lc.read().unwrap().conn_type.eq(&ConnType::RDP) self.lc.read().unwrap().conn_type.eq(&ConnType::RDP)
} }
fn msgbox(&self, msgtype: &str, title: &str, text: &str) { fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str) {
let retry = check_if_retry(msgtype, title, text); let retry = check_if_retry(msgtype, title, text);
self.ui_handler.msgbox(msgtype, title, text, retry); self.ui_handler.msgbox(msgtype, title, text, link, retry);
} }
fn handle_login_error(&mut self, err: &str) -> bool { fn handle_login_error(&mut self, err: &str) -> bool {
@ -1164,7 +1164,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
if pi.displays.is_empty() { if pi.displays.is_empty() {
self.lc.write().unwrap().handle_peer_info(&pi); self.lc.write().unwrap().handle_peer_info(&pi);
self.update_privacy_mode(); self.update_privacy_mode();
self.msgbox("error", "Remote Error", "No Display"); self.msgbox("error", "Remote Error", "No Display", "");
return; return;
} }
let p = self.lc.read().unwrap().should_auto_login(); let p = self.lc.read().unwrap().should_auto_login();
@ -1181,7 +1181,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
if self.is_file_transfer() { if self.is_file_transfer() {
self.close_success(); self.close_success();
} else if !self.is_port_forward() { } else if !self.is_port_forward() {
self.msgbox("success", "Successful", "Connected, waiting for image..."); self.msgbox("success", "Successful", "Connected, waiting for image...", "");
} }
#[cfg(windows)] #[cfg(windows)]
{ {