Merge branch 'rustdesk:master' into feat/optional_update_check

This commit is contained in:
Sahil Yeole 2023-09-14 14:00:25 +05:30 committed by GitHub
commit dccc791c99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1012 additions and 65 deletions

View File

@ -24,7 +24,7 @@ jobs:
path: /opt/artifacts path: /opt/artifacts
key: vcpkg-${{ matrix.job.arch }} key: vcpkg-${{ matrix.job.arch }}
- uses: Kingtous/run-on-arch-action@amd64-support - uses: rustdesk-org/run-on-arch-action@amd64-support
name: Run vcpkg install on ${{ matrix.job.arch }} name: Run vcpkg install on ${{ matrix.job.arch }}
id: vcpkg id: vcpkg
with: with:
@ -40,12 +40,16 @@ jobs:
apt update -y apt update -y
case "${{ matrix.job.arch }}" in case "${{ matrix.job.arch }}" in
x86_64) x86_64)
# CMake 3.15+
apt install -y gpg wget ca-certificates
echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ bionic main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
apt update -y apt update -y
apt install -y curl zip unzip tar git cmake g++ gcc build-essential pkg-config wget nasm yasm ninja-build libjpeg8-dev apt install -y curl zip unzip tar git g++ gcc build-essential pkg-config wget nasm yasm ninja-build libjpeg8-dev libssl-dev
wget https://github.com/Kitware/CMake/releases/download/v3.20.2/cmake-3.20.2.tar.gz
apt remove -y --purge cmake
tar -zxvf cmake-3.20.2.tar.gz
cd cmake-3.20.2
./bootstrap
make
make install
cd -
cmake --version cmake --version
gcc -v gcc -v
;; ;;

BIN
flutter/assets/scam.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

View File

@ -101,6 +101,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
required this.highlight, required this.highlight,
required this.drag_indicator, required this.drag_indicator,
required this.shadow, required this.shadow,
required this.errorBannerBg,
}); });
final Color? border; final Color? border;
@ -108,6 +109,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
final Color? highlight; final Color? highlight;
final Color? drag_indicator; final Color? drag_indicator;
final Color? shadow; final Color? shadow;
final Color? errorBannerBg;
static final light = ColorThemeExtension( static final light = ColorThemeExtension(
border: Color(0xFFCCCCCC), border: Color(0xFFCCCCCC),
@ -115,6 +117,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
highlight: Color(0xFFE5E5E5), highlight: Color(0xFFE5E5E5),
drag_indicator: Colors.grey[800], drag_indicator: Colors.grey[800],
shadow: Colors.black, shadow: Colors.black,
errorBannerBg: Color(0xFFFDEEEB),
); );
static final dark = ColorThemeExtension( static final dark = ColorThemeExtension(
@ -123,6 +126,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
highlight: Color(0xFF3F3F3F), highlight: Color(0xFF3F3F3F),
drag_indicator: Colors.grey, drag_indicator: Colors.grey,
shadow: Colors.grey, shadow: Colors.grey,
errorBannerBg: Color(0xFF470F2D),
); );
@override @override
@ -132,6 +136,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
Color? highlight, Color? highlight,
Color? drag_indicator, Color? drag_indicator,
Color? shadow, Color? shadow,
Color? errorBannerBg,
}) { }) {
return ColorThemeExtension( return ColorThemeExtension(
border: border ?? this.border, border: border ?? this.border,
@ -139,6 +144,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
highlight: highlight ?? this.highlight, highlight: highlight ?? this.highlight,
drag_indicator: drag_indicator ?? this.drag_indicator, drag_indicator: drag_indicator ?? this.drag_indicator,
shadow: shadow ?? this.shadow, shadow: shadow ?? this.shadow,
errorBannerBg: errorBannerBg ?? this.errorBannerBg,
); );
} }
@ -154,6 +160,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
highlight: Color.lerp(highlight, other.highlight, t), highlight: Color.lerp(highlight, other.highlight, t),
drag_indicator: Color.lerp(drag_indicator, other.drag_indicator, t), drag_indicator: Color.lerp(drag_indicator, other.drag_indicator, t),
shadow: Color.lerp(shadow, other.shadow, t), shadow: Color.lerp(shadow, other.shadow, t),
errorBannerBg: Color.lerp(shadow, other.errorBannerBg, t),
); );
} }
} }
@ -258,6 +265,14 @@ class MyTheme {
? EdgeInsets.only(left: dialogPadding) ? EdgeInsets.only(left: dialogPadding)
: EdgeInsets.only(left: dialogPadding / 3); : EdgeInsets.only(left: dialogPadding / 3);
static ScrollbarThemeData scrollbarTheme = ScrollbarThemeData(
thickness: MaterialStateProperty.all(kScrollbarThickness),
);
static ScrollbarThemeData scrollbarThemeDark = scrollbarTheme.copyWith(
thumbColor: MaterialStateProperty.all(Colors.grey[500]),
);
static ThemeData lightTheme = ThemeData( static ThemeData lightTheme = ThemeData(
brightness: Brightness.light, brightness: Brightness.light,
hoverColor: Color.fromARGB(255, 224, 224, 224), hoverColor: Color.fromARGB(255, 224, 224, 224),
@ -273,6 +288,7 @@ class MyTheme {
), ),
), ),
), ),
scrollbarTheme: scrollbarTheme,
inputDecorationTheme: isDesktop inputDecorationTheme: isDesktop
? InputDecorationTheme( ? InputDecorationTheme(
fillColor: grayBg, fillColor: grayBg,
@ -357,6 +373,7 @@ class MyTheme {
), ),
), ),
), ),
scrollbarTheme: scrollbarThemeDark,
inputDecorationTheme: isDesktop inputDecorationTheme: isDesktop
? InputDecorationTheme( ? InputDecorationTheme(
fillColor: Color(0xFF24252B), fillColor: Color(0xFF24252B),
@ -383,9 +400,6 @@ class MyTheme {
tabBarTheme: const TabBarTheme( tabBarTheme: const TabBarTheme(
labelColor: Colors.white70, labelColor: Colors.white70,
), ),
scrollbarTheme: ScrollbarThemeData(
thumbColor: MaterialStateProperty.all(Colors.grey[500]),
),
tooltipTheme: tooltipTheme(), tooltipTheme: tooltipTheme(),
splashColor: isDesktop ? Colors.transparent : null, splashColor: isDesktop ? Colors.transparent : null,
highlightColor: isDesktop ? Colors.transparent : null, highlightColor: isDesktop ? Colors.transparent : null,

View File

@ -76,7 +76,7 @@ class _AddressBookState extends State<AddressBook> {
child: Center( child: Center(
child: Container( child: Container(
height: height, height: height,
color: Color.fromARGB(255, 253, 238, 235), color: MyTheme.color(context).errorBannerBg,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,

View File

@ -302,6 +302,53 @@ Future<String> changeDirectAccessPort(
return controller.text; return controller.text;
} }
Future<String> changeAutoDisconnectTimeout(String old) async {
final controller = TextEditingController(text: old);
await gFFI.dialogManager.show((setState, close, context) {
return CustomAlertDialog(
title: Text(translate("Timeout in minutes")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8.0),
Row(
children: [
Expanded(
child: TextField(
maxLines: null,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: '10',
isCollapsed: true,
suffix: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.clear, size: 16),
onPressed: () => controller.clear())),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
],
controller: controller,
autofocus: true),
),
],
),
],
),
actions: [
dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("OK", onPressed: () async {
await bind.mainSetOption(
key: 'auto-disconnect-timeout', value: controller.text);
close();
}),
],
onCancel: close,
);
});
return controller.text;
}
class DialogTextField extends StatelessWidget { class DialogTextField extends StatelessWidget {
final String title; final String title;
final String? hintText; final String? hintText;

View File

@ -13,6 +13,8 @@ const String kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS"; const String kPeerPlatformMacOS = "Mac OS";
const String kPeerPlatformAndroid = "Android"; const String kPeerPlatformAndroid = "Android";
const double kScrollbarThickness = 12.0;
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page" /// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page"
const String kAppTypeMain = "main"; const String kAppTypeMain = "main";

View File

@ -732,6 +732,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
reverse: true, enabled: enabled), reverse: true, enabled: enabled),
...directIp(context), ...directIp(context),
whitelist(), whitelist(),
...autoDisconnect(context),
]); ]);
} }
@ -910,6 +911,63 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
)); ));
})); }));
} }
List<Widget> autoDisconnect(BuildContext context) {
TextEditingController controller = TextEditingController();
update() => setState(() {});
RxBool applyEnabled = false.obs;
final optionKey = 'allow-auto-disconnect';
final timeoutKey = 'auto-disconnect-timeout';
return [
_OptionCheckBox(context, 'auto_disconnect_option_tip', optionKey,
update: update, enabled: !locked),
() {
bool enabled =
option2bool(optionKey, bind.mainGetOptionSync(key: optionKey));
if (!enabled) applyEnabled.value = false;
controller.text = bind.mainGetOptionSync(key: timeoutKey);
return Offstage(
offstage: !enabled,
child: _SubLabeledWidget(
context,
'Timeout in minutes',
Row(children: [
SizedBox(
width: 95,
child: TextField(
controller: controller,
enabled: enabled && !locked,
onChanged: (_) => applyEnabled.value = true,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
],
decoration: const InputDecoration(
hintText: '10',
contentPadding:
EdgeInsets.symmetric(vertical: 12, horizontal: 12),
),
).marginOnly(right: 15),
),
Obx(() => ElevatedButton(
onPressed: applyEnabled.value && enabled && !locked
? () async {
applyEnabled.value = false;
await bind.mainSetOption(
key: timeoutKey, value: controller.text);
}
: null,
child: Text(
translate('Apply'),
),
))
]),
enabled: enabled && !locked,
),
);
}(),
];
}
} }
class _Network extends StatefulWidget { class _Network extends StatefulWidget {
@ -1541,9 +1599,14 @@ Widget _OptionCheckBox(BuildContext context, String label, String key,
isServer isServer
? await mainSetBoolOption(key, option) ? await mainSetBoolOption(key, option)
: await mainSetLocalBoolOption(key, option); : await mainSetLocalBoolOption(key, option);
ref.value = isServer final readOption = isServer
? mainGetBoolOptionSync(key) ? mainGetBoolOptionSync(key)
: mainGetLocalBoolOptionSync(key); : mainGetLocalBoolOptionSync(key);
if (reverse) {
ref.value = !readOption;
} else {
ref.value = readOption;
}
update?.call(); update?.call();
} }
} }

View File

@ -694,6 +694,7 @@ class _ImagePaintState extends State<ImagePaint> {
enableCustomMouseWheelScrolling: cursorOverImage.isFalse, enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
customMouseWheelScrollConfig: scrollConfig, customMouseWheelScrollConfig: scrollConfig,
child: RawScrollbar( child: RawScrollbar(
thickness: kScrollbarThickness,
thumbColor: Colors.grey, thumbColor: Colors.grey,
controller: _horizontal, controller: _horizontal,
thumbVisibility: false, thumbVisibility: false,
@ -711,6 +712,7 @@ class _ImagePaintState extends State<ImagePaint> {
enableCustomMouseWheelScrolling: cursorOverImage.isFalse, enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
customMouseWheelScrollConfig: scrollConfig, customMouseWheelScrollConfig: scrollConfig,
child: RawScrollbar( child: RawScrollbar(
thickness: kScrollbarThickness,
thumbColor: Colors.grey, thumbColor: Colors.grey,
controller: _vertical, controller: _vertical,
thumbVisibility: false, thumbVisibility: false,

View File

@ -210,11 +210,199 @@ class ServiceNotRunningNotification extends StatelessWidget {
.marginOnly(bottom: 8), .marginOnly(bottom: 8),
ElevatedButton.icon( ElevatedButton.icon(
icon: const Icon(Icons.play_arrow), icon: const Icon(Icons.play_arrow),
onPressed: serverModel.toggleService, onPressed: () {
if (gFFI.userModel.userName.value.isEmpty && bind.mainGetLocalOption(key: "show-scam-warning") != "N") {
_showScamWarning(context, serverModel);
} else {
serverModel.toggleService();
}
},
label: Text(translate("Start Service"))) label: Text(translate("Start Service")))
], ],
)); ));
} }
void _showScamWarning(BuildContext context, ServerModel serverModel) {
showDialog(
context: context,
builder: (BuildContext context) {
return ScamWarningDialog(serverModel: serverModel);
},
);
}
}
class ScamWarningDialog extends StatefulWidget {
final ServerModel serverModel;
ScamWarningDialog({required this.serverModel});
@override
_ScamWarningDialogState createState() => _ScamWarningDialogState();
}
class _ScamWarningDialogState extends State<ScamWarningDialog> {
int _countdown = 12;
bool show_warning = false;
late Timer _timer;
late ServerModel _serverModel;
@override
void initState() {
super.initState();
_serverModel = widget.serverModel;
startCountdown();
}
void startCountdown() {
const oneSecond = Duration(seconds: 1);
_timer = Timer.periodic(oneSecond, (timer) {
setState(() {
_countdown--;
if (_countdown <= 0) {
timer.cancel();
}
});
});
}
@override
void dispose() {
_timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isButtonLocked = _countdown > 0;
return AlertDialog(
content: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Color(0xffe242bc),
Color(0xfff4727c),
],
),
borderRadius: BorderRadius.circular(20.0),
),
padding: EdgeInsets.all(25.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.warning_amber_sharp,
color: Colors.white,
),
SizedBox(width: 10),
Text(
translate("Warning"),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 20.0,
),
),
],
),
SizedBox(height: 20),
Center(
child: Image.asset('assets/scam.png',
width: 180,
),
),
SizedBox(height: 18),
Text(
translate("scam_title"),
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 22.0,
),
),
SizedBox(height: 18),
Text(
translate("scam_text1")+"\n\n"
+translate("scam_text2")+"\n",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
Row(
children: <Widget>[
Checkbox(
value: show_warning,
onChanged: (value) {
setState((){
show_warning = value!;
});
},
),
Text(
translate("Don't show again"),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15.0,
),
),
],
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton(
onPressed: isButtonLocked
? null
: () {
Navigator.of(context).pop();
_serverModel.toggleService();
if (show_warning) {
bind.mainSetLocalOption(key: "show-scam-warning", value: "N");
}
},
style: ElevatedButton.styleFrom(
primary: Colors.blueAccent,
),
child: Text(
isButtonLocked ? translate("I Agree")+" (${_countdown}s)" : translate("I Agree"),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13.0,
),
),
),
SizedBox(width: 15),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
primary: Colors.blueAccent,
),
child: Text(
translate("Decline"),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13.0,
),
),
),
],
)])),
contentPadding: EdgeInsets.all(0.0),
);
}
} }
class ServerInfo extends StatelessWidget { class ServerInfo extends StatelessWidget {

View File

@ -45,10 +45,12 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
var _enableDirectIPAccess = false; var _enableDirectIPAccess = false;
var _enableRecordSession = false; var _enableRecordSession = false;
var _autoRecordIncomingSession = false; var _autoRecordIncomingSession = false;
var _allowAutoDisconnect = false;
var _localIP = ""; var _localIP = "";
var _directAccessPort = ""; var _directAccessPort = "";
var _fingerprint = ""; var _fingerprint = "";
var _buildDate = ""; var _buildDate = "";
var _autoDisconnectTimeout = "";
@override @override
void initState() { void initState() {
@ -151,6 +153,20 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
_buildDate = buildDate; _buildDate = buildDate;
} }
final allowAutoDisconnect = option2bool('allow-auto-disconnect',
await bind.mainGetOption(key: 'allow-auto-disconnect'));
if (allowAutoDisconnect != _allowAutoDisconnect) {
update = true;
_allowAutoDisconnect = allowAutoDisconnect;
}
final autoDisconnectTimeout =
await bind.mainGetOption(key: 'auto-disconnect-timeout');
if (autoDisconnectTimeout != _autoDisconnectTimeout) {
update = true;
_autoDisconnectTimeout = autoDisconnectTimeout;
}
if (update) { if (update) {
setState(() {}); setState(() {});
} }
@ -306,6 +322,48 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
await bind.mainSetOption(key: 'direct-server', value: value); await bind.mainSetOption(key: 'direct-server', value: value);
setState(() {}); setState(() {});
}, },
),
SettingsTile.switchTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("auto_disconnect_option_tip")),
Offstage(
offstage: !_allowAutoDisconnect,
child: Text(
'${_autoDisconnectTimeout.isEmpty ? '10' : _autoDisconnectTimeout} min',
style: Theme.of(context).textTheme.bodySmall,
)),
])),
Offstage(
offstage: !_allowAutoDisconnect,
child: IconButton(
padding: EdgeInsets.zero,
icon: Icon(
Icons.edit,
size: 20,
),
onPressed: () async {
final timeout = await changeAutoDisconnectTimeout(
_autoDisconnectTimeout);
setState(() {
_autoDisconnectTimeout = timeout;
});
}))
]),
initialValue: _allowAutoDisconnect,
onToggle: (_) async {
_allowAutoDisconnect = !_allowAutoDisconnect;
String value =
bool2option('allow-auto-disconnect', _allowAutoDisconnect);
await bind.mainSetOption(key: 'allow-auto-disconnect', value: value);
setState(() {});
},
) )
]; ];
if (_hasIgnoreBattery) { if (_hasIgnoreBattery) {

View File

@ -404,7 +404,7 @@ pub fn core_main() -> Option<Vec<String>> {
crate::ui_interface::start_option_status_sync(); crate::ui_interface::start_option_status_sync();
} else if args[0] == "--cm-no-ui" { } else if args[0] == "--cm-no-ui" {
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "windows")))]
crate::flutter::connection_manager::start_cm_no_ui(); crate::flutter::connection_manager::start_cm_no_ui();
return None; return None;
} else { } else {

View File

@ -157,6 +157,7 @@ fn handle_config_options(config_options: HashMap<String, String>) {
Config::set_options(options); Config::set_options(options);
} }
#[allow(unused)]
#[cfg(not(any(target_os = "ios")))] #[cfg(not(any(target_os = "ios")))]
pub fn is_pro() -> bool { pub fn is_pro() -> bool {
PRO.lock().unwrap().clone() PRO.lock().unwrap().clone()

View File

@ -234,6 +234,8 @@ pub enum Data {
#[cfg(windows)] #[cfg(windows)]
SyncWinCpuUsage(Option<f64>), SyncWinCpuUsage(Option<f64>),
FileTransferLog(String), FileTransferLog(String),
#[cfg(any(windows, target_os = "macos"))]
ControlledSessionCount(usize),
} }
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
@ -482,6 +484,16 @@ async fn handle(data: Data, stream: &mut Connection) {
#[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
Data::Plugin(plugin) => crate::plugin::ipc::handle_plugin(plugin, stream).await, Data::Plugin(plugin) => crate::plugin::ipc::handle_plugin(plugin, stream).await,
#[cfg(any(windows, target_os = "macos"))]
Data::ControlledSessionCount(_) => {
allow_err!(
stream
.send(&Data::ControlledSessionCount(
crate::Connection::alive_conns().len()
))
.await
);
}
_ => {} _ => {}
} }
} }

View File

@ -1,5 +1,7 @@
use hbb_common::regex::Regex;
use std::ops::Deref; use std::ops::Deref;
mod ar;
mod ca; mod ca;
mod cn; mod cn;
mod cs; mod cs;
@ -17,6 +19,7 @@ mod it;
mod ja; mod ja;
mod ko; mod ko;
mod kz; mod kz;
mod lt;
mod nl; mod nl;
mod pl; mod pl;
mod ptbr; mod ptbr;
@ -32,8 +35,6 @@ mod tr;
mod tw; mod tw;
mod ua; mod ua;
mod vn; mod vn;
mod lt;
mod ar;
pub const LANGS: &[(&str, &str)] = &[ pub const LANGS: &[(&str, &str)] = &[
("en", "English"), ("en", "English"),
@ -137,16 +138,67 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"ar" => ar::T.deref(), "ar" => ar::T.deref(),
_ => en::T.deref(), _ => en::T.deref(),
}; };
let (name, placeholder_value) = extract_placeholder(&name);
let replace = |s: &&str| {
let mut s = s.to_string();
if let Some(value) = placeholder_value.as_ref() {
s = s.replace("{}", &value);
}
s
};
if let Some(v) = m.get(&name as &str) { if let Some(v) = m.get(&name as &str) {
if v.is_empty() { if v.is_empty() {
if lang != "en" { if lang != "en" {
if let Some(v) = en::T.get(&name as &str) { if let Some(v) = en::T.get(&name as &str) {
return v.to_string(); return replace(v);
} }
} }
} else { } else {
return v.to_string(); return replace(v);
} }
} }
name replace(&name.as_str())
}
// Matching pattern is {}
// Write {value} in the UI and {} in the translation file
//
// Example:
// Write in the UI: translate("There are {24} hours in a day")
// Write in the translation file: ("There are {} hours in a day", "{} hours make up a day")
fn extract_placeholder(input: &str) -> (String, Option<String>) {
if let Ok(re) = Regex::new(r#"\{(.*?)\}"#) {
if let Some(captures) = re.captures(input) {
if let Some(inner_match) = captures.get(1) {
let name = re.replace(input, "{}").to_string();
let value = inner_match.as_str().to_string();
return (name, Some(value));
}
}
}
(input.to_string(), None)
}
mod test {
#[test]
fn test_extract_placeholders() {
use super::extract_placeholder as f;
assert_eq!(f(""), ("".to_string(), None));
assert_eq!(
f("{3} sessions"),
("{} sessions".to_string(), Some("3".to_string()))
);
assert_eq!(f(" } { "), (" } { ".to_string(), None));
// Allow empty value
assert_eq!(
f("{} sessions"),
("{} sessions".to_string(), Some("".to_string()))
);
// Match only the first one
assert_eq!(
f("{2} times {4} makes {8}"),
("{} times {4} makes {8}".to_string(), Some("2".to_string()))
);
}
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", "安装成功!"), ("Installation Successful!", "安装成功!"),
("Installation failed!", "安装失败!"), ("Installation failed!", "安装失败!"),
("Reverse mouse wheel", "鼠标滚轮反向"), ("Reverse mouse wheel", "鼠标滚轮反向"),
("{} sessions", "{}个会话"),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", "超时(分钟)"),
("auto_disconnect_option_tip", "自动关闭不活跃的会话"),
("Connection failed due to inactivity", "由于长时间无操作, 连接被自动断开"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -543,6 +543,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("HSV Color", "HSV-Farbe"), ("HSV Color", "HSV-Farbe"),
("Installation Successful!", "Installation erfolgreich!"), ("Installation Successful!", "Installation erfolgreich!"),
("Installation failed!", "Installation fehlgeschlagen!"), ("Installation failed!", "Installation fehlgeschlagen!"),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", "Mausrad rückwärts drehen"),
("{} sessions", "{} Sitzungen"),
("scam_title", "Sie werden möglicherweise BETROGEN!"),
("scam_text1", "Wenn Sie mit jemandem telefonieren, den Sie NICHT KENNEN, dem Sie NICHT VERTRAUEN und der Sie gebeten hat, RustDesk zu benutzen und den Dienst zu starten, fahren Sie nicht fort und legen Sie sofort auf."),
("scam_text2", "Es handelt sich wahrscheinlich um einen Betrüger, der versucht, Ihr Geld oder andere private Informationen zu stehlen."),
("Don't show again", "Nicht mehr anzeigen"),
("I Agree", "Ich bin einverstanden"),
("Decline", "Ablehnen"),
("Timeout in minutes", "Zeitüberschreitung in Minuten"),
("auto_disconnect_option_tip", "Automatisches Schließen eingehender Sitzungen bei Inaktivität des Benutzers"),
("Connection failed due to inactivity", "Automatische Trennung der Verbindung aufgrund von Inaktivität"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -82,5 +82,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Start session recording", "Start Session Recording"), ("Start session recording", "Start Session Recording"),
("Stop session recording", "Stop Session Recording"), ("Stop session recording", "Stop Session Recording"),
("Enable remote configuration modification", "Enable Remote Configuration Modification"), ("Enable remote configuration modification", "Enable Remote Configuration Modification"),
("scam_title", "You May Be Being SCAMMED!"),
("scam_text1", "If you are on the phone with someone you DON'T know AND TRUST who has asked you to use RustDesk and start the service, do not proceed and hang up immediately."),
("scam_text2", "They are likely a scammer trying to steal your money or other private information."),
("Don't show again", "Don't show again"),
("I Agree", "I Agree"),
("Decline", "Decline"),
("auto_disconnect_option_tip", "Automatically close incoming sessions on user inactivity"),
("Connection failed due to inactivity", "Automatically disconnected due to inactivity"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", "Instalación exitosa"), ("Installation Successful!", "Instalación exitosa"),
("Installation failed!", "La instalación ha fallado"), ("Installation failed!", "La instalación ha fallado"),
("Reverse mouse wheel", "Invertir rueda del ratón"), ("Reverse mouse wheel", "Invertir rueda del ratón"),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -541,8 +541,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Change Color", "Ganti warna"), ("Change Color", "Ganti warna"),
("Primary Color", "Warna utama"), ("Primary Color", "Warna utama"),
("HSV Color", "Warna HSV"), ("HSV Color", "Warna HSV"),
("Installation Successful!", ""), ("Installation Successful!", "Instalasi berhasil!"),
("Installation failed!", ""), ("Installation failed!", "Instalasi gagal!"),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", "Balikkan arah scroll mouse!"),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -75,9 +75,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Do you want to enter again?", "Vuoi riprovare?"), ("Do you want to enter again?", "Vuoi riprovare?"),
("Connection Error", "Errore di connessione"), ("Connection Error", "Errore di connessione"),
("Error", "Errore"), ("Error", "Errore"),
("Reset by the peer", "Reimpostata dal peer"), ("Reset by the peer", "Reimpostata dal dispositivo remoto"),
("Connecting...", "Connessione..."), ("Connecting...", "Connessione..."),
("Connection in progress. Please wait.", "Connessione in corso..."), ("Connection in progress. Please wait.", "Connessione..."),
("Please try 1 minute later", "Riprova fra 1 minuto"), ("Please try 1 minute later", "Riprova fra 1 minuto"),
("Login Error", "Errore accesso"), ("Login Error", "Errore accesso"),
("Successful", "Completato"), ("Successful", "Completato"),
@ -210,7 +210,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Settings", "Impostazioni"), ("Settings", "Impostazioni"),
("Username", "Nome utente"), ("Username", "Nome utente"),
("Invalid port", "Numero porta non valido"), ("Invalid port", "Numero porta non valido"),
("Closed manually by the peer", "Chiuso manualmente dal peer"), ("Closed manually by the peer", "Chiuso manualmente dal dispositivo remoto"),
("Enable remote configuration modification", "Abilita la modifica remota della configurazione"), ("Enable remote configuration modification", "Abilita la modifica remota della configurazione"),
("Run without install", "Esegui senza installare"), ("Run without install", "Esegui senza installare"),
("Connect via relay", "Collegati tramite relay"), ("Connect via relay", "Collegati tramite relay"),
@ -301,9 +301,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Succeeded", "Completato"), ("Succeeded", "Completato"),
("Someone turns on privacy mode, exit", "Qualcuno ha attivato la modalità privacy, uscita"), ("Someone turns on privacy mode, exit", "Qualcuno ha attivato la modalità privacy, uscita"),
("Unsupported", "Non supportato"), ("Unsupported", "Non supportato"),
("Peer denied", "Peer negato"), ("Peer denied", "Acvesso negato al dispositivo remoto"),
("Please install plugins", "Installa i plugin"), ("Please install plugins", "Installa i plugin"),
("Peer exit", "Uscita peer"), ("Peer exit", "Uscita dal dispostivo remoto"),
("Failed to turn off", "Impossibile spegnere"), ("Failed to turn off", "Impossibile spegnere"),
("Turned off", "Spegni"), ("Turned off", "Spegni"),
("In privacy mode", "In modalità privacy"), ("In privacy mode", "In modalità privacy"),
@ -392,7 +392,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland richiede Ubuntu 21.04 o versione successiva."), ("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.\nProva X11 desktop o cambia il sistema operativo."), ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland richiede una versione superiore della distribuzione Linux.\nProva X11 desktop o cambia il sistema operativo."),
("JumpLink", "Vai a"), ("JumpLink", "Vai a"),
("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato peer)."), ("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato dispositivo remoto)."),
("Show RustDesk", "Visualizza RustDesk"), ("Show RustDesk", "Visualizza RustDesk"),
("This PC", "Questo PC"), ("This PC", "Questo PC"),
("or", "O"), ("or", "O"),
@ -477,7 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", "Nome utente vuoto"), ("Empty Username", "Nome utente vuoto"),
("Empty Password", "Password vuota"), ("Empty Password", "Password vuota"),
("Me", "Io"), ("Me", "Io"),
("identical_file_tip", "Questo file è identico a quello del peer."), ("identical_file_tip", "Questo file è identico a quello nel dispositivo remoto."),
("show_monitors_tip", "Visualizza schermi nella barra strumenti"), ("show_monitors_tip", "Visualizza schermi nella barra strumenti"),
("View Mode", "Modalità visualizzazione"), ("View Mode", "Modalità visualizzazione"),
("login_linux_tip", "Accedi all'account Linux remoto"), ("login_linux_tip", "Accedi all'account Linux remoto"),
@ -498,8 +498,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Firma digitale"), ("Fingerprint", "Firma digitale"),
("Copy Fingerprint", "Copia firma digitale"), ("Copy Fingerprint", "Copia firma digitale"),
("no fingerprints", "Nessuna firma digitale"), ("no fingerprints", "Nessuna firma digitale"),
("Select a peer", "Seleziona un peer"), ("Select a peer", "Seleziona dispositivo remoto"),
("Select peers", "Seleziona peer"), ("Select peers", "Seleziona dispositivi remoti"),
("Plugins", "Plugin"), ("Plugins", "Plugin"),
("Uninstall", "Disinstalla"), ("Uninstall", "Disinstalla"),
("Update", "Aggiorna"), ("Update", "Aggiorna"),
@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", "Installazione completata"), ("Installation Successful!", "Installazione completata"),
("Installation failed!", "Installazione fallita"), ("Installation failed!", "Installazione fallita"),
("Reverse mouse wheel", "Rotella mouse inversa"), ("Reverse mouse wheel", "Rotella mouse inversa"),
("{} sessions", "{} sessioni"),
("scam_title", "Potresti essere stato TRUFFATO!"),
("scam_text1", "Se sei al telefono con qualcuno che NON conosci NON DI TUA FIDUCIA che ti ha chiesto di usare RustDesk e di avviare il servizio, non procedere e riattacca subito."),
("scam_text2", "Probabilmente è un truffatore che cerca di rubare i tuoi soldi o altre informazioni private."),
("Don't show again", "Non visualizzare più"),
("I Agree", "Accetto"),
("Decline", "Non accetto"),
("Timeout in minutes", "Timeout in minuti"),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", "Connessione non riuscita a causa di inattività"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -528,21 +528,31 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Move tab to new window", "Przenieś zakładkę do nowego okna"), ("Move tab to new window", "Przenieś zakładkę do nowego okna"),
("Can not be empty", "Nie może być puste"), ("Can not be empty", "Nie może być puste"),
("Already exists", "Już istnieje"), ("Already exists", "Już istnieje"),
("Change Password", ""), ("Change Password", "Zmień hasło"),
("Refresh Password", ""), ("Refresh Password", "Odśwież hasło"),
("ID", ""), ("ID", "ID"),
("Grid View", ""), ("Grid View", "Widok siatki"),
("List View", ""), ("List View", "Widok listy"),
("Select", ""), ("Select", "Wybierz"),
("Toggle Tags", ""), ("Toggle Tags", "Przełącz tagi"),
("pull_ab_failed_tip", ""), ("pull_ab_failed_tip", "Aktualizacja książki adresowej nie powiodła się"),
("push_ab_failed_tip", ""), ("push_ab_failed_tip", "Nie udało się zsynchronizować książki adresowej z serwerem"),
("synced_peer_readded_tip", ""), ("synced_peer_readded_tip", "Urządzenia, które były obecne w ostatnich sesjach, zostaną ponownie dodane do książki adresowej"),
("Change Color", ""), ("Change Color", "Zmień kolor"),
("Primary Color", ""), ("Primary Color", "Kolor podstawowy"),
("HSV Color", ""), ("HSV Color", "Kolor HSV"),
("Installation Successful!", ""), ("Installation Successful!", "Instalacja zakończona!"),
("Installation failed!", ""), ("Installation failed!", "Instalacja nie powiodła się"),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", "Odwróć rolkę myszki"),
("{} sessions", "{} sesji"),
("scam_title", "Prawdopodobnie zostałeś OSZUKANY!"),
("scam_text1", "Jeżeli rozmawiasz przez telefon z osobą której NIE ZNASZ i NIE UFASZ, która prosi Cię o uruchomienie programu RustDesk i uruchomienia usługi - nie rób tego i natychmiast się rozłącz."),
("scam_text2", "Wygląda to na oszusta, który próbuje ukraść Twoje pieniądze lub inne prywatne informacje."),
("Don't show again", "Nie pokazuj więcej"),
("I Agree", "Zgadzam się"),
("Decline", "Odmawiam"),
("Timeout in minutes", "Czas bezczynności w minutach"),
("auto_disconnect_option_tip", "Automatycznie rozłącz sesje przychodzące przy braku aktywności użytkownika"),
("Connection failed due to inactivity", "Automatycznie rozłącz przy bezczynności"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", "Установка выполнена успешно!"), ("Installation Successful!", "Установка выполнена успешно!"),
("Installation failed!", "Установка не выполнена!"), ("Installation failed!", "Установка не выполнена!"),
("Reverse mouse wheel", "Реверсировать колесо мыши"), ("Reverse mouse wheel", "Реверсировать колесо мыши"),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -544,5 +544,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Installation Successful!", ""), ("Installation Successful!", ""),
("Installation failed!", ""), ("Installation failed!", ""),
("Reverse mouse wheel", ""), ("Reverse mouse wheel", ""),
("{} sessions", ""),
("scam_title", ""),
("scam_text1", ""),
("scam_text2", ""),
("Don't show again", ""),
("I Agree", ""),
("Decline", ""),
("Timeout in minutes", ""),
("auto_disconnect_option_tip", ""),
("Connection failed due to inactivity", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -1183,6 +1183,7 @@ pub fn uninstall_service(show_new_window: bool) -> bool {
} }
pub fn install_service() -> bool { pub fn install_service() -> bool {
let _installing = crate::platform::InstallingService::new();
if !has_cmd("systemctl") { if !has_cmd("systemctl") {
return false; return false;
} }

View File

@ -23,9 +23,18 @@ pub mod linux_desktop_manager;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::{message_proto::CursorData, ResultType}; use hbb_common::{message_proto::CursorData, ResultType};
use std::sync::{Arc, Mutex};
#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))]
const SERVICE_INTERVAL: u64 = 300; const SERVICE_INTERVAL: u64 = 300;
lazy_static::lazy_static! {
static ref INSTALLING_SERVICE: Arc<Mutex<bool>>= Default::default();
}
pub fn installing_service() -> bool {
INSTALLING_SERVICE.lock().unwrap().clone()
}
pub fn is_xfce() -> bool { pub fn is_xfce() -> bool {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@ -71,6 +80,21 @@ pub fn get_active_username() -> String {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub const PA_SAMPLE_RATE: u32 = 48000; pub const PA_SAMPLE_RATE: u32 = 48000;
pub(crate) struct InstallingService; // please use new
impl InstallingService {
pub fn new() -> Self {
*INSTALLING_SERVICE.lock().unwrap() = true;
Self
}
}
impl Drop for InstallingService {
fn drop(&mut self) {
*INSTALLING_SERVICE.lock().unwrap() = false;
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -2167,6 +2167,7 @@ pub fn uninstall_service(show_new_window: bool) -> bool {
pub fn install_service() -> bool { pub fn install_service() -> bool {
log::info!("Installing service..."); log::info!("Installing service...");
let _installing = crate::platform::InstallingService::new();
let (_, _, _, exe) = get_install_info(); let (_, _, _, exe) = get_install_info();
let tmp_path = std::env::temp_dir().to_string_lossy().to_string(); let tmp_path = std::env::temp_dir().to_string_lossy().to_string();
let tray_shortcut = get_tray_shortcut(&exe, &tmp_path).unwrap_or_default(); let tray_shortcut = get_tray_shortcut(&exe, &tmp_path).unwrap_or_default();

View File

@ -79,7 +79,9 @@ impl RendezvousMediator {
crate::platform::linux_desktop_manager::start_xdesktop(); crate::platform::linux_desktop_manager::start_xdesktop();
loop { loop {
Config::reset_online(); Config::reset_online();
if Config::get_option("stop-service").is_empty() { if Config::get_option("stop-service").is_empty()
&& !crate::platform::installing_service()
{
if !nat_tested { if !nat_tested {
crate::test_nat_type(); crate::test_nat_type();
nat_tested = true; nat_tested = true;

View File

@ -204,6 +204,7 @@ pub struct Connection {
delay_response_instant: Instant, delay_response_instant: Instant,
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
start_cm_ipc_para: Option<StartCmIpcPara>, start_cm_ipc_para: Option<StartCmIpcPara>,
auto_disconnect_timer: Option<(Instant, u64)>,
} }
impl ConnInner { impl ConnInner {
@ -343,6 +344,7 @@ impl Connection {
rx_desktop_ready, rx_desktop_ready,
tx_cm_stream_ready, tx_cm_stream_ready,
}), }),
auto_disconnect_timer: None,
}; };
let addr = hbb_common::try_into_v4(addr); let addr = hbb_common::try_into_v4(addr);
if !conn.on_open(addr).await { if !conn.on_open(addr).await {
@ -605,6 +607,13 @@ impl Connection {
_ = second_timer.tick() => { _ = second_timer.tick() => {
#[cfg(windows)] #[cfg(windows)]
conn.portable_check(); conn.portable_check();
if let Some((instant, minute)) = conn.auto_disconnect_timer.as_ref() {
if instant.elapsed().as_secs() > minute * 60 {
conn.send_close_reason_no_retry("Connection failed due to inactivity").await;
conn.on_close("auto disconnect", true).await;
break;
}
}
} }
_ = test_delay_timer.tick() => { _ = test_delay_timer.tick() => {
if last_recv_time.elapsed() >= SEC30 { if last_recv_time.elapsed() >= SEC30 {
@ -1139,6 +1148,7 @@ impl Connection {
let mut s = s.write().unwrap(); let mut s = s.write().unwrap();
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
let _h = try_start_record_cursor_pos(); let _h = try_start_record_cursor_pos();
self.auto_disconnect_timer = Self::get_auto_disconenct_timer();
s.add_connection(self.inner.clone(), &noperms); s.add_connection(self.inner.clone(), &noperms);
} }
} }
@ -1351,6 +1361,12 @@ impl Connection {
log::error!("ipc to connection manager exit: {}", err); log::error!("ipc to connection manager exit: {}", err);
} }
}); });
#[cfg(all(windows, feature = "flutter"))]
std::thread::spawn(|| {
if crate::is_server() && !crate::check_process("--tray", false) {
crate::platform::run_as_user(vec!["--tray"]).ok();
}
});
} }
} }
@ -1606,6 +1622,7 @@ impl Connection {
} }
self.input_mouse(me, self.inner.id()); self.input_mouse(me, self.inner.id());
} }
self.update_auto_disconnect_timer();
} }
Some(message::Union::PointerDeviceEvent(pde)) => { Some(message::Union::PointerDeviceEvent(pde)) => {
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
@ -1641,6 +1658,7 @@ impl Connection {
MOUSE_MOVE_TIME.store(get_time(), Ordering::SeqCst); MOUSE_MOVE_TIME.store(get_time(), Ordering::SeqCst);
self.input_pointer(pde, self.inner.id()); self.input_pointer(pde, self.inner.id());
} }
self.update_auto_disconnect_timer();
} }
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
Some(message::Union::KeyEvent(..)) => {} Some(message::Union::KeyEvent(..)) => {}
@ -1696,6 +1714,7 @@ impl Connection {
self.input_key(me, false); self.input_key(me, false);
} }
} }
self.update_auto_disconnect_timer();
} }
Some(message::Union::Clipboard(_cb)) => Some(message::Union::Clipboard(_cb)) =>
{ {
@ -1884,6 +1903,7 @@ impl Connection {
Some(misc::Union::ChatMessage(c)) => { Some(misc::Union::ChatMessage(c)) => {
self.send_to_cm(ipc::Data::ChatMessage { text: c.text }); self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
self.chat_unanswered = true; self.chat_unanswered = true;
self.update_auto_disconnect_timer();
} }
Some(misc::Union::Option(o)) => { Some(misc::Union::Option(o)) => {
self.update_options(&o).await; self.update_options(&o).await;
@ -1892,6 +1912,7 @@ impl Connection {
if r { if r {
super::video_service::refresh(); super::video_service::refresh();
} }
self.update_auto_disconnect_timer();
} }
Some(misc::Union::VideoReceived(_)) => { Some(misc::Union::VideoReceived(_)) => {
video_service::notify_video_frame_fetched( video_service::notify_video_frame_fetched(
@ -2021,6 +2042,7 @@ impl Connection {
let mut msg = Message::new(); let mut msg = Message::new();
msg.set_misc(misc); msg.set_misc(misc);
self.send(msg).await; self.send(msg).await;
self.update_auto_disconnect_timer();
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -2278,7 +2300,7 @@ impl Connection {
lock_screen().await; lock_screen().await;
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
let data = if self.chat_unanswered || self.file_transferred { let data = if self.chat_unanswered || self.file_transferred && cfg!(feature = "flutter") {
ipc::Data::Disconnected ipc::Data::Disconnected
} else { } else {
ipc::Data::Close ipc::Data::Close
@ -2378,6 +2400,26 @@ impl Connection {
} }
self.pressed_modifiers.clear(); self.pressed_modifiers.clear();
} }
fn get_auto_disconenct_timer() -> Option<(Instant, u64)> {
if Config::get_option("allow-auto-disconnect") == "Y" {
let mut minute: u64 = Config::get_option("auto-disconnect-timeout")
.parse()
.unwrap_or(10);
if minute == 0 {
minute = 10;
}
Some((Instant::now(), minute))
} else {
None
}
}
fn update_auto_disconnect_timer(&mut self) {
self.auto_disconnect_timer
.as_mut()
.map(|t| t.0 = Instant::now());
}
} }
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) { pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
@ -2406,6 +2448,8 @@ async fn start_ipc(
if let Ok(s) = crate::ipc::connect(1000, "_cm").await { if let Ok(s) = crate::ipc::connect(1000, "_cm").await {
stream = Some(s); stream = Some(s);
} else { } else {
#[allow(unused_mut)]
#[allow(unused_assignments)]
let mut args = vec!["--cm"]; let mut args = vec!["--cm"];
if crate::hbbs_http::sync::is_pro() && password::hide_cm() { if crate::hbbs_http::sync::is_pro() && password::hide_cm() {
args.push("--hide"); args.push("--hide");

View File

@ -1,5 +1,11 @@
use crate::{client::translate, ipc::Data};
use hbb_common::{allow_err, log, tokio};
use std::{
sync::{Arc, Mutex},
time::Duration,
};
pub fn start_tray() { pub fn start_tray() {
use hbb_common::{allow_err, log};
allow_err!(make_tray()); allow_err!(make_tray());
} }
@ -40,29 +46,42 @@ pub fn make_tray() -> hbb_common::ResultType<()> {
let event_loop = EventLoopBuilder::new().build(); let event_loop = EventLoopBuilder::new().build();
let tray_menu = Menu::new(); let tray_menu = Menu::new();
let quit_i = MenuItem::new(crate::client::translate("Exit".to_owned()), true, None); let quit_i = MenuItem::new(translate("Exit".to_owned()), true, None);
let open_i = MenuItem::new(crate::client::translate("Open".to_owned()), true, None); let open_i = MenuItem::new(translate("Open".to_owned()), true, None);
tray_menu.append_items(&[&open_i, &quit_i]); tray_menu.append_items(&[&open_i, &quit_i]);
let tooltip = |count: usize| {
let _tray_icon = Some( if count == 0 {
TrayIconBuilder::new() format!(
.with_menu(Box::new(tray_menu))
.with_tooltip(format!(
"{} {}", "{} {}",
crate::get_app_name(), crate::get_app_name(),
crate::lang::translate("Service is running".to_owned()) translate("Service is running".to_owned()),
)) )
} else {
format!(
"{} - {}\n{}",
crate::get_app_name(),
translate("Ready".to_owned()),
translate("{".to_string() + &format!("{count}") + "} sessions"),
)
}
};
let tray_icon = Some(
TrayIconBuilder::new()
.with_menu(Box::new(tray_menu))
.with_tooltip(tooltip(0))
.with_icon(icon) .with_icon(icon)
.build()?, .build()?,
); );
let tray_icon = Arc::new(Mutex::new(tray_icon));
let menu_channel = MenuEvent::receiver(); let menu_channel = MenuEvent::receiver();
let tray_channel = TrayEvent::receiver(); let tray_channel = TrayEvent::receiver();
#[cfg(not(target_os = "linux"))]
let (ipc_sender, ipc_receiver) = std::sync::mpsc::channel::<Data>();
let mut docker_hiden = false; let mut docker_hiden = false;
let open_func = move || { let open_func = move || {
if cfg!(not(feature = "flutter")) if cfg!(not(feature = "flutter")) {
{
crate::run_me::<&str>(vec![]).ok(); crate::run_me::<&str>(vec![]).ok();
return; return;
} }
@ -89,6 +108,11 @@ pub fn make_tray() -> hbb_common::ResultType<()> {
} }
}; };
// ubuntu 22.04 can't see tooltip
#[cfg(not(target_os = "linux"))]
std::thread::spawn(move || {
start_query_session_count(ipc_sender.clone());
});
event_loop.run(move |_event, _, control_flow| { event_loop.run(move |_event, _, control_flow| {
if !docker_hiden { if !docker_hiden {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@ -121,5 +145,55 @@ pub fn make_tray() -> hbb_common::ResultType<()> {
open_func(); open_func();
} }
} }
#[cfg(not(target_os = "linux"))]
if let Ok(data) = ipc_receiver.try_recv() {
match data {
Data::ControlledSessionCount(count) => {
tray_icon
.lock()
.unwrap()
.as_mut()
.map(|t| t.set_tooltip(Some(tooltip(count))));
}
_ => {}
}
}
}); });
} }
#[cfg(not(target_os = "linux"))]
#[tokio::main(flavor = "current_thread")]
async fn start_query_session_count(sender: std::sync::mpsc::Sender<Data>) {
let mut last_count = 0;
loop {
if let Ok(mut c) = crate::ipc::connect(1000, "").await {
let mut timer = tokio::time::interval(Duration::from_secs(1));
loop {
tokio::select! {
res = c.next() => {
match res {
Err(err) => {
log::error!("ipc connection closed: {}", err);
break;
}
Ok(Some(Data::ControlledSessionCount(count))) => {
if count != last_count {
last_count = count;
sender.send(Data::ControlledSessionCount(count)).ok();
}
}
_ => {}
}
}
_ = timer.tick() => {
c.send(&Data::ControlledSessionCount(0)).await.ok();
}
}
}
}
hbb_common::sleep(1.).await;
}
}