mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-01-19 00:13:01 +08:00
Merge remote-tracking branch 'rd/master' into feat/x11/clipboard-file/init
Signed-off-by: ClSlaid <cailue@bupt.edu.cn>
This commit is contained in:
commit
796e2ec825
8
.github/workflows/flutter-build.yml
vendored
8
.github/workflows/flutter-build.yml
vendored
@ -1614,8 +1614,8 @@ jobs:
|
||||
# apt install -y flatpak flatpak-builder cmake g++ gcc git curl wget nasm yasm libgtk-3-dev git
|
||||
# # flatpak deps
|
||||
# flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
# flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/21.08
|
||||
# flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/21.08
|
||||
# flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/23.08
|
||||
# flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/23.08
|
||||
# # package
|
||||
# pushd flatpak
|
||||
# git clone https://github.com/flathub/shared-modules.git --depth=1
|
||||
@ -1677,8 +1677,8 @@ jobs:
|
||||
apt install -y flatpak flatpak-builder cmake g++ gcc git curl wget nasm yasm libgtk-3-dev git
|
||||
# flatpak deps
|
||||
flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/21.08
|
||||
flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/21.08
|
||||
flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/23.08
|
||||
flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/23.08
|
||||
# package
|
||||
pushd flatpak
|
||||
git clone https://github.com/flathub/shared-modules.git --depth=1
|
||||
|
@ -11,7 +11,9 @@
|
||||
|
||||
Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
|
||||
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Frustdesk%2Fbounties%3Fstatus%3Dopen)](https://console.algora.io/org/rustdesk/bounties?status=open)
|
||||
|
||||
Yet another remote desktop software, written in Rust. Works out of the box, no configuration required. You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, [set up your own](https://rustdesk.com/server), or [write your own rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "com.rustdesk.RustDesk",
|
||||
"runtime": "org.freedesktop.Platform",
|
||||
"runtime-version": "21.08",
|
||||
"runtime-version": "23.08",
|
||||
"sdk": "org.freedesktop.Sdk",
|
||||
"command": "rustdesk",
|
||||
"icon": "share/icons/hicolor/scalable/apps/rustdesk.svg",
|
||||
|
1
flutter/assets/checkbox-outline.svg
Normal file
1
flutter/assets/checkbox-outline.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1696255389449" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1922" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M435.2 704c-9 0-17.8-3.8-23.8-10.6l-115.2-128c-11.8-13.2-10.8-33.4 2.4-45.2 13.2-11.8 33.4-10.8 45.2 2.4l90.6 100.6 245.2-291.8c11.4-13.6 31.6-15.2 45-4 13.6 11.4 15.2 31.6 4 45l-268.8 320c-6 7-14.6 11.2-24 11.4-0.2 0.2-0.4 0.2-0.6 0.2z" p-id="1923"></path><path d="M800 928H224c-70.6 0-128-57.4-128-128V224c0-70.6 57.4-128 128-128h576c70.6 0 128 57.4 128 128v576c0 70.6-57.4 128-128 128zM224 160c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h576c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64H224z" p-id="1924"></path></svg>
|
After Width: | Height: | Size: 856 B |
Binary file not shown.
1
flutter/assets/chevron_up_chevron_down.svg
Normal file
1
flutter/assets/chevron_up_chevron_down.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1696245886035" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4133" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 132.717714c-9.435429 0-18.852571 3.84-29.147429 12.434286L194.011429 379.574857c-7.277714 6.418286-11.556571 15.433143-11.556572 28.288 0 22.272 16.713143 39.003429 39.424 39.003429 8.996571 0 18.432-3.437714 28.288-11.154286L512 222.281143l261.851429 213.430857c9.874286 7.716571 19.291429 11.154286 28.708571 11.154286 22.308571 0 39.003429-16.731429 39.003429-39.003429 0-12.854857-4.278857-21.869714-11.556572-28.288L541.147429 144.713143c-10.294857-8.137143-19.291429-11.995429-29.147429-11.995429z m0 758.564572c9.856 0 18.852571-3.84 29.147429-11.995429L829.988571 644.425143c7.277714-6.418286 11.556571-15.433143 11.556572-28.288 0-22.272-16.713143-39.424-38.985143-39.424-9.435429 0-18.870857 3.858286-28.708571 11.574857L512 801.718857 250.148571 588.288c-9.874286-7.716571-19.291429-11.574857-28.288-11.574857-22.710857 0-39.424 17.152-39.424 39.424 0 12.854857 4.278857 21.869714 11.556572 28.288l288.859428 234.422857c10.294857 8.594286 19.712 12.434286 29.147429 12.434286z" p-id="4134"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -91,7 +91,6 @@ class IconFont {
|
||||
static const IconData roundClose = IconData(0xe6ed, fontFamily: _family2);
|
||||
static const IconData addressBook =
|
||||
IconData(0xe602, fontFamily: "AddressBook");
|
||||
static const IconData checkbox = IconData(0xe7d6, fontFamily: "CheckBox");
|
||||
}
|
||||
|
||||
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
@ -102,6 +101,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
required this.drag_indicator,
|
||||
required this.shadow,
|
||||
required this.errorBannerBg,
|
||||
required this.me,
|
||||
});
|
||||
|
||||
final Color? border;
|
||||
@ -110,6 +110,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
final Color? drag_indicator;
|
||||
final Color? shadow;
|
||||
final Color? errorBannerBg;
|
||||
final Color? me;
|
||||
|
||||
static final light = ColorThemeExtension(
|
||||
border: Color(0xFFCCCCCC),
|
||||
@ -118,6 +119,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
drag_indicator: Colors.grey[800],
|
||||
shadow: Colors.black,
|
||||
errorBannerBg: Color(0xFFFDEEEB),
|
||||
me: Colors.green,
|
||||
);
|
||||
|
||||
static final dark = ColorThemeExtension(
|
||||
@ -127,6 +129,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
drag_indicator: Colors.grey,
|
||||
shadow: Colors.grey,
|
||||
errorBannerBg: Color(0xFF470F2D),
|
||||
me: Colors.greenAccent,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -137,6 +140,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
Color? drag_indicator,
|
||||
Color? shadow,
|
||||
Color? errorBannerBg,
|
||||
Color? me,
|
||||
}) {
|
||||
return ColorThemeExtension(
|
||||
border: border ?? this.border,
|
||||
@ -145,6 +149,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
drag_indicator: drag_indicator ?? this.drag_indicator,
|
||||
shadow: shadow ?? this.shadow,
|
||||
errorBannerBg: errorBannerBg ?? this.errorBannerBg,
|
||||
me: me ?? this.me,
|
||||
);
|
||||
}
|
||||
|
||||
@ -161,6 +166,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
drag_indicator: Color.lerp(drag_indicator, other.drag_indicator, t),
|
||||
shadow: Color.lerp(shadow, other.shadow, t),
|
||||
errorBannerBg: Color.lerp(shadow, other.errorBannerBg, t),
|
||||
me: Color.lerp(shadow, other.me, t),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -266,11 +272,29 @@ class MyTheme {
|
||||
: EdgeInsets.only(left: dialogPadding / 3);
|
||||
|
||||
static ScrollbarThemeData scrollbarTheme = ScrollbarThemeData(
|
||||
thickness: MaterialStateProperty.all(kScrollbarThickness),
|
||||
thickness: MaterialStateProperty.all(6),
|
||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((states) {
|
||||
if (states.contains(MaterialState.dragged)) {
|
||||
return Colors.grey[900];
|
||||
} else if (states.contains(MaterialState.hovered)) {
|
||||
return Colors.grey[700];
|
||||
} else {
|
||||
return Colors.grey[500];
|
||||
}
|
||||
}),
|
||||
crossAxisMargin: 4,
|
||||
);
|
||||
|
||||
static ScrollbarThemeData scrollbarThemeDark = scrollbarTheme.copyWith(
|
||||
thumbColor: MaterialStateProperty.all(Colors.grey[500]),
|
||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((states) {
|
||||
if (states.contains(MaterialState.dragged)) {
|
||||
return Colors.grey[100];
|
||||
} else if (states.contains(MaterialState.hovered)) {
|
||||
return Colors.grey[300];
|
||||
} else {
|
||||
return Colors.grey[500];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
static ThemeData lightTheme = ThemeData(
|
||||
@ -971,11 +995,22 @@ void msgBox(SessionID sessionId, String type, String title, String text,
|
||||
}));
|
||||
}
|
||||
if (reconnect != null && title == "Connection Error") {
|
||||
buttons.insert(
|
||||
0,
|
||||
dialogButton('Reconnect', isOutline: true, onPressed: () {
|
||||
reconnect(dialogManager, sessionId, false);
|
||||
}));
|
||||
// `enabled` is used to disable the dialog button once the button is clicked.
|
||||
final enabled = true.obs;
|
||||
final button = Obx(
|
||||
() => dialogButton(
|
||||
'Reconnect',
|
||||
isOutline: true,
|
||||
onPressed: enabled.isTrue
|
||||
? () {
|
||||
// Disable the button
|
||||
enabled.value = false;
|
||||
reconnect(dialogManager, sessionId, false);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
);
|
||||
buttons.insert(0, button);
|
||||
}
|
||||
if (link.isNotEmpty) {
|
||||
buttons.insert(0, dialogButton('JumpLink', onPressed: jumplink));
|
||||
@ -2303,7 +2338,7 @@ String getWindowName({WindowType? overrideType}) {
|
||||
}
|
||||
|
||||
String getWindowNameWithId(String id, {WindowType? overrideType}) {
|
||||
return "${DesktopTab.labelGetterAlias(id).value} - ${getWindowName(overrideType: overrideType)}";
|
||||
return "${DesktopTab.tablabelGetter(id).value} - ${getWindowName(overrideType: overrideType)}";
|
||||
}
|
||||
|
||||
Future<void> updateSystemWindowTheme() async {
|
||||
@ -2536,3 +2571,21 @@ Widget buildErrorBanner(BuildContext context,
|
||||
)).marginOnly(bottom: 14),
|
||||
));
|
||||
}
|
||||
|
||||
String getDesktopTabLabel(String peerId, String alias) {
|
||||
String label = alias.isEmpty ? peerId : alias;
|
||||
try {
|
||||
String peer = bind.mainGetPeerSync(id: peerId);
|
||||
Map<String, dynamic> config = jsonDecode(peer);
|
||||
if (config['info']['hostname'] is String) {
|
||||
String hostname = config['info']['hostname'];
|
||||
if (hostname.isNotEmpty &&
|
||||
!label.toLowerCase().contains(hostname.toLowerCase())) {
|
||||
label += "@$hostname";
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("Failed to get hostname:$e");
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dynamic_layouts/dynamic_layouts.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
@ -156,20 +159,31 @@ class _AddressBookState extends State<AddressBook> {
|
||||
} else {
|
||||
tags = gFFI.abModel.tags;
|
||||
}
|
||||
return Wrap(
|
||||
children: tags
|
||||
.map((e) => AddressBookTag(
|
||||
name: e,
|
||||
tags: gFFI.abModel.selectedTags,
|
||||
onTap: () {
|
||||
if (gFFI.abModel.selectedTags.contains(e)) {
|
||||
gFFI.abModel.selectedTags.remove(e);
|
||||
} else {
|
||||
gFFI.abModel.selectedTags.add(e);
|
||||
}
|
||||
}))
|
||||
.toList(),
|
||||
);
|
||||
tagBuilder(String e) {
|
||||
return AddressBookTag(
|
||||
name: e,
|
||||
tags: gFFI.abModel.selectedTags,
|
||||
onTap: () {
|
||||
if (gFFI.abModel.selectedTags.contains(e)) {
|
||||
gFFI.abModel.selectedTags.remove(e);
|
||||
} else {
|
||||
gFFI.abModel.selectedTags.add(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final gridView = DynamicGridView.builder(
|
||||
shrinkWrap: isMobile,
|
||||
gridDelegate: SliverGridDelegateWithWrapping(),
|
||||
itemCount: tags.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final e = tags[index];
|
||||
return tagBuilder(e);
|
||||
});
|
||||
final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
|
||||
return isDesktop
|
||||
? gridView
|
||||
: LimitedBox(maxHeight: maxHeight, child: gridView);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -711,6 +711,13 @@ void showWaitUacDialog(
|
||||
(setState, close, context) => CustomAlertDialog(
|
||||
title: null,
|
||||
content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'),
|
||||
actions: [
|
||||
dialogButton(
|
||||
'OK',
|
||||
icon: Icon(Icons.done_rounded),
|
||||
onPressed: close,
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@ -931,7 +938,7 @@ void showElevationError(SessionID sessionId, String type, String title,
|
||||
dialogButton('Cancel', onPressed: () {
|
||||
close();
|
||||
}, isOutline: true),
|
||||
dialogButton('Retry', onPressed: submit),
|
||||
if (text != 'No permission') dialogButton('Retry', onPressed: submit),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: close,
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/hbbs/hbbs.dart';
|
||||
import 'package:flutter_hbb/common/widgets/login.dart';
|
||||
@ -120,6 +122,7 @@ class _MyGroupState extends State<MyGroup> {
|
||||
}
|
||||
|
||||
Widget _buildLeftHeader() {
|
||||
final fontSize = 14.0;
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@ -128,16 +131,16 @@ class _MyGroupState extends State<MyGroup> {
|
||||
onChanged: (value) {
|
||||
searchUserText.value = value;
|
||||
},
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
style: TextStyle(fontSize: fontSize),
|
||||
decoration: InputDecoration(
|
||||
filled: false,
|
||||
prefixIcon: Icon(
|
||||
Icons.search_rounded,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 10),
|
||||
).paddingOnly(top: 2),
|
||||
hintText: translate("Search"),
|
||||
hintStyle:
|
||||
TextStyle(fontSize: 14, color: Theme.of(context).hintColor),
|
||||
hintStyle: TextStyle(fontSize: fontSize),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
@ -148,16 +151,22 @@ class _MyGroupState extends State<MyGroup> {
|
||||
|
||||
Widget _buildUserContacts() {
|
||||
return Obx(() {
|
||||
return Column(
|
||||
children: gFFI.groupModel.users
|
||||
.where((p0) {
|
||||
if (searchUserText.isNotEmpty) {
|
||||
return p0.name.contains(searchUserText.value);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((e) => _buildUserItem(e))
|
||||
.toList());
|
||||
final items = gFFI.groupModel.users.where((p0) {
|
||||
if (searchUserText.isNotEmpty) {
|
||||
return p0.name
|
||||
.toLowerCase()
|
||||
.contains(searchUserText.value.toLowerCase());
|
||||
}
|
||||
return true;
|
||||
}).toList();
|
||||
final listView = ListView.builder(
|
||||
shrinkWrap: isMobile,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) => _buildUserItem(items[index]));
|
||||
var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
|
||||
return isDesktop
|
||||
? listView
|
||||
: LimitedBox(maxHeight: maxHeight, child: listView);
|
||||
});
|
||||
}
|
||||
|
||||
@ -173,6 +182,7 @@ class _MyGroupState extends State<MyGroup> {
|
||||
() {
|
||||
bool selected = selectedUser.value == username;
|
||||
final isMe = username == gFFI.userModel.userName.value;
|
||||
final colorMe = MyTheme.color(context).me!;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? MyTheme.color(context).highlight : null,
|
||||
@ -184,9 +194,42 @@ class _MyGroupState extends State<MyGroup> {
|
||||
child: Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.person_rounded, color: Colors.grey, size: 16)
|
||||
.marginOnly(right: 4),
|
||||
Expanded(child: Text(isMe ? translate('Me') : username)),
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: str2color(username, 0xAF),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Center(
|
||||
child: Text(
|
||||
username.characters.first.toUpperCase(),
|
||||
style: TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
).marginOnly(right: 4),
|
||||
if (isMe) Flexible(child: Text(username)),
|
||||
if (isMe)
|
||||
Flexible(
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(left: 5),
|
||||
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: colorMe.withAlpha(20),
|
||||
borderRadius: BorderRadius.all(Radius.circular(2)),
|
||||
border: Border.all(color: colorMe.withAlpha(100))),
|
||||
child: Text(
|
||||
translate('Me'),
|
||||
style: TextStyle(
|
||||
color: colorMe.withAlpha(200), fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isMe) Expanded(child: Text(username)),
|
||||
],
|
||||
).paddingSymmetric(vertical: 4),
|
||||
),
|
||||
|
@ -863,12 +863,12 @@ class RecentPeerCard extends BasePeerCard {
|
||||
|
||||
final List favs = (await bind.mainGetFav()).toList();
|
||||
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
@ -917,12 +917,12 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
];
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
@ -971,12 +971,12 @@ class DiscoveredPeerCard extends BasePeerCard {
|
||||
|
||||
final List favs = (await bind.mainGetFav()).toList();
|
||||
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(_wolAction(peer.id));
|
||||
@ -1021,12 +1021,12 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
];
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
@ -1089,18 +1089,18 @@ class MyGroupPeerCard extends BasePeerCard {
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
];
|
||||
if (isDesktop && peer.platform != 'Android') {
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
// menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
menuItems.add(_createShortCutAction(peer.id));
|
||||
}
|
||||
menuItems.add(MenuEntryDivider());
|
||||
// menuItems.add(MenuEntryDivider());
|
||||
// menuItems.add(_renameAction(peer.id));
|
||||
// if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
// menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
|
@ -15,8 +15,10 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/models/ab_model.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:pull_down_button/pull_down_button.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
@ -110,43 +112,14 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
Expanded(
|
||||
child:
|
||||
visibleContextMenuListener(_createSwitchBar(context))),
|
||||
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.group,
|
||||
loading: gFFI.groupModel.groupLoading),
|
||||
_createMultiSelection(),
|
||||
Offstage(
|
||||
offstage: !isDesktop,
|
||||
child: _createPeerViewTypeSwitch(context)),
|
||||
Offstage(
|
||||
offstage: gFFI.peerTabModel.currentTab == 0,
|
||||
child: PeerSortDropdown(),
|
||||
),
|
||||
Offstage(
|
||||
offstage: gFFI.peerTabModel.currentTab != 3,
|
||||
child: _hoverAction(
|
||||
context: context,
|
||||
hoverableWhenfalse: hideAbTagsPanel,
|
||||
child: Tooltip(
|
||||
message: translate('Toggle Tags'),
|
||||
child: Icon(
|
||||
Icons.tag_rounded,
|
||||
size: 18,
|
||||
)),
|
||||
onTap: () async {
|
||||
await bind.mainSetLocalOption(
|
||||
key: "hideAbTagsPanel",
|
||||
value: hideAbTagsPanel.value ? "" : "Y");
|
||||
hideAbTagsPanel.value = !hideAbTagsPanel.value;
|
||||
},
|
||||
),
|
||||
),
|
||||
if (isMobile)
|
||||
..._mobileRightActions(context)
|
||||
else
|
||||
..._desktopRightActions(context)
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
).paddingOnly(right: isDesktop ? 12 : 0),
|
||||
_createPeersView(),
|
||||
],
|
||||
);
|
||||
@ -270,17 +243,20 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
Widget _createMultiSelection() {
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
if (model.currentTabCachedPeers.isEmpty) return Offstage();
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
onTap: () {
|
||||
model.setMultiSelectionMode(true);
|
||||
if (isMobile && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Select'),
|
||||
child: Icon(
|
||||
IconFont.checkbox,
|
||||
size: 18,
|
||||
child: SvgPicture.asset(
|
||||
"assets/checkbox-outline.svg",
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: textColor,
|
||||
)),
|
||||
);
|
||||
@ -564,6 +540,130 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
Tooltip(message: translate('Close'), child: Icon(Icons.clear)))
|
||||
.marginOnly(left: 6);
|
||||
}
|
||||
|
||||
Widget _toggleTags() {
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
hoverableWhenfalse: hideAbTagsPanel,
|
||||
child: Tooltip(
|
||||
message: translate('Toggle Tags'),
|
||||
child: Icon(
|
||||
Icons.tag_rounded,
|
||||
size: 18,
|
||||
)),
|
||||
onTap: () async {
|
||||
await bind.mainSetLocalOption(
|
||||
key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? "" : "Y");
|
||||
hideAbTagsPanel.value = !hideAbTagsPanel.value;
|
||||
});
|
||||
}
|
||||
|
||||
List<Widget> _desktopRightActions(BuildContext context) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return [
|
||||
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
|
||||
_createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading),
|
||||
Offstage(
|
||||
offstage: model.currentTabCachedPeers.isEmpty,
|
||||
child: _createMultiSelection(),
|
||||
),
|
||||
_createPeerViewTypeSwitch(context),
|
||||
Offstage(
|
||||
offstage: model.currentTab == PeerTabIndex.recent.index,
|
||||
child: PeerSortDropdown(),
|
||||
),
|
||||
Offstage(
|
||||
offstage: model.currentTab != PeerTabIndex.ab.index,
|
||||
child: _toggleTags(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _mobileRightActions(BuildContext context) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
|
||||
final leftActionsSize =
|
||||
(leftIconSize + (4 + 4) * 2) * model.visibleIndexs.length;
|
||||
final availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2;
|
||||
final searchWidth = 120;
|
||||
final otherActionWidth = 18 + 10;
|
||||
|
||||
dropDown(List<Widget> menus) {
|
||||
final padding = 6.0;
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return PullDownButton(
|
||||
buttonBuilder:
|
||||
(BuildContext context, Future<void> Function() showMenu) {
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
child: Tooltip(
|
||||
message: translate('More'),
|
||||
child: SvgPicture.asset(
|
||||
"assets/chevron_up_chevron_down.svg",
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: textColor,
|
||||
)),
|
||||
onTap: showMenu,
|
||||
);
|
||||
},
|
||||
routeTheme: PullDownMenuRouteTheme(
|
||||
width: menus.length * (otherActionWidth + padding * 2) * 1.0),
|
||||
itemBuilder: (context) => [
|
||||
PullDownMenuEntryImpl(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: menus
|
||||
.map((e) =>
|
||||
Material(child: e.paddingSymmetric(horizontal: padding)))
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Always show search, refresh
|
||||
List<Widget> actions = [
|
||||
const PeerSearchBar(),
|
||||
if (model.currentTab == PeerTabIndex.ab.index)
|
||||
_createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
||||
if (model.currentTab == PeerTabIndex.group.index)
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading),
|
||||
];
|
||||
final List<Widget> dynamicActions = [
|
||||
if (model.currentTabCachedPeers.isNotEmpty) _createMultiSelection(),
|
||||
if (model.currentTab != PeerTabIndex.recent.index) PeerSortDropdown(),
|
||||
if (model.currentTab == PeerTabIndex.ab.index) _toggleTags()
|
||||
];
|
||||
final rightWidth = availableWidth -
|
||||
searchWidth -
|
||||
(actions.length == 2 ? otherActionWidth : 0);
|
||||
final availablePositions = rightWidth ~/ otherActionWidth;
|
||||
debugPrint(
|
||||
"dynamic action count:${dynamicActions.length}, available positions: $availablePositions");
|
||||
|
||||
if (availablePositions < dynamicActions.length &&
|
||||
dynamicActions.length > 1) {
|
||||
if (availablePositions < 2) {
|
||||
actions.addAll([
|
||||
dropDown(dynamicActions),
|
||||
]);
|
||||
} else {
|
||||
actions.addAll([
|
||||
...dynamicActions.sublist(0, availablePositions - 1),
|
||||
dropDown(dynamicActions.sublist(availablePositions - 1)),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
actions.addAll(dynamicActions);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
class PeerSearchBar extends StatefulWidget {
|
||||
@ -839,3 +939,14 @@ Widget _hoverAction(
|
||||
child: Container(padding: padding, child: child))),
|
||||
);
|
||||
}
|
||||
|
||||
class PullDownMenuEntryImpl extends StatelessWidget
|
||||
implements PullDownMenuEntry {
|
||||
final Widget child;
|
||||
const PullDownMenuEntryImpl({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import 'dart:collection';
|
||||
import 'package:dynamic_layouts/dynamic_layouts.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
@ -95,6 +96,8 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
return width;
|
||||
}();
|
||||
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
_PeersViewState() {
|
||||
_startCheckOnlines();
|
||||
}
|
||||
@ -176,31 +179,52 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
return FutureBuilder<List<Peer>>(
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final peers = snapshot.data!;
|
||||
var peers = snapshot.data!;
|
||||
if (peers.length > 1000) peers = peers.sublist(0, 1000);
|
||||
gFFI.peerTabModel.setCurrentTabCachedPeers(peers);
|
||||
final child = DynamicGridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithWrapping(
|
||||
mainAxisSpacing: space / 2, crossAxisSpacing: space),
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final visibilityChild = VisibilityDetector(
|
||||
key: ValueKey(_cardId(peers[index].id)),
|
||||
onVisibilityChanged: onVisibilityChanged,
|
||||
child: widget.peerCardBuilder(peers[index]),
|
||||
);
|
||||
return isDesktop
|
||||
? Obx(
|
||||
() => SizedBox(
|
||||
width: 220,
|
||||
height: peerCardUiType.value == PeerUiType.grid
|
||||
? 140
|
||||
: 42,
|
||||
child: visibilityChild,
|
||||
),
|
||||
)
|
||||
: SizedBox(width: mobileWidth, child: visibilityChild);
|
||||
},
|
||||
);
|
||||
buildOnePeer(Peer peer) {
|
||||
final visibilityChild = VisibilityDetector(
|
||||
key: ValueKey(_cardId(peer.id)),
|
||||
onVisibilityChanged: onVisibilityChanged,
|
||||
child: widget.peerCardBuilder(peer),
|
||||
);
|
||||
return isDesktop
|
||||
? Obx(
|
||||
() => SizedBox(
|
||||
width: 220,
|
||||
height:
|
||||
peerCardUiType.value == PeerUiType.grid ? 140 : 42,
|
||||
child: visibilityChild,
|
||||
),
|
||||
)
|
||||
: SizedBox(width: mobileWidth, child: visibilityChild);
|
||||
}
|
||||
|
||||
final Widget child;
|
||||
if (isMobile) {
|
||||
child = DynamicGridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithWrapping(
|
||||
mainAxisSpacing: space / 2, crossAxisSpacing: space),
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildOnePeer(peers[index]);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
child = DesktopScrollWrapper(
|
||||
scrollController: _scrollController,
|
||||
child: DynamicGridView.builder(
|
||||
controller: _scrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithWrapping(
|
||||
mainAxisSpacing: space / 2, crossAxisSpacing: space),
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildOnePeer(peers[index]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (updateEvent == UpdateEvent.load) {
|
||||
_curPeers.clear();
|
||||
_curPeers.addAll(peers.map((e) => e.id));
|
||||
|
@ -93,6 +93,7 @@ class _RawTouchGestureDetectorRegionState
|
||||
return;
|
||||
}
|
||||
if (handleTouch) {
|
||||
// Desktop or mobile "Touch mode"
|
||||
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||
inputModel.tapDown(MouseButtons.left);
|
||||
}
|
||||
@ -112,7 +113,10 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||
return;
|
||||
}
|
||||
inputModel.tap(MouseButtons.left);
|
||||
if (!handleTouch) {
|
||||
// Mobile, "Mouse mode"
|
||||
inputModel.tap(MouseButtons.left);
|
||||
}
|
||||
}
|
||||
|
||||
onDoubleTapDown(TapDownDetails d) {
|
||||
|
@ -49,7 +49,8 @@ class TToggleMenu {
|
||||
handleOsPasswordEditIcon(
|
||||
SessionID sessionId, OverlayDialogManager dialogManager) {
|
||||
isEditOsPassword = true;
|
||||
showSetOSPassword(sessionId, false, dialogManager, null, () => isEditOsPassword = false);
|
||||
showSetOSPassword(
|
||||
sessionId, false, dialogManager, null, () => isEditOsPassword = false);
|
||||
}
|
||||
|
||||
handleOsPasswordAction(
|
||||
@ -62,7 +63,8 @@ handleOsPasswordAction(
|
||||
await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ??
|
||||
'';
|
||||
if (password.isEmpty) {
|
||||
showSetOSPassword(sessionId, true, dialogManager, password, () => isEditOsPassword = false);
|
||||
showSetOSPassword(sessionId, true, dialogManager, password,
|
||||
() => isEditOsPassword = false);
|
||||
} else {
|
||||
bind.sessionInputOsPassword(sessionId: sessionId, value: password);
|
||||
}
|
||||
@ -76,7 +78,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
|
||||
List<TTextMenu> v = [];
|
||||
// elevation
|
||||
if (ffi.elevationModel.showRequestMenu) {
|
||||
if (perms['keyboard'] != false && ffi.elevationModel.showRequestMenu) {
|
||||
v.add(
|
||||
TTextMenu(
|
||||
child: Text(translate('Request Elevation')),
|
||||
|
@ -7,7 +7,6 @@ import 'dart:io';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
@ -33,9 +32,6 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
/// Controller for the id input bar.
|
||||
final _idController = IDTextEditingController();
|
||||
|
||||
/// Nested scroll controller
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
Timer? _updateTimer;
|
||||
|
||||
final RxBool _idInputFocused = false.obs;
|
||||
@ -121,30 +117,18 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: DesktopScrollWrapper(
|
||||
scrollController: _scrollController,
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
Row(
|
||||
children: [
|
||||
Flexible(child: _buildRemoteIDTextField(context)),
|
||||
],
|
||||
).marginOnly(top: 22),
|
||||
SizedBox(height: 12),
|
||||
Divider().paddingOnly(right: 12),
|
||||
])),
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: true,
|
||||
child: PeerTabPage().paddingOnly(right: 12.0),
|
||||
)
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Flexible(child: _buildRemoteIDTextField(context)),
|
||||
],
|
||||
).paddingOnly(left: 12.0),
|
||||
),
|
||||
),
|
||||
).marginOnly(top: 22),
|
||||
SizedBox(height: 12),
|
||||
Divider().paddingOnly(right: 12),
|
||||
Expanded(child: PeerTabPage()),
|
||||
],
|
||||
).paddingOnly(left: 12.0)),
|
||||
const Divider(height: 1),
|
||||
buildStatus()
|
||||
],
|
||||
|
@ -499,13 +499,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Timer(const Duration(seconds: 1), () async {
|
||||
updateUrl = await bind.mainGetSoftwareUpdateUrl();
|
||||
if (updateUrl.isNotEmpty) setState(() {});
|
||||
});
|
||||
_updateTimer = periodic_immediate(const Duration(seconds: 1), () async {
|
||||
await gFFI.serverModel.fetchID();
|
||||
final url = await bind.mainGetSoftwareUpdateUrl();
|
||||
if (updateUrl != url) {
|
||||
updateUrl = url;
|
||||
setState(() {});
|
||||
}
|
||||
final error = await bind.mainGetError();
|
||||
if (systemError != error) {
|
||||
systemError = error;
|
||||
|
@ -88,6 +88,11 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
Get.put<RxInt>(selectedIndex, tag: _kSettingPageIndexTag);
|
||||
controller = PageController(initialPage: widget.initialPage);
|
||||
Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
|
||||
controller.addListener(() {
|
||||
if (controller.page != null) {
|
||||
selectedIndex.value = controller.page!.toInt();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -154,7 +159,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
scrollController: controller,
|
||||
child: PageView(
|
||||
controller: controller,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: _children(),
|
||||
)),
|
||||
),
|
||||
@ -330,9 +335,11 @@ class _GeneralState extends State<_General> {
|
||||
child: _OptionCheckBox(context, "Always use software rendering",
|
||||
'allow-always-software-render'),
|
||||
));
|
||||
children.add(
|
||||
_OptionCheckBox(context, 'Check for software update on startup','enable-check-update',
|
||||
isServer: false,
|
||||
children.add(_OptionCheckBox(
|
||||
context,
|
||||
'Check for software update on startup',
|
||||
'enable-check-update',
|
||||
isServer: false,
|
||||
));
|
||||
if (bind.mainShowOption(key: 'allow-linux-headless')) {
|
||||
children.add(_OptionCheckBox(
|
||||
|
@ -99,7 +99,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
controller: tabController,
|
||||
onWindowCloseButton: handleWindowCloseButton,
|
||||
tail: const AddButton().paddingOnly(left: 10),
|
||||
labelGetter: DesktopTab.labelGetterAlias,
|
||||
labelGetter: DesktopTab.tablabelGetter,
|
||||
)),
|
||||
);
|
||||
return Platform.isMacOS || kUseCompatibleUiMode
|
||||
|
@ -266,7 +266,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
}
|
||||
|
||||
void refreshTunnelConfig() async {
|
||||
String peer = await bind.mainGetPeer(id: widget.id);
|
||||
String peer = bind.mainGetPeerSync(id: widget.id);
|
||||
Map<String, dynamic> config = jsonDecode(peer);
|
||||
List<dynamic> infos = config['port_forwards'] as List;
|
||||
List<_PortForward> result = List.empty(growable: true);
|
||||
|
@ -108,7 +108,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
||||
return true;
|
||||
},
|
||||
tail: AddButton().paddingOnly(left: 10),
|
||||
labelGetter: DesktopTab.labelGetterAlias,
|
||||
labelGetter: DesktopTab.tablabelGetter,
|
||||
)),
|
||||
);
|
||||
return Platform.isMacOS || kUseCompatibleUiMode
|
||||
|
@ -209,7 +209,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}");
|
||||
await _renderTexture.destroy(closeSession);
|
||||
// ensure we leave this session, this is a double check
|
||||
bind.sessionEnterOrLeave(sessionId: sessionId, enter: false);
|
||||
_ffi.inputModel.enterOrLeave(false);
|
||||
DesktopMultiWindow.removeListener(this);
|
||||
_ffi.dialogManager.hideMobileActionsOverlay();
|
||||
_ffi.recordingModel.onClose();
|
||||
@ -329,7 +329,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
if (!_rawKeyFocusNode.hasFocus) {
|
||||
_rawKeyFocusNode.requestFocus();
|
||||
}
|
||||
bind.sessionEnterOrLeave(sessionId: sessionId, enter: true);
|
||||
_ffi.inputModel.enterOrLeave(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,7 +349,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
}
|
||||
// See [onWindowBlur].
|
||||
if (!Platform.isWindows) {
|
||||
bind.sessionEnterOrLeave(sessionId: sessionId, enter: false);
|
||||
_ffi.inputModel.enterOrLeave(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -614,7 +614,7 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
} else {
|
||||
final key = cache.updateGetKey(scale);
|
||||
if (!cursor.cachedKeys.contains(key)) {
|
||||
debugPrint("Register custom cursor with key $key");
|
||||
debugPrint("Register custom cursor with key $key (${cache.hotx},${cache.hoty})");
|
||||
// [Safety]
|
||||
// It's ok to call async registerCursor in current synchronous context,
|
||||
// because activating the cursor is also an async call and will always
|
||||
|
@ -210,7 +210,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
onWindowCloseButton: handleWindowCloseButton,
|
||||
tail: const AddButton().paddingOnly(left: 10),
|
||||
pageViewBuilder: (pageView) => pageView,
|
||||
labelGetter: DesktopTab.labelGetterAlias,
|
||||
labelGetter: DesktopTab.tablabelGetter,
|
||||
tabBuilder: (key, icon, label, themeConf) => Obx(() {
|
||||
final connectionType = ConnectionTypeState.find(key);
|
||||
if (!connectionType.isValid()) {
|
||||
@ -415,8 +415,24 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
|
||||
void onRemoveId(String id) async {
|
||||
if (tabController.state.value.tabs.isEmpty) {
|
||||
await WindowController.fromWindowId(windowId()).close();
|
||||
stateGlobal.setFullscreen(false, procWnd: false);
|
||||
// Keep calling until the window status is hidden.
|
||||
//
|
||||
// Workaround for Windows:
|
||||
// If you click other buttons and close in msgbox within a very short period of time, the close may fail.
|
||||
// `await WindowController.fromWindowId(windowId()).close();`.
|
||||
Future<void> loopCloseWindow() async {
|
||||
int c = 0;
|
||||
final windowController = WindowController.fromWindowId(windowId());
|
||||
while (c < 20 &&
|
||||
tabController.state.value.tabs.isEmpty &&
|
||||
(!await windowController.isHidden())) {
|
||||
await windowController.close();
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
c++;
|
||||
}
|
||||
}
|
||||
loopCloseWindow();
|
||||
}
|
||||
ConnectionTypeState.delete(id);
|
||||
_update_remote_count();
|
||||
|
@ -175,34 +175,47 @@ class ConnectionManagerState extends State<ConnectionManager> {
|
||||
],
|
||||
);
|
||||
},
|
||||
pageViewBuilder: (pageView) => Row(
|
||||
children: [
|
||||
Consumer<ChatModel>(
|
||||
builder: (_, model, child) => model.isShowCMSidePage
|
||||
? Expanded(
|
||||
child: buildRemoteBlock(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.dividerColor))),
|
||||
child: buildSidePage()),
|
||||
),
|
||||
flex: (kConnectionManagerWindowSizeOpenChat.width -
|
||||
kConnectionManagerWindowSizeClosedChat
|
||||
.width)
|
||||
.toInt(),
|
||||
)
|
||||
: Offstage(),
|
||||
),
|
||||
Expanded(
|
||||
child: pageView,
|
||||
flex: kConnectionManagerWindowSizeClosedChat.width
|
||||
.toInt() -
|
||||
4 // prevent stretch of the page view when chat is open,
|
||||
),
|
||||
],
|
||||
pageViewBuilder: (pageView) => LayoutBuilder(
|
||||
builder: (context, constrains) {
|
||||
var borderWidth = 0.0;
|
||||
if (constrains.maxWidth >
|
||||
kConnectionManagerWindowSizeClosedChat.width) {
|
||||
borderWidth = kConnectionManagerWindowSizeOpenChat.width -
|
||||
constrains.maxWidth;
|
||||
} else {
|
||||
borderWidth = kConnectionManagerWindowSizeClosedChat.width -
|
||||
constrains.maxWidth;
|
||||
}
|
||||
if (borderWidth < 0 || borderWidth > 50) {
|
||||
borderWidth = 0;
|
||||
}
|
||||
final realClosedWidth =
|
||||
kConnectionManagerWindowSizeClosedChat.width -
|
||||
borderWidth;
|
||||
final realChatPageWidth =
|
||||
constrains.maxWidth - realClosedWidth;
|
||||
return Row(children: [
|
||||
if (constrains.maxWidth >
|
||||
kConnectionManagerWindowSizeClosedChat.width)
|
||||
Consumer<ChatModel>(
|
||||
builder: (_, model, child) => SizedBox(
|
||||
width: realChatPageWidth,
|
||||
child: buildRemoteBlock(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.dividerColor))),
|
||||
child: buildSidePage()),
|
||||
),
|
||||
)),
|
||||
SizedBox(
|
||||
width: realClosedWidth,
|
||||
child:
|
||||
SizedBox(width: realClosedWidth, child: pageView)),
|
||||
]);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -966,8 +979,7 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
|
||||
return PreferredSize(
|
||||
preferredSize: const Size(200, double.infinity),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Obx(
|
||||
() {
|
||||
final jobTable = gFFI.cmFileModel.currentJobTable;
|
||||
|
@ -1053,10 +1053,12 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
FfiModel get ffiModel => widget.ffi.ffiModel;
|
||||
Display get display => ffiModel.display;
|
||||
List<Resolution> get resolutions => pi.resolutions;
|
||||
bool get isWayland => bind.mainCurrentIsWayland();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_getLocalResolutionWayland();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1065,7 +1067,6 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
final visible =
|
||||
ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1);
|
||||
if (!visible) return Offstage();
|
||||
_getLocalResolution();
|
||||
final showOriginalBtn =
|
||||
display.isOriginalResolutionSet && !display.isOriginalResolution;
|
||||
final showFitLocalBtn = !_isRemoteResolutionFitLocal();
|
||||
@ -1101,6 +1102,20 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _getLocalResolutionWayland() async {
|
||||
if (!isWayland) return _getLocalResolution();
|
||||
final window = await window_size.getWindowInfo();
|
||||
final screen = window.screen;
|
||||
if (screen != null) {
|
||||
setState(() {
|
||||
_localResolution = Resolution(
|
||||
screen.frame.width.toInt(),
|
||||
screen.frame.height.toInt(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_getLocalResolution() {
|
||||
_localResolution = null;
|
||||
final String currentDisplay = bind.mainGetCurrentDisplay();
|
||||
|
@ -8,7 +8,6 @@ import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' hide TabBarTheme;
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
@ -267,13 +266,9 @@ class DesktopTab extends StatelessWidget {
|
||||
tabType == DesktopTabType.install;
|
||||
}
|
||||
|
||||
static RxString labelGetterAlias(String peerId) {
|
||||
final opt = 'alias';
|
||||
PeerStringOption.init(peerId, opt, () {
|
||||
final alias = bind.mainGetPeerOptionSync(id: peerId, key: opt);
|
||||
return alias.isEmpty ? peerId : alias;
|
||||
});
|
||||
return PeerStringOption.find(peerId, opt);
|
||||
static RxString tablabelGetter(String peerId) {
|
||||
final alias = bind.mainGetPeerOptionSync(id: peerId, key: 'alias');
|
||||
return RxString(getDesktopTabLabel(peerId, alias));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -921,14 +916,17 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
final labelWidget = Obx(() {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
|
||||
child: Text(
|
||||
translate(widget.label.value),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
: MyTheme.tabbar(context).unSelectedTextColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
child: Tooltip(
|
||||
message: translate(widget.label.value),
|
||||
child: Text(
|
||||
translate(widget.label.value),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
: MyTheme.tabbar(context).unSelectedTextColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
));
|
||||
});
|
||||
|
||||
|
@ -125,8 +125,7 @@ void runMainApp(bool startService) async {
|
||||
bind.pluginSyncUi(syncTo: kAppTypeMain);
|
||||
bind.pluginListReload();
|
||||
}
|
||||
gFFI.abModel.loadCache();
|
||||
gFFI.groupModel.loadCache();
|
||||
await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]);
|
||||
gFFI.userModel.refreshCurrentUser();
|
||||
runApp(App());
|
||||
// Set window option.
|
||||
@ -154,8 +153,7 @@ void runMobileApp() async {
|
||||
await initEnv(kAppTypeMain);
|
||||
if (isAndroid) androidChannelInit();
|
||||
platformFFI.syncAndroidServiceAppDirConfigPath();
|
||||
gFFI.abModel.loadCache();
|
||||
gFFI.groupModel.loadCache();
|
||||
await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]);
|
||||
gFFI.userModel.refreshCurrentUser();
|
||||
runApp(App());
|
||||
}
|
||||
@ -236,19 +234,24 @@ void runConnectionManagerScreen(bool hide) async {
|
||||
listenUniLinks(handleByFlutter: false);
|
||||
}
|
||||
|
||||
bool _isCmReadyToShow = false;
|
||||
|
||||
showCmWindow({bool isStartup = false}) async {
|
||||
if (isStartup) {
|
||||
WindowOptions windowOptions = getHiddenTitleBarWindowOptions(
|
||||
size: kConnectionManagerWindowSizeClosedChat);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
bind.mainHideDocker();
|
||||
await windowManager.show();
|
||||
await Future.wait([windowManager.focus(), windowManager.setOpacity(1)]);
|
||||
// ensure initial window size to be changed
|
||||
await windowManager.setSizeAlignment(
|
||||
kConnectionManagerWindowSizeClosedChat, Alignment.topRight);
|
||||
});
|
||||
} else {
|
||||
await windowManager.waitUntilReadyToShow(windowOptions, null);
|
||||
bind.mainHideDocker();
|
||||
await Future.wait([
|
||||
windowManager.show(),
|
||||
windowManager.focus(),
|
||||
windowManager.setOpacity(1)
|
||||
]);
|
||||
// ensure initial window size to be changed
|
||||
await windowManager.setSizeAlignment(
|
||||
kConnectionManagerWindowSizeClosedChat, Alignment.topRight);
|
||||
_isCmReadyToShow = true;
|
||||
} else if (_isCmReadyToShow) {
|
||||
if (await windowManager.getOpacity() != 1) {
|
||||
await windowManager.setOpacity(1);
|
||||
await windowManager.focus();
|
||||
@ -265,12 +268,12 @@ hideCmWindow({bool isStartup = false}) async {
|
||||
WindowOptions windowOptions = getHiddenTitleBarWindowOptions(
|
||||
size: kConnectionManagerWindowSizeClosedChat);
|
||||
windowManager.setOpacity(0);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
bind.mainHideDocker();
|
||||
await windowManager.minimize();
|
||||
await windowManager.hide();
|
||||
});
|
||||
} else {
|
||||
await windowManager.waitUntilReadyToShow(windowOptions, null);
|
||||
bind.mainHideDocker();
|
||||
await windowManager.minimize();
|
||||
await windowManager.hide();
|
||||
_isCmReadyToShow = true;
|
||||
} else if (_isCmReadyToShow) {
|
||||
if (await windowManager.getOpacity() != 0) {
|
||||
await windowManager.setOpacity(0);
|
||||
bind.mainHideDocker();
|
||||
|
@ -57,7 +57,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
}();
|
||||
}
|
||||
if (isAndroid) {
|
||||
Timer(const Duration(seconds: 5), () async {
|
||||
Timer(const Duration(seconds: 1), () async {
|
||||
_updateUrl = await bind.mainGetSoftwareUpdateUrl();
|
||||
if (_updateUrl.isNotEmpty) setState(() {});
|
||||
});
|
||||
@ -80,7 +80,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
_buildRemoteIDTextField(),
|
||||
])),
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
hasScrollBody: true,
|
||||
child: PeerTabPage(),
|
||||
)
|
||||
],
|
||||
|
@ -421,6 +421,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
);
|
||||
}
|
||||
|
||||
bool get showCursorPaint =>
|
||||
!gFFI.ffiModel.isPeerAndroid && !gFFI.canvasModel.cursorEmbedded;
|
||||
|
||||
Widget getBodyForMobile() {
|
||||
final keyboardIsVisible = keyboardVisibilityController.isVisible;
|
||||
return Container(
|
||||
@ -453,7 +456,7 @@ class _RemotePageState extends State<RemotePage> {
|
||||
),
|
||||
),
|
||||
];
|
||||
if (!gFFI.canvasModel.cursorEmbedded) {
|
||||
if (showCursorPaint) {
|
||||
paints.add(CursorPaint());
|
||||
}
|
||||
return paints;
|
||||
@ -462,7 +465,7 @@ class _RemotePageState extends State<RemotePage> {
|
||||
|
||||
Widget getBodyForDesktopWithListener(bool keyboard) {
|
||||
var paints = <Widget>[ImagePaint()];
|
||||
if (!gFFI.canvasModel.cursorEmbedded) {
|
||||
if (showCursorPaint) {
|
||||
final cursor = bind.sessionGetToggleOptionSync(
|
||||
sessionId: sessionId, arg: 'show-remote-cursor');
|
||||
if (keyboard || cursor) {
|
||||
@ -737,8 +740,8 @@ class CursorPaint extends StatelessWidget {
|
||||
return CustomPaint(
|
||||
painter: ImagePainter(
|
||||
image: m.image ?? preDefaultCursor.image,
|
||||
x: m.x * s - hotx * s + c.x,
|
||||
y: m.y * s - hoty * s + c.y - adjust,
|
||||
x: m.x * s - hotx + c.x,
|
||||
y: m.y * s - hoty + c.y - adjust,
|
||||
scale: 1),
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peers_view.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
@ -105,9 +104,6 @@ class AbModel {
|
||||
if (!quiet) {
|
||||
pullError.value =
|
||||
'${translate('pull_ab_failed_tip')}: ${translate(err.toString())}';
|
||||
if (gFFI.peerTabModel.currentTab != PeerTabIndex.ab.index) {
|
||||
BotToast.showText(contentColor: Colors.red, text: pullError.value);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
abLoading.value = false;
|
||||
@ -132,6 +128,7 @@ class AbModel {
|
||||
'alias': alias,
|
||||
'tags': tags,
|
||||
});
|
||||
_mergePeerFromGroup(peer);
|
||||
peers.add(peer);
|
||||
}
|
||||
|
||||
@ -480,7 +477,7 @@ class AbModel {
|
||||
}
|
||||
}
|
||||
|
||||
loadCache() async {
|
||||
Future<void> loadCache() async {
|
||||
try {
|
||||
if (_cacheLoadOnceFlag || abLoading.value || initialized) return;
|
||||
_cacheLoadOnceFlag = true;
|
||||
@ -573,4 +570,18 @@ class AbModel {
|
||||
peers.clear();
|
||||
await bind.mainClearAb();
|
||||
}
|
||||
|
||||
_mergePeerFromGroup(Peer p) {
|
||||
final g = gFFI.groupModel.peers.firstWhereOrNull((e) => p.id == e.id);
|
||||
if (g == null) return;
|
||||
if (p.username.isEmpty) {
|
||||
p.username = g.username;
|
||||
}
|
||||
if (p.hostname.isEmpty) {
|
||||
p.hostname = g.hostname;
|
||||
}
|
||||
if (p.platform.isEmpty) {
|
||||
p.platform = g.platform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -285,7 +285,10 @@ class ChatModel with ChangeNotifier {
|
||||
await toggleCMSidePage();
|
||||
}
|
||||
|
||||
var _togglingCMSidePage = false; // protect order for await
|
||||
toggleCMSidePage() async {
|
||||
if (_togglingCMSidePage) return false;
|
||||
_togglingCMSidePage = true;
|
||||
if (_isShowCMSidePage) {
|
||||
_isShowCMSidePage = !_isShowCMSidePage;
|
||||
notifyListeners();
|
||||
@ -300,6 +303,7 @@ class ChatModel with ChangeNotifier {
|
||||
_isShowCMSidePage = !_isShowCMSidePage;
|
||||
notifyListeners();
|
||||
}
|
||||
_togglingCMSidePage = false;
|
||||
}
|
||||
|
||||
changeCurrentKey(MessageKey key) {
|
||||
|
@ -133,7 +133,8 @@ class GroupModel {
|
||||
return true;
|
||||
} catch (err) {
|
||||
debugPrint('get accessible users: $err');
|
||||
groupLoadError.value = err.toString();
|
||||
groupLoadError.value =
|
||||
'${translate('pull_group_failed_tip')}: ${translate(err.toString())}';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -172,9 +173,6 @@ class GroupModel {
|
||||
}
|
||||
if (json.containsKey('total')) {
|
||||
if (total == 0) total = json['total'];
|
||||
if (total > 1000) {
|
||||
total = 1000;
|
||||
}
|
||||
if (json.containsKey('data')) {
|
||||
final data = json['data'];
|
||||
if (data is List) {
|
||||
@ -187,9 +185,6 @@ class GroupModel {
|
||||
} else {
|
||||
tmpPeers[index] = peer;
|
||||
}
|
||||
if (tmpPeers.length >= 1000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -198,7 +193,8 @@ class GroupModel {
|
||||
return true;
|
||||
} catch (err) {
|
||||
debugPrint('get accessible peers: $err');
|
||||
groupLoadError.value = err.toString();
|
||||
groupLoadError.value =
|
||||
'${translate('pull_group_failed_tip')}: ${translate(err.toString())}';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -229,7 +225,7 @@ class GroupModel {
|
||||
}
|
||||
}
|
||||
|
||||
loadCache() async {
|
||||
Future<void> loadCache() async {
|
||||
try {
|
||||
if (_cacheLoadOnceFlag || groupLoading.value || initialized) return;
|
||||
_cacheLoadOnceFlag = true;
|
||||
|
@ -202,10 +202,12 @@ class FfiModel with ChangeNotifier {
|
||||
}, sessionId, peerId);
|
||||
updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId);
|
||||
setConnectionType(peerId, data.secure, data.direct);
|
||||
handlePeerInfo(data.peerInfo, peerId);
|
||||
for (var element in data.cursorDataList) {
|
||||
handleCursorData(element);
|
||||
await handlePeerInfo(data.peerInfo, peerId);
|
||||
for (final element in data.cursorDataList) {
|
||||
updateLastCursorId(element);
|
||||
await handleCursorData(element);
|
||||
}
|
||||
updateLastCursorId(data.lastCursorId);
|
||||
handleCursorId(data.lastCursorId);
|
||||
}
|
||||
|
||||
@ -225,9 +227,11 @@ class FfiModel with ChangeNotifier {
|
||||
} else if (name == 'switch_display') {
|
||||
handleSwitchDisplay(evt, sessionId, peerId);
|
||||
} else if (name == 'cursor_data') {
|
||||
updateLastCursorId(evt);
|
||||
await handleCursorData(evt);
|
||||
} else if (name == 'cursor_id') {
|
||||
await handleCursorId(evt);
|
||||
updateLastCursorId(evt);
|
||||
handleCursorId(evt);
|
||||
} else if (name == 'cursor_position') {
|
||||
await parent.target?.cursorModel.updateCursorPosition(evt, peerId);
|
||||
} else if (name == 'clipboard') {
|
||||
@ -265,8 +269,6 @@ class FfiModel with ChangeNotifier {
|
||||
updateBlockInputState(evt, peerId);
|
||||
} else if (name == 'update_privacy_mode') {
|
||||
updatePrivacyMode(evt, sessionId, peerId);
|
||||
} else if (name == 'alias') {
|
||||
handleAliasChanged(evt);
|
||||
} else if (name == 'show_elevation') {
|
||||
final show = evt['show'].toString() == 'true';
|
||||
parent.target?.serverModel.setShowElevation(show);
|
||||
@ -352,13 +354,6 @@ class FfiModel with ChangeNotifier {
|
||||
platformFFI.setEventCallback(startEventListener(sessionId, peerId));
|
||||
}
|
||||
|
||||
handleAliasChanged(Map<String, dynamic> evt) {
|
||||
final rxAlias = PeerStringOption.find(evt['id'], 'alias');
|
||||
if (rxAlias.value != evt['alias']) {
|
||||
rxAlias.value = evt['alias'];
|
||||
}
|
||||
}
|
||||
|
||||
_updateCurDisplay(SessionID sessionId, Display newDisplay) {
|
||||
if (newDisplay != _display) {
|
||||
if (newDisplay.x != _display.x || newDisplay.y != _display.y) {
|
||||
@ -660,9 +655,13 @@ class FfiModel with ChangeNotifier {
|
||||
return d;
|
||||
}
|
||||
|
||||
handleCursorId(Map<String, dynamic> evt) async {
|
||||
updateLastCursorId(Map<String, dynamic> evt) {
|
||||
parent.target?.cursorModel.id = int.parse(evt['id']);
|
||||
}
|
||||
|
||||
handleCursorId(Map<String, dynamic> evt) {
|
||||
cachedPeerData.lastCursorId = evt;
|
||||
await parent.target?.cursorModel.updateCursorId(evt);
|
||||
parent.target?.cursorModel.updateCursorId(evt);
|
||||
}
|
||||
|
||||
handleCursorData(Map<String, dynamic> evt) async {
|
||||
@ -1288,6 +1287,7 @@ class CursorModel with ChangeNotifier {
|
||||
final _cacheKeys = <String>{};
|
||||
double _x = -10000;
|
||||
double _y = -10000;
|
||||
int _id = -1;
|
||||
double _hotx = 0;
|
||||
double _hoty = 0;
|
||||
double _displayOriginX = 0;
|
||||
@ -1296,7 +1296,7 @@ class CursorModel with ChangeNotifier {
|
||||
bool gotMouseControl = true;
|
||||
DateTime _lastPeerMouse = DateTime.now()
|
||||
.subtract(Duration(milliseconds: 3000 * kMouseControlTimeoutMSec));
|
||||
String id = '';
|
||||
String peerId = '';
|
||||
WeakReference<FFI> parent;
|
||||
|
||||
ui.Image? get image => _image;
|
||||
@ -1310,6 +1310,8 @@ class CursorModel with ChangeNotifier {
|
||||
double get hotx => _hotx;
|
||||
double get hoty => _hoty;
|
||||
|
||||
set id(int id) => _id = id;
|
||||
|
||||
bool get isPeerControlProtected =>
|
||||
DateTime.now().difference(_lastPeerMouse).inMilliseconds <
|
||||
kMouseControlTimeoutMSec;
|
||||
@ -1448,32 +1450,33 @@ class CursorModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
updateCursorData(Map<String, dynamic> evt) async {
|
||||
var id = int.parse(evt['id']);
|
||||
_hotx = double.parse(evt['hotx']);
|
||||
_hoty = double.parse(evt['hoty']);
|
||||
var width = int.parse(evt['width']);
|
||||
var height = int.parse(evt['height']);
|
||||
final id = int.parse(evt['id']);
|
||||
final hotx = double.parse(evt['hotx']);
|
||||
final hoty = double.parse(evt['hoty']);
|
||||
final width = int.parse(evt['width']);
|
||||
final height = int.parse(evt['height']);
|
||||
List<dynamic> colors = json.decode(evt['colors']);
|
||||
final rgba = Uint8List.fromList(colors.map((s) => s as int).toList());
|
||||
final image = await img.decodeImageFromPixels(
|
||||
rgba, width, height, ui.PixelFormat.rgba8888);
|
||||
_image = image;
|
||||
if (await _updateCache(rgba, image, id, width, height)) {
|
||||
_images[id] = Tuple3(image, _hotx, _hoty);
|
||||
} else {
|
||||
_hotx = 0;
|
||||
_hoty = 0;
|
||||
}
|
||||
try {
|
||||
// my throw exception, because the listener maybe already dispose
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('WARNING: updateCursorId $id, without notifyListeners(). $e');
|
||||
if (await _updateCache(rgba, image, id, hotx, hoty, width, height)) {
|
||||
_images[id] = Tuple3(image, hotx, hoty);
|
||||
}
|
||||
|
||||
// Update last cursor data.
|
||||
// Do not use the previous `image` and `id`, because `_id` may be changed.
|
||||
_updateCurData();
|
||||
}
|
||||
|
||||
Future<bool> _updateCache(
|
||||
Uint8List rgba, ui.Image image, int id, int w, int h) async {
|
||||
Uint8List rgba,
|
||||
ui.Image image,
|
||||
int id,
|
||||
double hotx,
|
||||
double hoty,
|
||||
int w,
|
||||
int h,
|
||||
) async {
|
||||
Uint8List? data;
|
||||
img2.Image imgOrigin = img2.Image.fromBytes(
|
||||
width: w, height: h, bytes: rgba.buffer, order: img2.ChannelOrder.rgba);
|
||||
@ -1487,33 +1490,45 @@ class CursorModel with ChangeNotifier {
|
||||
}
|
||||
data = imgBytes.buffer.asUint8List();
|
||||
}
|
||||
_cache = CursorData(
|
||||
peerId: this.id,
|
||||
final cache = CursorData(
|
||||
peerId: peerId,
|
||||
id: id,
|
||||
image: imgOrigin,
|
||||
scale: 1.0,
|
||||
data: data,
|
||||
hotxOrigin: _hotx,
|
||||
hotyOrigin: _hoty,
|
||||
hotxOrigin: hotx,
|
||||
hotyOrigin: hoty,
|
||||
width: w,
|
||||
height: h,
|
||||
);
|
||||
_cacheMap[id] = _cache!;
|
||||
_cacheMap[id] = cache;
|
||||
return true;
|
||||
}
|
||||
|
||||
updateCursorId(Map<String, dynamic> evt) async {
|
||||
final id = int.parse(evt['id']);
|
||||
_cache = _cacheMap[id];
|
||||
final tmp = _images[id];
|
||||
bool _updateCurData() {
|
||||
_cache = _cacheMap[_id];
|
||||
final tmp = _images[_id];
|
||||
if (tmp != null) {
|
||||
_image = tmp.item1;
|
||||
_hotx = tmp.item2;
|
||||
_hoty = tmp.item3;
|
||||
notifyListeners();
|
||||
try {
|
||||
// may throw exception, because the listener maybe already dispose
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'WARNING: updateCursorId $_id, without notifyListeners(). $e');
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
updateCursorId(Map<String, dynamic> evt) {
|
||||
if (!_updateCurData()) {
|
||||
debugPrint(
|
||||
'WARNING: updateCursorId $id, cache is ${_cache == null ? "null" : "not null"}. without notifyListeners()');
|
||||
'WARNING: updateCursorId $_id, cache is ${_cache == null ? "null" : "not null"}. without notifyListeners()');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1668,6 +1683,7 @@ class ElevationModel with ChangeNotifier {
|
||||
bool get showRequestMenu => _canElevate && !_running;
|
||||
onPeerInfo(PeerInfo pi) {
|
||||
_canElevate = pi.platform == kPeerPlatformWindows && pi.sasEnabled == false;
|
||||
_running = false;
|
||||
}
|
||||
|
||||
onPortableServiceRunning(Map<String, dynamic> evt) {
|
||||
@ -1756,7 +1772,7 @@ class FFI {
|
||||
connType = ConnType.defaultConn;
|
||||
canvasModel.id = id;
|
||||
imageModel.id = id;
|
||||
cursorModel.id = id;
|
||||
cursorModel.peerId = id;
|
||||
}
|
||||
// If tabWindowId != null, this session is a "tab -> window" one.
|
||||
// Else this session is a new one.
|
||||
@ -1797,7 +1813,7 @@ class FFI {
|
||||
debugPrint('Unreachable, the cached data cannot be decoded.');
|
||||
return;
|
||||
}
|
||||
ffiModel.handleCachedPeerData(data, id);
|
||||
await ffiModel.handleCachedPeerData(data, id);
|
||||
await bind.sessionRefresh(sessionId: sessionId);
|
||||
});
|
||||
isToNewWindowNotified.value = true;
|
||||
|
@ -328,7 +328,7 @@ packages:
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: e51fddf7f3b46d4423b7aa79ba824a45a1ea1b7a
|
||||
resolved-ref: ef03db52a20a7899da135d694c071fa3866c8fb1
|
||||
url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
@ -399,10 +399,10 @@ packages:
|
||||
dynamic_layouts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/dynamic_layouts"
|
||||
ref: "0023d01996576e494094793a6552463f01c5627a"
|
||||
resolved-ref: "0023d01996576e494094793a6552463f01c5627a"
|
||||
url: "https://github.com/flutter/packages.git"
|
||||
path: "."
|
||||
ref: "24cb88413fa5181d949ddacbb30a65d5c459e7d9"
|
||||
resolved-ref: "24cb88413fa5181d949ddacbb30a65d5c459e7d9"
|
||||
url: "https://github.com/21pages/dynamic_layouts.git"
|
||||
source: git
|
||||
version: "0.0.1+1"
|
||||
event_bus:
|
||||
@ -1045,6 +1045,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
pull_down_button:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pull_down_button
|
||||
sha256: "235b302701ce029fd9e9470975069376a6700935bb47a5f1b3ec8a5efba07e6f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3"
|
||||
puppeteer:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1080,12 +1088,11 @@ packages:
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "406b9b0"
|
||||
resolved-ref: "406b9b038b2c1d779f1e7bf609c8c248be247372"
|
||||
url: "https://github.com/Kingtous/rustdesk_screen_retriever.git"
|
||||
source: git
|
||||
version: "0.1.2"
|
||||
name: screen_retriever
|
||||
sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.9"
|
||||
scroll_pos:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1529,10 +1536,10 @@ packages:
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "2c4b242e668acf4e652b09b13f650bcfbbaa3871"
|
||||
resolved-ref: f19acdb008645366339444a359a45c3257c8b32e
|
||||
url: "https://github.com/rustdesk-org/window_manager"
|
||||
source: git
|
||||
version: "0.3.4"
|
||||
version: "0.3.6"
|
||||
window_size:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -102,9 +102,9 @@ dependencies:
|
||||
flex_color_picker: ^3.3.0
|
||||
dynamic_layouts:
|
||||
git:
|
||||
url: https://github.com/flutter/packages.git
|
||||
path: packages/dynamic_layouts
|
||||
ref: 0023d01996576e494094793a6552463f01c5627a
|
||||
url: https://github.com/21pages/dynamic_layouts.git
|
||||
ref: 24cb88413fa5181d949ddacbb30a65d5c459e7d9
|
||||
pull_down_button: ^0.9.3
|
||||
|
||||
dev_dependencies:
|
||||
icons_launcher: ^2.0.4
|
||||
@ -157,9 +157,6 @@ flutter:
|
||||
- family: AddressBook
|
||||
fonts:
|
||||
- asset: assets/address_book.ttf
|
||||
- family: CheckBox
|
||||
fonts:
|
||||
- asset: assets/checkbox.ttf
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
@ -16,7 +16,7 @@ final testClients = [
|
||||
Client(3, false, false, "UserDDDDDDDDDDDd", "441123123", true, false, false)
|
||||
];
|
||||
|
||||
/// flutter run -d {platform} -t lib/cm_test.dart to test cm
|
||||
/// flutter run -d {platform} -t test/cm_test.dart to test cm
|
||||
void main(List<String> args) async {
|
||||
isTest = true;
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
@ -195,6 +195,9 @@ impl Encoder {
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut auto_codec = CodecName::VP9;
|
||||
if av1_useable {
|
||||
auto_codec = CodecName::AV1;
|
||||
}
|
||||
if vp8_useable && System::new_all().total_memory() <= 4 * 1024 * 1024 * 1024 {
|
||||
// 4 Gb
|
||||
auto_codec = CodecName::VP8
|
||||
|
10
src/cli.rs
10
src/cli.rs
@ -75,15 +75,15 @@ impl Interface for Session {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_login_error(&mut self, err: &str) -> bool {
|
||||
fn handle_login_error(&self, err: &str) -> bool {
|
||||
handle_login_error(self.lc.clone(), err, self)
|
||||
}
|
||||
|
||||
fn handle_peer_info(&mut self, pi: PeerInfo) {
|
||||
fn handle_peer_info(&self, pi: PeerInfo) {
|
||||
self.lc.write().unwrap().handle_peer_info(&pi);
|
||||
}
|
||||
|
||||
async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream) {
|
||||
async fn handle_hash(&self, pass: &str, hash: Hash, peer: &mut Stream) {
|
||||
log::info!(
|
||||
"password={}",
|
||||
hbb_common::password_security::temporary_password()
|
||||
@ -92,7 +92,7 @@ impl Interface for Session {
|
||||
}
|
||||
|
||||
async fn handle_login_from_ui(
|
||||
&mut self,
|
||||
&self,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: String,
|
||||
@ -110,7 +110,7 @@ impl Interface for Session {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {
|
||||
async fn handle_test_delay(&self, t: TestDelay, peer: &mut Stream) {
|
||||
handle_test_delay(t, peer).await;
|
||||
}
|
||||
|
||||
|
@ -677,7 +677,7 @@ impl Client {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn try_stop_clipboard(_self_uuid: &uuid::Uuid) {
|
||||
#[cfg(feature = "flutter")]
|
||||
if crate::flutter::other_sessions_running(_self_uuid) {
|
||||
if crate::flutter::sessions::other_sessions_running(_self_uuid) {
|
||||
return;
|
||||
}
|
||||
TEXT_CLIPBOARD_STATE.lock().unwrap().running = false;
|
||||
@ -2417,21 +2417,21 @@ pub trait Interface: Send + Clone + 'static + Sized {
|
||||
/// Send message data to remote peer.
|
||||
fn send(&self, data: Data);
|
||||
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str);
|
||||
fn handle_login_error(&mut self, err: &str) -> bool;
|
||||
fn handle_peer_info(&mut self, pi: PeerInfo);
|
||||
fn handle_login_error(&self, err: &str) -> bool;
|
||||
fn handle_peer_info(&self, pi: PeerInfo);
|
||||
fn on_error(&self, err: &str) {
|
||||
self.msgbox("error", "Error", err, "");
|
||||
}
|
||||
async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream);
|
||||
async fn handle_hash(&self, pass: &str, hash: Hash, peer: &mut Stream);
|
||||
async fn handle_login_from_ui(
|
||||
&mut self,
|
||||
&self,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: String,
|
||||
remember: bool,
|
||||
peer: &mut Stream,
|
||||
);
|
||||
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream);
|
||||
async fn handle_test_delay(&self, t: TestDelay, peer: &mut Stream);
|
||||
|
||||
fn get_login_config_handler(&self) -> Arc<RwLock<LoginConfigHandler>>;
|
||||
|
||||
|
@ -106,7 +106,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn io_loop(&mut self, key: &str, token: &str) {
|
||||
pub async fn io_loop(&mut self, key: &str, token: &str, round: u32) {
|
||||
let mut last_recv_time = Instant::now();
|
||||
let mut received = false;
|
||||
let conn_type = if self.handler.is_file_transfer() {
|
||||
@ -125,6 +125,11 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
.await
|
||||
{
|
||||
Ok((mut peer, direct, pk)) => {
|
||||
self.handler
|
||||
.connection_round_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_connected();
|
||||
self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready
|
||||
self.handler.update_direct(Some(direct));
|
||||
if conn_type == ConnType::DEFAULT_CONN {
|
||||
@ -246,11 +251,21 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler.on_establish_connection_error(err.to_string());
|
||||
}
|
||||
}
|
||||
// set_disconnected_ok is used to check if new connection round is started.
|
||||
let _set_disconnected_ok = self
|
||||
.handler
|
||||
.connection_round_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_disconnected(round);
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Client::try_stop_clipboard(&self.handler.session_id);
|
||||
if _set_disconnected_ok {
|
||||
Client::try_stop_clipboard(&self.handler.session_id);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
{
|
||||
if _set_disconnected_ok {
|
||||
let conn_id = self.client_conn_id;
|
||||
log::debug!("try empty cliprdr for conn_id {}", conn_id);
|
||||
let _ = ContextSend::proc(|context| -> ResultType<()> {
|
||||
@ -803,7 +818,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
job: &fs::TransferJob,
|
||||
elapsed: i32,
|
||||
last_update_jobs_status: &mut (Instant, HashMap<i32, u64>),
|
||||
handler: &mut Session<T>,
|
||||
handler: &Session<T>,
|
||||
) {
|
||||
if elapsed <= 0 {
|
||||
return;
|
||||
@ -830,7 +845,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
job,
|
||||
elapsed,
|
||||
&mut self.last_update_jobs_status,
|
||||
&mut self.handler,
|
||||
&self.handler,
|
||||
);
|
||||
}
|
||||
for job in self.write_jobs.iter() {
|
||||
@ -1305,9 +1320,10 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
Some(misc::Union::Uac(uac)) => {
|
||||
let keyboard = self.handler.server_keyboard_enabled.read().unwrap().clone();
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
if uac {
|
||||
if uac && keyboard {
|
||||
self.handler.msgbox(
|
||||
"on-uac",
|
||||
"Prompt",
|
||||
@ -1326,7 +1342,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
let title = "Prompt";
|
||||
let text = "Please wait for confirmation of UAC...";
|
||||
let link = "";
|
||||
if uac {
|
||||
if uac && keyboard {
|
||||
self.handler.msgbox(msgtype, title, text, link);
|
||||
} else {
|
||||
self.handler.cancel_msgbox(&format!(
|
||||
@ -1337,9 +1353,10 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
Some(misc::Union::ForegroundWindowElevated(elevated)) => {
|
||||
let keyboard = self.handler.server_keyboard_enabled.read().unwrap().clone();
|
||||
#[cfg(feature = "flutter")]
|
||||
{
|
||||
if elevated {
|
||||
if elevated && keyboard {
|
||||
self.handler.msgbox(
|
||||
"on-foreground-elevated",
|
||||
"Prompt",
|
||||
@ -1358,7 +1375,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
let title = "Prompt";
|
||||
let text = "elevated_foreground_window_tip";
|
||||
let link = "";
|
||||
if elevated {
|
||||
if elevated && keyboard {
|
||||
self.handler.msgbox(msgtype, title, text, link);
|
||||
} else {
|
||||
self.handler.cancel_msgbox(&format!(
|
||||
@ -1372,6 +1389,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
if err.is_empty() {
|
||||
self.handler.msgbox("wait-uac", "", "", "");
|
||||
} else {
|
||||
self.handler.cancel_msgbox("wait-uac");
|
||||
self.handler
|
||||
.msgbox("elevation-error", "Elevation Error", &err, "");
|
||||
}
|
||||
|
@ -844,7 +844,7 @@ async fn check_software_update_() -> hbb_common::ResultType<()> {
|
||||
.path()
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.unwrap();
|
||||
.unwrap_or_default();
|
||||
|
||||
let response_url = latest_release_response.url().to_string();
|
||||
|
||||
|
123
src/flutter.rs
123
src/flutter.rs
@ -42,9 +42,10 @@ pub(crate) const APP_TYPE_CM: &str = "main";
|
||||
// pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
||||
// pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
|
||||
|
||||
pub type FlutterSession = Arc<Session<FlutterHandler>>;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub(crate) static ref CUR_SESSION_ID: RwLock<SessionID> = Default::default();
|
||||
pub(crate) static ref SESSIONS: RwLock<HashMap<SessionID, Session<FlutterHandler>>> = Default::default();
|
||||
static ref GLOBAL_EVENT_STREAM: RwLock<HashMap<String, StreamSink<String>>> = Default::default(); // rust to dart event channel
|
||||
}
|
||||
|
||||
@ -287,7 +288,7 @@ impl FlutterHandler {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn close_event_stream(&mut self) {
|
||||
pub(crate) fn close_event_stream(&self) {
|
||||
let mut stream_lock = self.event_stream.write().unwrap();
|
||||
if let Some(stream) = &*stream_lock {
|
||||
stream.add(EventToUI::Event("close".to_owned()));
|
||||
@ -337,13 +338,13 @@ impl FlutterHandler {
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
pub fn register_texture(&mut self, ptr: usize) {
|
||||
pub fn register_texture(&self, ptr: usize) {
|
||||
*self.renderer.read().unwrap().ptr.write().unwrap() = ptr;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
pub fn set_size(&mut self, width: usize, height: usize) {
|
||||
pub fn set_size(&self, width: usize, height: usize) {
|
||||
*self.notify_rendered.write().unwrap() = false;
|
||||
self.renderer.write().unwrap().set_size(width, height);
|
||||
}
|
||||
@ -731,7 +732,7 @@ pub fn session_add(
|
||||
switch_uuid: &str,
|
||||
force_relay: bool,
|
||||
password: String,
|
||||
) -> ResultType<Session<FlutterHandler>> {
|
||||
) -> ResultType<FlutterSession> {
|
||||
LocalConfig::set_remote_id(&id);
|
||||
|
||||
let session: Session<FlutterHandler> = Session {
|
||||
@ -768,11 +769,8 @@ pub fn session_add(
|
||||
.unwrap()
|
||||
.initialize(id.to_owned(), conn_type, switch_uuid, force_relay);
|
||||
|
||||
if let Some(same_id_session) = SESSIONS
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(session_id.to_owned(), session.clone())
|
||||
{
|
||||
let session = Arc::new(session.clone());
|
||||
if let Some(same_id_session) = sessions::add_session(session_id.to_owned(), session.clone()) {
|
||||
log::error!("Should not happen");
|
||||
same_id_session.close();
|
||||
}
|
||||
@ -791,7 +789,7 @@ pub fn session_start_(
|
||||
id: &str,
|
||||
event_stream: StreamSink<EventToUI>,
|
||||
) -> ResultType<()> {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(session_id) {
|
||||
if let Some(session) = sessions::get_session(session_id) {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
log::info!(
|
||||
"Session {} start, render by flutter texture rgba plugin",
|
||||
@ -803,9 +801,10 @@ pub fn session_start_(
|
||||
session.close_event_stream();
|
||||
*session.event_stream.write().unwrap() = Some(event_stream);
|
||||
if !is_pre_added {
|
||||
let session = session.clone();
|
||||
let session = (*session).clone();
|
||||
std::thread::spawn(move || {
|
||||
io_loop(session);
|
||||
let round = session.connection_round_state.lock().unwrap().new_round();
|
||||
io_loop(session, round);
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
@ -816,29 +815,15 @@ pub fn session_start_(
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn update_text_clipboard_required() {
|
||||
let is_required = SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
let is_required = sessions::get_sessions()
|
||||
.iter()
|
||||
.any(|(_id, session)| session.is_text_clipboard_required());
|
||||
.any(|session| session.is_text_clipboard_required());
|
||||
Client::set_is_text_clipboard_required(is_required);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn other_sessions_running(session_id: &SessionID) -> bool {
|
||||
SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.filter(|k| *k != session_id)
|
||||
.count()
|
||||
!= 0
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn send_text_clipboard_msg(msg: Message) {
|
||||
for (_id, session) in SESSIONS.read().unwrap().iter() {
|
||||
for session in sessions::get_sessions() {
|
||||
if session.is_text_clipboard_required() {
|
||||
session.send(Data::Message(msg.clone()));
|
||||
}
|
||||
@ -1051,7 +1036,7 @@ fn char_to_session_id(c: *const char) -> ResultType<SessionID> {
|
||||
|
||||
pub fn session_get_rgba_size(_session_id: SessionID) -> usize {
|
||||
#[cfg(not(feature = "flutter_texture_render"))]
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&_session_id) {
|
||||
if let Some(session) = sessions::get_session(&_session_id) {
|
||||
return session.rgba.read().unwrap().len();
|
||||
}
|
||||
0
|
||||
@ -1060,7 +1045,7 @@ pub fn session_get_rgba_size(_session_id: SessionID) -> usize {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
|
||||
if let Ok(session_id) = char_to_session_id(session_uuid_str) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
return session.get_rgba();
|
||||
}
|
||||
}
|
||||
@ -1069,7 +1054,7 @@ pub extern "C" fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
|
||||
}
|
||||
|
||||
pub fn session_next_rgba(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
return session.next_rgba();
|
||||
}
|
||||
}
|
||||
@ -1077,8 +1062,9 @@ pub fn session_next_rgba(session_id: SessionID) {
|
||||
#[inline]
|
||||
pub fn session_register_texture(_session_id: SessionID, _ptr: usize) {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) {
|
||||
return session.register_texture(_ptr);
|
||||
if let Some(session) = sessions::get_session(&_session_id) {
|
||||
session.register_texture(_ptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1088,11 +1074,7 @@ pub fn push_session_event(
|
||||
name: &str,
|
||||
event: Vec<(&str, &str)>,
|
||||
) -> Option<bool> {
|
||||
SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(session_id)?
|
||||
.push_event(name, event)
|
||||
sessions::get_session(session_id)?.push_event(name, event)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -1141,7 +1123,7 @@ fn session_send_touch_scale(
|
||||
) {
|
||||
match v.get("v").and_then(|s| s.as_i64()) {
|
||||
Some(scale) => {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
@ -1165,7 +1147,7 @@ fn session_send_touch_pan(
|
||||
v.get("y").and_then(|y| y.as_i64()),
|
||||
) {
|
||||
(Some(x), Some(y)) => {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session
|
||||
.send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command);
|
||||
}
|
||||
@ -1214,3 +1196,60 @@ pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
||||
pub enum SessionHook {
|
||||
OnSessionRgba(fn(String, &mut scrap::ImageRgb)),
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_cur_session() -> Option<FlutterSession> {
|
||||
sessions::get_session(&*CUR_SESSION_ID.read().unwrap())
|
||||
}
|
||||
|
||||
// sessions mod is used to avoid the big lock of sessions' map.
|
||||
pub mod sessions {
|
||||
use super::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref SESSIONS: RwLock<HashMap<SessionID, FlutterSession>> = Default::default();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_session(session_id: SessionID, session: FlutterSession) -> Option<FlutterSession> {
|
||||
SESSIONS.write().unwrap().insert(session_id, session)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove_session(session_id: &SessionID) -> Option<FlutterSession> {
|
||||
SESSIONS.write().unwrap().remove(session_id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_session(session_id: &SessionID) -> Option<FlutterSession> {
|
||||
SESSIONS.read().unwrap().get(session_id).cloned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_sessions() -> Vec<FlutterSession> {
|
||||
SESSIONS.read().unwrap().values().cloned().collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_session_by_peer_id(peer_id: &str) -> Option<FlutterSession> {
|
||||
SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.find(|session| session.id == peer_id)
|
||||
.map(|s| s.clone())
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn other_sessions_running(session_id: &SessionID) -> bool {
|
||||
SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.filter(|k| *k != session_id)
|
||||
.count()
|
||||
!= 0
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ use crate::{
|
||||
client::file_trait::FileManager,
|
||||
common::is_keyboard_mode_supported,
|
||||
common::make_fd_to_json,
|
||||
flutter::{self, SESSIONS},
|
||||
flutter::{session_add, session_start_},
|
||||
flutter::{self, session_add, session_start_, sessions},
|
||||
input::*,
|
||||
ui_interface::{self, *},
|
||||
};
|
||||
@ -113,7 +112,7 @@ pub fn session_start(
|
||||
}
|
||||
|
||||
pub fn session_get_remember(session_id: SessionID) -> Option<bool> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_remember())
|
||||
} else {
|
||||
None
|
||||
@ -121,7 +120,7 @@ pub fn session_get_remember(session_id: SessionID) -> Option<bool> {
|
||||
}
|
||||
|
||||
pub fn session_get_toggle_option(session_id: SessionID, arg: String) -> Option<bool> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_toggle_option(arg))
|
||||
} else {
|
||||
None
|
||||
@ -134,7 +133,7 @@ pub fn session_get_toggle_option_sync(session_id: SessionID, arg: String) -> Syn
|
||||
}
|
||||
|
||||
pub fn session_get_option(session_id: SessionID, arg: String) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_option(arg))
|
||||
} else {
|
||||
None
|
||||
@ -148,55 +147,55 @@ pub fn session_login(
|
||||
password: String,
|
||||
remember: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.login(os_username, os_password, password, remember);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_close(session_id: SessionID) {
|
||||
if let Some(mut session) = SESSIONS.write().unwrap().remove(&session_id) {
|
||||
if let Some(session) = sessions::remove_session(&session_id) {
|
||||
session.close_event_stream();
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_refresh(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.refresh_video();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_record_screen(session_id: SessionID, start: bool, width: usize, height: usize) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.record_screen(start, width as _, height as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_record_status(session_id: SessionID, status: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.record_status(status);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_reconnect(session_id: SessionID, force_relay: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.reconnect(force_relay);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_toggle_option(session_id: SessionID, value: String) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
log::warn!("toggle option {}", &value);
|
||||
session.toggle_option(value.clone());
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if SESSIONS.read().unwrap().get(&session_id).is_some() && value == "disable-clipboard" {
|
||||
if sessions::get_session(&session_id).is_some() && value == "disable-clipboard" {
|
||||
crate::flutter::update_text_clipboard_required();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_flutter_option(k))
|
||||
} else {
|
||||
None
|
||||
@ -204,13 +203,13 @@ pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<St
|
||||
}
|
||||
|
||||
pub fn session_set_flutter_option(session_id: SessionID, k: String, v: String) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.save_flutter_option(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_flutter_option_by_peer_id(id: String, k: String) -> Option<String> {
|
||||
if let Some((_, session)) = SESSIONS.read().unwrap().iter().find(|(_, s)| s.id == id) {
|
||||
if let Some(session) = sessions::get_session_by_peer_id(&id) {
|
||||
Some(session.get_flutter_option(k))
|
||||
} else {
|
||||
None
|
||||
@ -239,7 +238,7 @@ pub fn set_local_kb_layout_type(kb_layout_type: String) {
|
||||
}
|
||||
|
||||
pub fn session_get_view_style(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_view_style())
|
||||
} else {
|
||||
None
|
||||
@ -247,13 +246,13 @@ pub fn session_get_view_style(session_id: SessionID) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn session_set_view_style(session_id: SessionID, value: String) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.save_view_style(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_scroll_style(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_scroll_style())
|
||||
} else {
|
||||
None
|
||||
@ -261,13 +260,13 @@ pub fn session_get_scroll_style(session_id: SessionID) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn session_set_scroll_style(session_id: SessionID, value: String) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.save_scroll_style(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_image_quality(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_image_quality())
|
||||
} else {
|
||||
None
|
||||
@ -275,13 +274,13 @@ pub fn session_get_image_quality(session_id: SessionID) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn session_set_image_quality(session_id: SessionID, value: String) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.save_image_quality(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_keyboard_mode(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_keyboard_mode())
|
||||
} else {
|
||||
None
|
||||
@ -290,7 +289,7 @@ pub fn session_get_keyboard_mode(session_id: SessionID) -> Option<String> {
|
||||
|
||||
pub fn session_set_keyboard_mode(session_id: SessionID, value: String) {
|
||||
let mut _mode_updated = false;
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.save_keyboard_mode(value.clone());
|
||||
_mode_updated = true;
|
||||
}
|
||||
@ -301,7 +300,7 @@ pub fn session_set_keyboard_mode(session_id: SessionID, value: String) {
|
||||
}
|
||||
|
||||
pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_reverse_mouse_wheel())
|
||||
} else {
|
||||
None
|
||||
@ -309,13 +308,13 @@ pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String>
|
||||
}
|
||||
|
||||
pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.save_reverse_mouse_wheel(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32>> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
Some(session.get_custom_image_quality())
|
||||
} else {
|
||||
None
|
||||
@ -323,7 +322,7 @@ pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32
|
||||
}
|
||||
|
||||
pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) -> SyncReturn<bool> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
if let Ok(mode) = KeyboardMode::from_str(&mode[..]) {
|
||||
SyncReturn(is_keyboard_mode_supported(
|
||||
&mode,
|
||||
@ -338,31 +337,31 @@ pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) -
|
||||
}
|
||||
|
||||
pub fn session_set_custom_image_quality(session_id: SessionID, value: i32) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.save_custom_image_quality(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_custom_fps(session_id: SessionID, fps: i32) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.set_custom_fps(fps);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_lock_screen(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.lock_screen();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_ctrl_alt_del(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.ctrl_alt_del();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_switch_display(session_id: SessionID, value: i32) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.switch_display(value);
|
||||
}
|
||||
}
|
||||
@ -375,7 +374,7 @@ pub fn session_handle_flutter_key_event(
|
||||
lock_modes: i32,
|
||||
down_or_up: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
let keyboard_mode = session.get_keyboard_mode();
|
||||
session.handle_flutter_key_event(
|
||||
&keyboard_mode,
|
||||
@ -396,7 +395,7 @@ pub fn session_handle_flutter_key_event(
|
||||
// This will cause the keyboard input to take no effect.
|
||||
pub fn session_enter_or_leave(_session_id: SessionID, _enter: bool) -> SyncReturn<()> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&_session_id) {
|
||||
if let Some(session) = sessions::get_session(&_session_id) {
|
||||
let keyboard_mode = session.get_keyboard_mode();
|
||||
if _enter {
|
||||
set_cur_session_id_(_session_id, &keyboard_mode);
|
||||
@ -418,14 +417,14 @@ pub fn session_input_key(
|
||||
shift: bool,
|
||||
command: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
session.input_key(&name, down, press, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_input_string(session_id: SessionID, value: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
session.input_string(&value);
|
||||
}
|
||||
@ -433,33 +432,33 @@ pub fn session_input_string(session_id: SessionID, value: String) {
|
||||
|
||||
// chat_client_mode
|
||||
pub fn session_send_chat(session_id: SessionID, text: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.send_chat(text);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_peer_option(session_id: SessionID, name: String, value: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.set_option(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_peer_option(session_id: SessionID, name: String) -> String {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
return session.get_option(name);
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn session_input_os_password(session_id: SessionID, value: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.input_os_password(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
// File Action
|
||||
pub fn session_read_remote_dir(session_id: SessionID, path: String, include_hidden: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.read_remote_dir(path, include_hidden);
|
||||
}
|
||||
}
|
||||
@ -473,7 +472,7 @@ pub fn session_send_files(
|
||||
include_hidden: bool,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
|
||||
}
|
||||
}
|
||||
@ -486,7 +485,7 @@ pub fn session_set_confirm_override_file(
|
||||
remember: bool,
|
||||
is_upload: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.set_confirm_override_file(act_id, file_num, need_override, remember, is_upload);
|
||||
}
|
||||
}
|
||||
@ -498,7 +497,7 @@ pub fn session_remove_file(
|
||||
file_num: i32,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.remove_file(act_id, path, file_num, is_remote);
|
||||
}
|
||||
}
|
||||
@ -510,7 +509,7 @@ pub fn session_read_dir_recursive(
|
||||
is_remote: bool,
|
||||
show_hidden: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.remove_dir_all(act_id, path, is_remote, show_hidden);
|
||||
}
|
||||
}
|
||||
@ -521,19 +520,19 @@ pub fn session_remove_all_empty_dirs(
|
||||
path: String,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.remove_dir(act_id, path, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_cancel_job(session_id: SessionID, act_id: i32) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.cancel_job(act_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_create_dir(session_id: SessionID, act_id: i32, path: String, is_remote: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.create_dir(act_id, path, is_remote);
|
||||
}
|
||||
}
|
||||
@ -550,14 +549,14 @@ pub fn session_read_local_dir_sync(
|
||||
}
|
||||
|
||||
pub fn session_get_platform(session_id: SessionID, is_remote: bool) -> String {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
return session.get_platform(is_remote);
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn session_load_last_transfer_jobs(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
return session.load_last_jobs();
|
||||
} else {
|
||||
// a tip for flutter dev
|
||||
@ -577,44 +576,44 @@ pub fn session_add_job(
|
||||
include_hidden: bool,
|
||||
is_remote: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.add_job(act_id, path, to, file_num, include_hidden, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_resume_job(session_id: SessionID, act_id: i32, is_remote: bool) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.resume_job(act_id, is_remote);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_elevate_direct(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.elevate_direct();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_elevate_with_logon(session_id: SessionID, username: String, password: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.elevate_with_logon(username, password);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_switch_sides(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.switch_sides();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32, height: i32) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.change_resolution(display, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) {
|
||||
if let Some(session) = sessions::get_session(&_session_id) {
|
||||
session.set_size(_width, _height);
|
||||
}
|
||||
}
|
||||
@ -729,9 +728,9 @@ pub fn main_store_fav(favs: Vec<String>) {
|
||||
store_fav(favs)
|
||||
}
|
||||
|
||||
pub fn main_get_peer(id: String) -> String {
|
||||
pub fn main_get_peer_sync(id: String) -> SyncReturn<String> {
|
||||
let conf = get_peer(id);
|
||||
serde_json::to_string(&conf).unwrap_or("".to_string())
|
||||
SyncReturn(serde_json::to_string(&conf).unwrap_or("".to_string()))
|
||||
}
|
||||
|
||||
pub fn main_get_lan_peers() -> String {
|
||||
@ -823,11 +822,6 @@ pub fn main_set_peer_option_sync(id: String, key: String, value: String) -> Sync
|
||||
}
|
||||
|
||||
pub fn main_set_peer_alias(id: String, alias: String) {
|
||||
main_broadcast_message(&HashMap::from([
|
||||
("name", "alias"),
|
||||
("id", &id),
|
||||
("alias", &alias),
|
||||
]));
|
||||
set_peer_option(id, "alias".to_owned(), alias)
|
||||
}
|
||||
|
||||
@ -1035,31 +1029,31 @@ pub fn session_add_port_forward(
|
||||
remote_host: String,
|
||||
remote_port: i32,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.add_port_forward(local_port, remote_host, remote_port);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_remove_port_forward(session_id: SessionID, local_port: i32) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.remove_port_forward(local_port);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_new_rdp(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.new_rdp();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_request_voice_call(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.request_voice_call();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_close_voice_call(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.close_voice_call();
|
||||
}
|
||||
}
|
||||
@ -1244,20 +1238,20 @@ pub fn session_send_mouse(session_id: SessionID, msg: String) {
|
||||
_ => 0,
|
||||
} << 3;
|
||||
}
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.send_mouse(mask, x, y, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_restart_remote_device(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.restart_remote_device();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> SyncReturn<String> {
|
||||
let res = if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
let res = if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.get_audit_server(typ)
|
||||
} else {
|
||||
"".to_owned()
|
||||
@ -1266,13 +1260,13 @@ pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> Sync
|
||||
}
|
||||
|
||||
pub fn session_send_note(session_id: SessionID, note: String) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.send_note(note)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_alternative_codecs(session_id: SessionID) -> String {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
let (vp8, av1, h264, h265) = session.alternative_codecs();
|
||||
let msg = HashMap::from([("vp8", vp8), ("av1", av1), ("h264", h264), ("h265", h265)]);
|
||||
serde_json::ser::to_string(&msg).unwrap_or("".to_owned())
|
||||
@ -1282,13 +1276,13 @@ pub fn session_alternative_codecs(session_id: SessionID) -> String {
|
||||
}
|
||||
|
||||
pub fn session_change_prefer_codec(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.change_prefer_codec();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
session.ui_handler.on_waiting_for_image_dialog_show();
|
||||
}
|
||||
}
|
||||
@ -1528,7 +1522,7 @@ pub fn main_update_me() -> SyncReturn<bool> {
|
||||
}
|
||||
|
||||
pub fn set_cur_session_id(session_id: SessionID) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(session) = sessions::get_session(&session_id) {
|
||||
set_cur_session_id_(session_id, &session.get_keyboard_mode())
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#[cfg(feature = "flutter")]
|
||||
use crate::flutter::{CUR_SESSION_ID, SESSIONS};
|
||||
use crate::flutter;
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::platform::windows::{get_char_from_vk, get_unicode_from_vk};
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
@ -14,10 +14,8 @@ use rdev::KeyCode;
|
||||
use rdev::{Event, EventType, Key};
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use std::time::SystemTime;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
@ -35,7 +33,7 @@ const OS_LOWER_MACOS: &str = "macos";
|
||||
static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref TO_RELEASE: Arc<Mutex<HashSet<Key>>> = Arc::new(Mutex::new(HashSet::<Key>::new()));
|
||||
static ref TO_RELEASE: Arc<Mutex<HashMap<Key, Event>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||
static ref MODIFIERS_STATE: Mutex<HashMap<Key, bool>> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(Key::ShiftLeft, false);
|
||||
@ -222,11 +220,7 @@ fn get_keyboard_mode() -> String {
|
||||
return session.get_keyboard_mode();
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
if let Some(session) = flutter::get_cur_session() {
|
||||
return session.get_keyboard_mode();
|
||||
}
|
||||
"legacy".to_string()
|
||||
@ -336,12 +330,17 @@ pub fn release_remote_keys(keyboard_mode: &str) {
|
||||
// todo!: client quit suddenly, how to release keys?
|
||||
let to_release = TO_RELEASE.lock().unwrap().clone();
|
||||
TO_RELEASE.lock().unwrap().clear();
|
||||
for key in to_release {
|
||||
let event_type = EventType::KeyRelease(key);
|
||||
let event = event_type_to_event(event_type);
|
||||
// to-do: BUG
|
||||
// Release events should be sent to the corresponding sessions, instead of current session.
|
||||
for (key, mut event) in to_release.into_iter() {
|
||||
event.event_type = EventType::KeyRelease(key);
|
||||
client::process_event(keyboard_mode, &event, None);
|
||||
// If Alt or AltGr is pressed, we need to send another key stoke to release it.
|
||||
// Because the controlled side may hold the alt state, if local window is switched by [Alt + Tab].
|
||||
if key == Key::Alt || key == Key::AltGr {
|
||||
event.event_type = EventType::KeyPress(key);
|
||||
client::process_event(keyboard_mode, &event, None);
|
||||
event.event_type = EventType::KeyRelease(key);
|
||||
client::process_event(keyboard_mode, &event, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -519,7 +518,7 @@ pub fn event_to_key_events(
|
||||
|
||||
match event.event_type {
|
||||
EventType::KeyPress(key) => {
|
||||
TO_RELEASE.lock().unwrap().insert(key);
|
||||
TO_RELEASE.lock().unwrap().insert(key, event.clone());
|
||||
}
|
||||
EventType::KeyRelease(key) => {
|
||||
TO_RELEASE.lock().unwrap().remove(&key);
|
||||
@ -570,30 +569,13 @@ pub fn event_to_key_events(
|
||||
key_events
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn event_type_to_event(event_type: EventType) -> Event {
|
||||
Event {
|
||||
event_type,
|
||||
time: SystemTime::now(),
|
||||
unicode: None,
|
||||
platform_code: 0,
|
||||
position_code: 0,
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
extra_data: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_key_event(key_event: &KeyEvent) {
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
session.send_key_event(key_event);
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
if let Some(session) = flutter::get_cur_session() {
|
||||
session.send_key_event(key_event);
|
||||
}
|
||||
}
|
||||
@ -604,11 +586,7 @@ pub fn get_peer_platform() -> String {
|
||||
return session.peer_platform();
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
if let Some(session) = flutter::get_cur_session() {
|
||||
return session.peer_platform();
|
||||
}
|
||||
"Windows".to_string()
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -535,8 +535,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("List View", "列表视图"),
|
||||
("Select", "选择"),
|
||||
("Toggle Tags", "切换标签"),
|
||||
("pull_ab_failed_tip", "未成功获取地址簿"),
|
||||
("push_ab_failed_tip", "未成功上传地址簿"),
|
||||
("pull_ab_failed_tip", "获取地址簿失败"),
|
||||
("push_ab_failed_tip", "上传地址簿失败"),
|
||||
("synced_peer_readded_tip", "最近会话中存在的设备将会被重新同步到地址簿。"),
|
||||
("Change Color", "更改颜色"),
|
||||
("Primary Color", "基本色"),
|
||||
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", "由于长时间无操作, 连接被自动断开"),
|
||||
("Check for software update on startup", "启动时检查软件更新"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "请升级专业版服务器到{}或更高版本!"),
|
||||
("pull_group_failed_tip", "获取组信息失败"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,23 +538,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "Nepodařilo se obnovit adresář"),
|
||||
("push_ab_failed_tip", "Nepodařilo se synchronizovat adresář se serverem"),
|
||||
("synced_peer_readded_tip", "Zařízení, která byla přítomna v posledních relacích, budou synchronizována zpět do adresáře."),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
("Installation Successful!", ""),
|
||||
("Installation failed!", ""),
|
||||
("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", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("Change Color", "Změna barvy"),
|
||||
("Primary Color", "Základní barva"),
|
||||
("HSV Color", "HSV barva"),
|
||||
("Installation Successful!", "Instalace úspěšná!"),
|
||||
("Installation failed!", "Instalace se nezdařila!"),
|
||||
("Reverse mouse wheel", "Reverzní kolečko myši"),
|
||||
("{} sessions", "{} sezení"),
|
||||
("scam_title", "Možná vás někdo PODVEDL!"),
|
||||
("scam_text1", "Pokud telefonujete s někým, koho NEZNÁTE a komu NEDŮVĚŘUJETE a kdo vás požádal o použití služby RustDesk a její spuštění, nepokračujte v hovoru a okamžitě zavěste."),
|
||||
("scam_text2", "Pravděpodobně se jedná o podvodníka, který se snaží ukrást vaše peníze nebo jiné soukromé informace."),
|
||||
("Don't show again", "Znovu se neukázat"),
|
||||
("I Agree", "Souhlasím"),
|
||||
("Decline", "Odmítnout"),
|
||||
("Timeout in minutes", "Časový limit v minutách"),
|
||||
("auto_disconnect_option_tip", "Automatické ukončení příchozích relací při nečinnosti uživatele"),
|
||||
("Connection failed due to inactivity", "Připojení se nezdařilo z důvodu nečinnosti"),
|
||||
("Check for software update on startup", "Kontrola aktualizace softwaru při spuštění"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Upgradujte prosím RustDesk Server Pro na verzi {} nebo novější!"),
|
||||
("pull_group_failed_tip", "Nepodařilo se obnovit skupinu"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", "Automatische Trennung der Verbindung aufgrund von Inaktivität"),
|
||||
("Check for software update on startup", "Beim Start auf Softwareaktualisierung prüfen"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Bitte aktualisieren Sie RustDesk Server Pro auf die Version {} oder neuer!"),
|
||||
("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Decline", "Decline"),
|
||||
("auto_disconnect_option_tip", "Automatically close incoming sessions on user inactivity"),
|
||||
("Connection failed due to inactivity", "Automatically disconnected due to inactivity"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Please upgrade RustDesk Server Pro to version {} or newer!")
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Please upgrade RustDesk Server Pro to version {} or newer!"),
|
||||
("pull_group_failed_tip", "Failed to refresh group"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", "Desconectar automáticamente por inactividad."),
|
||||
("Check for software update on startup", "Comprobar actualización al iniciar"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "¡Por favor, actualiza RustDesk Server Pro a la versión {} o superior"),
|
||||
("pull_group_failed_tip", "No se ha podido refrescar el grupo"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -317,7 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection not allowed", "Connexion non autorisée"),
|
||||
("Legacy mode", "Mode hérité"),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("Translate mode", "Mode traduction"),
|
||||
("Use permanent password", "Utiliser un mot de passe permanent"),
|
||||
("Use both passwords", "Utiliser les mots de passe unique et permanent"),
|
||||
("Set permanent password", "Définir le mot de passe permanent"),
|
||||
@ -380,7 +380,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable LAN Discovery", "Activer la découverte sur réseau local"),
|
||||
("Deny LAN Discovery", "Interdir la découverte sur réseau local"),
|
||||
("Write a message", "Ecrire un message"),
|
||||
("Prompt", ""),
|
||||
("Prompt", "Annonce"),
|
||||
("Please wait for confirmation of UAC...", "Veuillez attendre la confirmation de l'UAC..."),
|
||||
("elevated_foreground_window_tip", "La fenêtre actuelle de l'appareil distant nécessite des privilèges plus élevés pour fonctionner, elle ne peut donc pas être atteinte par la souris et le clavier. Vous pouvez demander à l'utilisateur distant de réduire la fenêtre actuelle ou de cliquer sur le bouton d'élévation dans la fenêtre de gestion des connexions. Pour éviter ce problème, il est recommandé d'installer le logiciel sur l'appareil distant."),
|
||||
("Disconnected", "Déconnecté"),
|
||||
@ -397,8 +397,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("This PC", "Ce PC"),
|
||||
("or", "ou"),
|
||||
("Continue with", "Continuer avec"),
|
||||
("Elevate", ""),
|
||||
("Zoom cursor", ""),
|
||||
("Elevate", "Autoriser l'accès"),
|
||||
("Zoom cursor", "Augmenter la taille du curseur"),
|
||||
("Accept sessions via password", "Accepter les sessions via mot de passe"),
|
||||
("Accept sessions via click", "Accepter les sessions via clique de confirmation"),
|
||||
("Accept sessions via both", "Accepter les sessions via mot de passe ou clique de confirmation"),
|
||||
@ -541,20 +541,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Change Color", "Modifier la couleur"),
|
||||
("Primary Color", "Couleur primaire"),
|
||||
("HSV Color", "Couleur TSL"),
|
||||
("Installation Successful!", ""),
|
||||
("Installation failed!", ""),
|
||||
("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", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("Installation Successful!", "Installation réussie !"),
|
||||
("Installation failed!", "Échec de l'installation !"),
|
||||
("Reverse mouse wheel", "Inverser le sens de la molette de la souris"),
|
||||
("{} sessions", "{} sessions"),
|
||||
("scam_title", "Vous êtes peut-être victime d'une ESCROQUERIE !"),
|
||||
("scam_text1", "Si vous êtes au téléphone avec quelqu'un QUE VOUS NE CONNAISSEZ PAS et en qui VOUS N'AVEZ PAS CONFIANCE et qui vous a demandé d'utiliser RustDesk et de démarrer le service, ne le faites pas et raccrochez immédiatement."),
|
||||
("scam_text2", "Il s'agit probablement d'un escroc qui tente de vous voler de l'argent ou d'autres informations personnelles."),
|
||||
("Don't show again", "Ne plus montrer"),
|
||||
("I Agree", "J'accepte"),
|
||||
("Decline", "Refuser"),
|
||||
("Timeout in minutes", "Délai d'expiration en minutes"),
|
||||
("auto_disconnect_option_tip", "Fermer automatiquement les sessions entrantes en cas d'inactivité de l'utilisateur"),
|
||||
("Connection failed due to inactivity", "Déconnecté automatiquement pour cause d'inactivité"),
|
||||
("Check for software update on startup", "Vérifier la disponibilité des mises à jour au démarrage"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Veuillez mettre à jour RustDesk Server Pro avec la version {} ou une version plus récente !"),
|
||||
("pull_group_failed_tip", "Échec de l'actualisation du groupe"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", "Secara otomatis akan terputus ketik tidak ada aktivitas."),
|
||||
("Check for software update on startup", "Periksa pembaruan aplikasi saat sistem dinyalakan."),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Silahkan perbarui RustDesk Server Pro ke versi {} atau yang lebih baru!"),
|
||||
("pull_group_failed_tip", "Gagal memperbarui grup"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", "Connessione non riuscita a causa di inattività"),
|
||||
("Check for software update on startup", "All'avvio verifica presenza aggiornamenti programma"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Aggiorna RustDesk Server Pro alla versione {} o successiva!"),
|
||||
("pull_group_failed_tip", "Impossibile aggiornare il gruppo"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Confirmation", "Apstiprinājums"),
|
||||
("TCP Tunneling", "TCP tunelēšana"),
|
||||
("Remove", "Noņemt"),
|
||||
("Refresh random password", "Atsvaidzināt nejaušu paroli"),
|
||||
("Refresh random password", "Atsvaidzināt nejaušo paroli"),
|
||||
("Set your own password", "Iestatiet savu paroli"),
|
||||
("Enable Keyboard/Mouse", "Iespējot tastatūru/peli"),
|
||||
("Enable Clipboard", "Iespējot starpliktuvi"),
|
||||
@ -151,8 +151,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Click to download", "Lejupielādēt"),
|
||||
("Click to update", "Atjaunināt"),
|
||||
("Configure", "Konfigurēt"),
|
||||
("config_acc", "Lai attālināti vadītu savu darbvirsmu, jums ir jāpiešķir RustDesk \"Accessibility\" atļaujas."),
|
||||
("config_screen", "Lai attālināti piekļūtu darbvirsmai, jums ir jāpiešķir RustDesk \"Screen Recording\" atļaujas."),
|
||||
("config_acc", "Lai attālināti vadītu savu darbvirsmu, jums ir jāpiešķir RustDesk \"Pieejamība\" atļaujas."),
|
||||
("config_screen", "Lai attālināti piekļūtu darbvirsmai, jums ir jāpiešķir RustDesk \"Ekrāna tveršana\" atļaujas."),
|
||||
("Installing ...", "Notiek instalēšana..."),
|
||||
("Install", "Uzstādīt"),
|
||||
("Installation", "Instalēšana"),
|
||||
@ -219,7 +219,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_tip", "Man var piekļūt tikai baltajā sarakstā iekļautās IP adreses"),
|
||||
("Login", "Pieslēgties"),
|
||||
("Verify", "Pārbaudīt"),
|
||||
("Remember me", "Atceries mani"),
|
||||
("Remember me", "Atcerēties mani"),
|
||||
("Trust this device", "Uzticēties šai ierīcei"),
|
||||
("Verification code", "Verifikācijas kods"),
|
||||
("verification_tip", "Verifikācijas kods ir nosūtīts uz reģistrēto e-pasta adresi, ievadiet verifikācijas kodu, lai turpinātu pieslēgšanos."),
|
||||
@ -245,7 +245,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Socks5 Proxy", "Socks5 starpniekserveris"),
|
||||
("Hostname", "Resursdatora nosaukums"),
|
||||
("Discovered", "Atklāts"),
|
||||
("install_daemon_tip", "Lai startētu sāknēšanu, ir jāinstalē sistēmas serviss."),
|
||||
("install_daemon_tip", "Lai palaistu pie startēšanas, ir jāinstalē sistēmas serviss."),
|
||||
("Remote ID", "Attālais ID"),
|
||||
("Paste", "Ielīmēt"),
|
||||
("Paste here?", "Ielīmēt šeit?"),
|
||||
@ -284,13 +284,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you accept?", "Vai Jūs pieņemat?"),
|
||||
("Open System Setting", "Atvērt sistēmas iestatījumus"),
|
||||
("How to get Android input permission?", "Kā iegūt Android ievades atļauju?"),
|
||||
("android_input_permission_tip1", "Lai attālā ierīce varētu vadīt jūsu Android ierīci, izmantojot peli vai pieskārienu, jums ir jāatļauj RustDesk izmantot servisu \"Accessibility\"."),
|
||||
("android_input_permission_tip2", "Lūdzu, dodieties uz nākamo sistēmas iestatījumu lapu, atrodiet un ievadiet [Installed Services], ieslēdziet servisu [RustDesk Input]."),
|
||||
("android_input_permission_tip1", "Lai attālā ierīce varētu vadīt jūsu Android ierīci, izmantojot peli vai pieskārienu, jums ir jāatļauj RustDesk izmantot servisu \"Pieejamība\"."),
|
||||
("android_input_permission_tip2", "Lūdzu, dodieties uz nākamo sistēmas iestatījumu lapu, atrodiet un atveriet [Instalētie pakalpojumi], ieslēdziet servisu [RustDesk Input]."),
|
||||
("android_new_connection_tip", "Ir saņemts jauns vadības pieprasījums, kas vēlas kontrolēt jūsu pašreizējo ierīci."),
|
||||
("android_service_will_start_tip", "Ieslēdzot \"Screen Capture\", serviss tiks automātiski startēts, ļaujot citām ierīcēm pieprasīt savienojumu ar jūsu ierīci."),
|
||||
("android_service_will_start_tip", "Ieslēdzot \"Ekrāna tveršana\", serviss tiks automātiski startēts, ļaujot citām ierīcēm pieprasīt savienojumu ar jūsu ierīci."),
|
||||
("android_stop_service_tip", "Pakalpojuma aizvēršana automātiski aizvērs visus izveidotos savienojumus."),
|
||||
("android_version_audio_tip", "Pašreizējā Android versija neatbalsta audio uztveršanu, lūdzu, jauniniet uz Android 10 vai jaunāku versiju."),
|
||||
("android_start_service_tip", "Pieskarieties [Start Service] vai iespējojiet [Screen Capture] atļauju, lai sāktu ekrāna koplietošanas servisu."),
|
||||
("android_start_service_tip", "Pieskarieties [Sākt servisu] vai iespējojiet [Ekrāna tveršana] atļauju, lai sāktu ekrāna koplietošanas servisu."),
|
||||
("android_permission_may_not_change_tip", "Izveidoto savienojumu atļaujas nevar mainīt uzreiz, kamēr nav atkārtoti izveidots savienojums."),
|
||||
("Account", "Konts"),
|
||||
("Overwrite", "Pārrakstīt"),
|
||||
@ -305,7 +305,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Unsupported", "Neatbalstīts"),
|
||||
("Peer denied", "Sesija noraidīta"),
|
||||
("Please install plugins", "Lūdzu, instalējiet spraudņus"),
|
||||
("Peer exit", "Vienādranga izeja"),
|
||||
("Peer exit", "Iziet no attālās ierīces"),
|
||||
("Failed to turn off", "Neizdevās izslēgt"),
|
||||
("Turned off", "Izslēgts"),
|
||||
("In privacy mode", "Privātuma režīmā"),
|
||||
@ -313,9 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Language", "Valoda"),
|
||||
("Keep RustDesk background service", "Saglabāt RustDesk fona servisu"),
|
||||
("Ignore Battery Optimizations", "Ignorēt akumulatora optimizāciju"),
|
||||
("android_open_battery_optimizations_tip", "Ja vēlaties atspējot šo funkciju, lūdzu, dodieties uz nākamo RustDesk lietojumprogrammas iestatījumu lapu, atrodiet un ievadiet [Battery], noņemiet atzīmi no [Unrestricted]"),
|
||||
("Start on Boot", "Sāciet ar sāknēšanu"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Startējiet ekrāna koplietošanas pakalpojumu sāknēšanas laikā, nepieciešamas īpašas atļaujas"),
|
||||
("android_open_battery_optimizations_tip", "Ja vēlaties atspējot šo funkciju, lūdzu, dodieties uz nākamo RustDesk lietojumprogrammas iestatījumu lapu, atrodiet un atveriet [Akumulators], noņemiet atzīmi no [Neierobežots]"),
|
||||
("Start on Boot", "Palaist pie ieslēgšanas"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Startējiet ekrāna koplietošanas pakalpojumu ieslēgšanas laikā, nepieciešamas īpašas atļaujas"),
|
||||
("Connection not allowed", "Savienojums nav atļauts"),
|
||||
("Legacy mode", "Novecojis režīms"),
|
||||
("Map mode", "Kartēšanas režīms"),
|
||||
@ -366,7 +366,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Disconnect all devices?", "Atvienot visas ierīces?"),
|
||||
("Clear", "Notīrīt"),
|
||||
("Audio Input Device", "Audio ievades ierīce"),
|
||||
("Use IP Whitelisting", "Izmantot IP balto sarakstu"),
|
||||
("Use IP Whitelisting", "Izmantot balto IP sarakstu"),
|
||||
("Network", "Tīkls"),
|
||||
("Enable RDP", "Iespējot RDP"),
|
||||
("Pin Toolbar", "Piespraust rīkjoslu"),
|
||||
@ -405,9 +405,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept sessions via click", "Pieņemt sesijas, noklikšķinot"),
|
||||
("Accept sessions via both", "Pieņemt sesijas, izmantojot abus"),
|
||||
("Please wait for the remote side to accept your session request...", "Lūdzu, uzgaidiet, kamēr attālā puse pieņems jūsu sesijas pieprasījumu..."),
|
||||
("One-time Password", "Vienreizēja parole"),
|
||||
("One-time Password", "Vienreizējā parole"),
|
||||
("Use one-time password", "Izmantot vienreizējo paroli"),
|
||||
("One-time password length", "Vienreizējas paroles garums"),
|
||||
("One-time password length", "Vienreizējās paroles garums"),
|
||||
("Request access to your device", "Pieprasīt piekļuvi savai ierīcei"),
|
||||
("Hide connection management window", "Slēpt savienojuma pārvaldības logu"),
|
||||
("hide_cm_tip", "Atļaut paslēpšanu tikai tad, ja akceptējat sesijas, izmantojot paroli un pastāvīgo paroli"),
|
||||
@ -422,8 +422,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Select local keyboard type", "Izvēlēties vietējās tastatūras veidu"),
|
||||
("software_render_tip", "Ja izmantojat Nvidia grafikas karti operētājsistēmā Linux un attālais logs tiek aizvērts uzreiz pēc savienojuma izveides, var palīdzēt pārslēgšanās uz atvērtā koda Nouveau draiveri un izvēle izmantot programmatūras renderēšanu. Nepieciešama programmatūras restartēšana."),
|
||||
("Always use software rendering", "Vienmēr izmantot programmatūras renderēšanu"),
|
||||
("config_input", "Lai vadītu attālo darbvirsmu ar tastatūru, jums ir jāpiešķir RustDesk \"Input Monitoring\" atļaujas."),
|
||||
("config_microphone", "Lai runātu attālināti, jums ir jāpiešķir RustDesk \"Record Audio\" atļaujas."),
|
||||
("config_input", "Lai vadītu attālo darbvirsmu ar tastatūru, jums ir jāpiešķir RustDesk \"Ievades uzraudzība\" atļaujas."),
|
||||
("config_microphone", "Lai runātu attālināti, jums ir jāpiešķir RustDesk \"Ierakstīt audio\" atļaujas."),
|
||||
("request_elevation_tip", "Paaugstinājumu var pieprasīt arī tad, ja attālajā pusē ir kāds cilvēks."),
|
||||
("Wait", "Pagaidiet"),
|
||||
("Elevation Error", "Paaugstinājuma kļūda"),
|
||||
@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Codec", "Kodeks"),
|
||||
("Resolution", "Izšķirtspēja"),
|
||||
("No transfers in progress", "Notiek pārsūtīšana"),
|
||||
("Set one-time password length", "Iestatīt vienreizējas paroles garumu"),
|
||||
("Set one-time password length", "Iestatīt vienreizējās paroles garumu"),
|
||||
("install_cert_tip", "Instalēt RustDesk sertifikātu"),
|
||||
("confirm_install_cert_tip", "Šis ir RustDesk testēšanas sertifikāts, kuram var uzticēties. Sertifikāts tiks izmantots, lai uzticētos un vajadzības gadījumā instalētu RustDesk draiverus."),
|
||||
("RDP Settings", "RDP iestatījumi"),
|
||||
@ -559,5 +559,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", "Automātiski atvienots neaktivitātes dēļ"),
|
||||
("Check for software update on startup", "Startējot pārbaudīt, vai nav programmatūras atjauninājumu"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Lūdzu, jauniniet RustDesk Server Pro uz versiju {} vai jaunāku!"),
|
||||
("pull_group_failed_tip", "Neizdevās atsvaidzināt grupu"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", "Automatycznie rozłącz przy bezczynności"),
|
||||
("Check for software update on startup", "Sprawdź aktualizacje przy starcie programu"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Proszę zaktualizować RustDesk Server Pro do wersji {} lub nowszej!"),
|
||||
("pull_group_failed_tip", "Błąd odświeżania grup"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -555,6 +555,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("auto_disconnect_option_tip", "Автоматически закрывать входящие сеансы при неактивности пользователя"),
|
||||
("Connection failed due to inactivity", "Подключение не выполнено из-за неактивности"),
|
||||
("Check for software update on startup", "Проверять обновления программы при запуске"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Обновите RustDesk Server Pro до версии {} или новее!"),
|
||||
("pull_group_failed_tip", "Невозможно обновить группу"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is empty", "Urklippet är tomt"),
|
||||
("Stop service", "Avsluta tjänsten"),
|
||||
("Change ID", "Byt ID"),
|
||||
("Your new ID", ""),
|
||||
("Your new ID", "Ditt nya ID"),
|
||||
("length %min% to %max%", ""),
|
||||
("starts with a letter", ""),
|
||||
("allowed characters", ""),
|
||||
@ -145,7 +145,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to make direct connection to remote desktop", "Lyckades inte ansluta direkt till fjärrskrivbordet"),
|
||||
("Set Password", "Välj lösenord"),
|
||||
("OS Password", "OS lösenord"),
|
||||
("install_tip", "På grund av UAC, kan inte RustDesk fungera ordentligt på klientsidan. För att undvika UAC, tryck på knappen nedan för att installera RustDesk på systemet."),
|
||||
("install_tip", "På grund av UAC, kan inte RustDesk fungera ordentligt på klientsidan. För att undvika problem med UAC, tryck på knappen nedan för att installera RustDesk på systemet."),
|
||||
("Click to upgrade", "Klicka för att nedgradera"),
|
||||
("Click to download", "Klicka för att ladda ner"),
|
||||
("Click to update", "Klicka för att uppdatera"),
|
||||
@ -213,14 +213,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Closed manually by the peer", "Stängd manuellt av klienten"),
|
||||
("Enable remote configuration modification", "Tillåt fjärrkonfigurering"),
|
||||
("Run without install", "Kör utan installation"),
|
||||
("Connect via relay", ""),
|
||||
("Connect via relay", "Anslut via relay"),
|
||||
("Always connect via relay", "Anslut alltid via relay"),
|
||||
("whitelist_tip", "Bara vitlistade IPs kan koppla upp till mig"),
|
||||
("Login", "Logga in"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("Verify", "Verifiera"),
|
||||
("Remember me", "Kom ihåg mig"),
|
||||
("Trust this device", "Lita på denna enhet"),
|
||||
("Verification code", "Verifikationskod"),
|
||||
("verification_tip", ""),
|
||||
("Logout", "Logga ut"),
|
||||
("Tags", "Taggar"),
|
||||
@ -364,8 +364,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Disconnect all devices?", "Koppla ifrån alla enheter?"),
|
||||
("Clear", "Töm"),
|
||||
("Audio Input Device", "Inmatningsenhet för ljud"),
|
||||
("Use IP Whitelisting", "Använd IP Vitlistning"),
|
||||
("Network", "Nätvärk"),
|
||||
("Use IP Whitelisting", "Använd IP-Vitlistning"),
|
||||
("Network", "Nätverk"),
|
||||
("Enable RDP", "Aktivera RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
355
src/lang/th.rs
355
src/lang/th.rs
@ -37,23 +37,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is empty", "คลิปบอร์ดว่างเปล่า"),
|
||||
("Stop service", "หยุดการใช้งานเซอร์วิส"),
|
||||
("Change ID", "เปลี่ยน ID"),
|
||||
("Your new ID", ""),
|
||||
("length %min% to %max%", ""),
|
||||
("starts with a letter", ""),
|
||||
("allowed characters", ""),
|
||||
("Your new ID", "ID ใหม่ของคุณ"),
|
||||
("length %min% to %max%", "ความยาวตั้งแต่ %min% ถึง %max%"),
|
||||
("starts with a letter", "เริ่มต้นด้วยตัวอักษร"),
|
||||
("allowed characters", "ตัวอักขระที่อนุญาต"),
|
||||
("id_change_tip", "อนุญาตเฉพาะตัวอักษร a-z A-Z 0-9 และ _ (ขีดล่าง) เท่านั้น โดยตัวอักษรขึ้นต้นจะต้องเป็น a-z หรือไม่ก็ A-Z และมีความยาวระหว่าง 6 ถึง 16 ตัวอักษร"),
|
||||
("Website", "เว็บไซต์"),
|
||||
("About", "เกี่ยวกับ"),
|
||||
("Slogan_tip", "ทำด้วยใจ ในโลกใบนี้ที่ยุ่งเหยิง!"),
|
||||
("Slogan_tip", "ทำด้วยใจ ในโลกที่วุ่นวาย!"),
|
||||
("Privacy Statement", "คำแถลงเกี่ยวกับความเป็นส่วนตัว"),
|
||||
("Mute", "ปิดเสียง"),
|
||||
("Build Date", ""),
|
||||
("Version", ""),
|
||||
("Home", ""),
|
||||
("Build Date", "วันที่ Build"),
|
||||
("Version", "เวอร์ชัน"),
|
||||
("Home", "หน้าหลัก"),
|
||||
("Audio Input", "ออดิโออินพุท"),
|
||||
("Enhancements", "การปรับปรุง"),
|
||||
("Hardware Codec", "ฮาร์ดแวร์ codec"),
|
||||
("Adaptive bitrate", "บิทเรทผันแปร"),
|
||||
("Hardware Codec", "ฮาร์ดแวร์ Codec"),
|
||||
("Adaptive bitrate", "Bitrate ผันแปร"),
|
||||
("ID Server", "เซิร์ฟเวอร์ ID"),
|
||||
("Relay Server", "เซิร์ฟเวอร์ Relay"),
|
||||
("API Server", "เซิร์ฟเวอร์ API"),
|
||||
@ -139,9 +139,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Remote desktop is offline", "เดสก์ท็อปปลายทางออฟไลน์"),
|
||||
("Key mismatch", "คีย์ไม่ถูกต้อง"),
|
||||
("Timeout", "หมดเวลา"),
|
||||
("Failed to connect to relay server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์รีเลย์ล้มเหลว"),
|
||||
("Failed to connect to relay server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์ Relay ล้มเหลว"),
|
||||
("Failed to connect via rendezvous server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์นัดพบล้มเหลว"),
|
||||
("Failed to connect via relay server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์รีเลย์ล้มเหลว"),
|
||||
("Failed to connect via relay server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์ Relay ล้มเหลว"),
|
||||
("Failed to make direct connection to remote desktop", "การเชื่อมต่อตรงไปยังเดสก์ท็อปปลายทางล้มเหลว"),
|
||||
("Set Password", "ตั้งรหัสผ่าน"),
|
||||
("OS Password", "รหัสผ่านระบบปฏิบัติการ"),
|
||||
@ -162,7 +162,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Install", "ยอมรับและติดตั้ง"),
|
||||
("End-user license agreement", "ข้อตกลงใบอนุญาตผู้ใช้งาน"),
|
||||
("Generating ...", "กำลังสร้าง ..."),
|
||||
("Your installation is lower version.", "การติดตั้งของคุณเป็นเวอร์ชั่นที่ต่ำกว่า"),
|
||||
("Your installation is lower version.", "การติดตั้งของคุณเป็นเวอร์ชันที่ต่ำกว่า"),
|
||||
("not_close_tcp_tip", "อย่าปิดหน้าต่างนี้ในขณะที่คุณกำลังใช้งานอุโมงค์การเชื่อมต่อ"),
|
||||
("Listening ...", "กำลังรอรับข้อมูล ..."),
|
||||
("Remote Host", "โฮสต์ปลายทาง"),
|
||||
@ -185,9 +185,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Allow file copy and paste", "อนุญาตให้มีการคัดลอกและวางไฟล์"),
|
||||
("Connected", "เชื่อมต่อแล้ว"),
|
||||
("Direct and encrypted connection", "การเชื่อมต่อตรงที่มีการเข้ารหัส"),
|
||||
("Relayed and encrypted connection", "การเชื่อมต่อแบบรีเลย์ที่มีการเข้ารหัส"),
|
||||
("Relayed and encrypted connection", "การเชื่อมต่อแบบ Relay ที่มีการเข้ารหัส"),
|
||||
("Direct and unencrypted connection", "การเชื่อมต่อตรงที่ไม่มีการเข้ารหัส"),
|
||||
("Relayed and unencrypted connection", "การเชื่อมต่อแบบรีเลย์ที่ไม่มีการเข้ารหัส"),
|
||||
("Relayed and unencrypted connection", "การเชื่อมต่อแบบ Relay ที่ไม่มีการเข้ารหัส"),
|
||||
("Enter Remote ID", "กรอก ID ปลายทาง"),
|
||||
("Enter your password", "กรอกรหัสผ่าน"),
|
||||
("Logging in...", "กำลังเข้าสู่ระบบ..."),
|
||||
@ -210,18 +210,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Settings", "ตั้งค่า"),
|
||||
("Username", "ชื่อผู้ใช้งาน"),
|
||||
("Invalid port", "พอร์ทไม่ถูกต้อง"),
|
||||
("Closed manually by the peer", "ถูกปิดโดยอีกฝั่งการการเชื่อมต่อ"),
|
||||
("Closed manually by the peer", "ถูกปิดโดยอีกฝั่งของการเชื่อมต่อ"),
|
||||
("Enable remote configuration modification", "เปิดการใช้งานการแก้ไขการตั้งค่าปลายทาง"),
|
||||
("Run without install", "ใช้งานโดยไม่ต้องติดตั้ง"),
|
||||
("Connect via relay", ""),
|
||||
("Always connect via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"),
|
||||
("Connect via relay", "เชื่อมต่อผ่าน Relay"),
|
||||
("Always connect via relay", "เชื่อมต่อผ่าน Relay เสมอ"),
|
||||
("whitelist_tip", "อนุญาตเฉพาะการเชื่อมต่อจาก IP ที่ไวท์ลิสต์"),
|
||||
("Login", "เข้าสู่ระบบ"),
|
||||
("Verify", ""),
|
||||
("Remember me", ""),
|
||||
("Trust this device", ""),
|
||||
("Verification code", ""),
|
||||
("verification_tip", ""),
|
||||
("Verify", "ยืนยันความถูกต้อง"),
|
||||
("Remember me", "จดจำฉัน"),
|
||||
("Trust this device", "เชื่อถืออุปกรณ์นี้"),
|
||||
("Verification code", "รหัสยืนยันความถูกต้อง"),
|
||||
("verification_tip", "รหัสยืนยันความถูกต้องได้ถูกส่งไปยังอีเมล์ที่ลงทะเบียนแล้ว กรุณากรอกรหัสยืนยันความถูกต้องเพื่อดำเนินการเข้าสู่ระบบต่อ"),
|
||||
("Logout", "ออกจากระบบ"),
|
||||
("Tags", "แท็ก"),
|
||||
("Search ID", "ค้นหา ID"),
|
||||
@ -233,7 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "ไม่พบข้อมูลผู้ใช้งาน"),
|
||||
("Password missed", "ไม่พบรหัสผ่าน"),
|
||||
("Wrong credentials", "ข้อมูลสำหรับเข้าสู่ระบบไม่ถูกต้อง"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("The verification code is incorrect or has expired", "รหัสยืนยันไม่ถูกต้องหรือหมดอายุแล้ว"),
|
||||
("Edit Tag", "แก้ไขแท็ก"),
|
||||
("Forget Password", "ยกเลิกการจดจำรหัสผ่าน"),
|
||||
("Favorites", "รายการโปรด"),
|
||||
@ -249,7 +249,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Paste", "วาง"),
|
||||
("Paste here?", "วางที่นี่หรือไม่?"),
|
||||
("Are you sure to close the connection?", "คุณแน่ใจหรือไม่ที่จะปิดการเชื่อมต่อ?"),
|
||||
("Download new version", "ดาวน์โหลดเวอร์ชั่นใหม่"),
|
||||
("Download new version", "ดาวน์โหลดเวอร์ชันใหม่"),
|
||||
("Touch mode", "โหมดการสัมผัส"),
|
||||
("Mouse mode", "โหมดการใช้เมาส์"),
|
||||
("One-Finger Tap", "แตะนิ้วเดียว"),
|
||||
@ -275,9 +275,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Total", "รวม"),
|
||||
("items", "รายการ"),
|
||||
("Selected", "ถูกเลือก"),
|
||||
("Screen Capture", "แคปเจอร์หน้าจอ"),
|
||||
("Screen Capture", "บันทึกหน้าจอ"),
|
||||
("Input Control", "ควบคุมอินพุท"),
|
||||
("Audio Capture", "แคปเจอร์เสียง"),
|
||||
("Audio Capture", "บันทึกเสียง"),
|
||||
("File Connection", "การเชื่อมต่อไฟล์"),
|
||||
("Screen Connection", "การเชื่อมต่อหน้าจอ"),
|
||||
("Do you accept?", "ยอมรับหรือไม่?"),
|
||||
@ -288,9 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_new_connection_tip", "ได้รับคำขอควบคุมใหม่ที่ต้องการควบคุมอุปกรณ์ของคุณ"),
|
||||
("android_service_will_start_tip", "การเปิดการใช้งาน \"การบันทึกหน้าจอ\" จะเป็นการเริ่มต้นการทำงานของเซอร์วิสโดยอัตโนมัติ ที่จะอนุญาตให้อุปกรณ์อื่นๆ ส่งคำขอเข้าถึงมายังอุปกรณ์ของคุณได้"),
|
||||
("android_stop_service_tip", "การปิดการใช้งานเซอร์วิสจะปิดการเชื่อมต่อทั้งหมดโดยอัตโนมัติ"),
|
||||
("android_version_audio_tip", "เวอร์ชั่นแอนดรอยด์ปัจจุบันของคุณไม่รองรับการบันทึกข้อมูลเสียง กรุณาอัปเกรดเป็นแอนดรอยด์เวอร์ชั่น 10 หรือสูงกว่า"),
|
||||
("android_start_service_tip", ""),
|
||||
("android_permission_may_not_change_tip", ""),
|
||||
("android_version_audio_tip", "เวอร์ชันแอนดรอยด์ปัจจุบันของคุณไม่รองรับการบันทึกข้อมูลเสียง กรุณาอัปเกรดเป็นแอนดรอยด์เวอร์ชัน 10 หรือสูงกว่า"),
|
||||
("android_start_service_tip", "แตะ [เริ่มต้นใช้งานเซอร์วิส] หรือเปิดสิทธิ์การใช้งาน [บันทึกหน้าจอ] เพื่อเริ่มต้นใช้งานเซอร์วิสสำหรับการแบ่งปันหน้าจอ"),
|
||||
("android_permission_may_not_change_tip", "สิทธิ์การใช้งานสำหรับการเชื่อมต่อที่กำลังเปิดใช้งานอยู่อาจจะไม่ได้เปลี่ยนแปลงในทันทีจนกว่าจะเริ่มต้นการเชื่อมต่อใหม่อีกครั้ง"),
|
||||
("Account", "บัญชี"),
|
||||
("Overwrite", "เขียนทับ"),
|
||||
("This file exists, skip or overwrite this file?", "พบไฟล์ที่มีอยู่แล้ว ต้องการเขียนทับหรือไม่?"),
|
||||
@ -312,12 +312,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"),
|
||||
("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"),
|
||||
("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"),
|
||||
("Start on Boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Start on Boot", "เริ่มต้นเมื่อเปิดเครื่อง"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "เริ่มต้นใช้งานเซอร์วิสสำหรับการแบ่งปันหน้าจอเมื่อเปิดเครื่อง (ต้องมีการให้สิทธิ์การใช้งานพิเศษเพิ่มเติม)"),
|
||||
("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("Legacy mode", "โหมดดั้งเดิม"),
|
||||
("Map mode", "โหมดการจับคู่"),
|
||||
("Translate mode", "โหมดแปลงค่า"),
|
||||
("Use permanent password", "ใช้รหัสผ่านถาวร"),
|
||||
("Use both passwords", "ใช้รหัสผ่านทั้งสองแบบ"),
|
||||
("Set permanent password", "ตั้งค่ารหัสผ่านถาวร"),
|
||||
@ -337,10 +337,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Ratio", "อัตราส่วน"),
|
||||
("Image Quality", "คุณภาพภาพ"),
|
||||
("Scroll Style", "ลักษณะการเลื่อน"),
|
||||
("Show Toolbar", ""),
|
||||
("Hide Toolbar", ""),
|
||||
("Show Toolbar", "แสดงแถบเครื่องมือ"),
|
||||
("Hide Toolbar", "ซ่อนแถบเครื่องมือ"),
|
||||
("Direct Connection", "การเชื่อมต่อตรง"),
|
||||
("Relay Connection", "การเชื่อมต่อแบบรีเลย์"),
|
||||
("Relay Connection", "การเชื่อมต่อแบบ Relay "),
|
||||
("Secure Connection", "การเชื่อมต่อที่ปลอดภัย"),
|
||||
("Insecure Connection", "การเชื่อมต่อที่ไม่ปลอดภัย"),
|
||||
("Scale original", "ขนาดเดิม"),
|
||||
@ -349,7 +349,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Security", "ความปลอดภัย"),
|
||||
("Theme", "ธีม"),
|
||||
("Dark Theme", "ธีมมืด"),
|
||||
("Light Theme", ""),
|
||||
("Light Theme", "ธีมสว่าง"),
|
||||
("Dark", "มืด"),
|
||||
("Light", "สว่าง"),
|
||||
("Follow System", "ตามระบบ"),
|
||||
@ -367,8 +367,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Use IP Whitelisting", "ใช้งาน IP ไวท์ลิสต์"),
|
||||
("Network", "เครือข่าย"),
|
||||
("Enable RDP", "เปิดการใช้งาน RDP"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Pin Toolbar", "ปักหมุดแถบเครื่องมือ"),
|
||||
("Unpin Toolbar", "ยกเลิกการปักหมุดแถบเครื่องมือ"),
|
||||
("Recording", "การบันทึก"),
|
||||
("Directory", "ไดเรกทอรี่"),
|
||||
("Automatically record incoming sessions", "บันทึกเซสชันขาเข้าโดยอัตโนมัติ"),
|
||||
@ -389,12 +389,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keyboard Settings", "การตั้งค่าคีย์บอร์ด"),
|
||||
("Full Access", "การเข้าถึงทั้งหมด"),
|
||||
("Screen Share", "การแชร์จอ"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland ต้องการ Ubuntu เวอร์ชั่น 21.04 หรือสูงกว่า"),
|
||||
("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 ต้องการลินุกซ์เวอร์ชันที่สูงกว่านี้ กรุณาเปลี่ยนไปใช้เดสก์ท็อป X11 หรือเปลี่ยนระบบปฏิบัติการของคุณ"),
|
||||
("JumpLink", "View"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "กรุณาเลือกหน้าจอที่ต้องการแชร์ (ใช้งานในอีกฝั่งของการเชื่อมต่อ)"),
|
||||
("Show RustDesk", "แสดง RustDesk"),
|
||||
("This PC", ""),
|
||||
("This PC", "พีซีเครื่องนี้"),
|
||||
("or", "หรือ"),
|
||||
("Continue with", "ทำต่อด้วย"),
|
||||
("Elevate", "ยกระดับ"),
|
||||
@ -418,143 +418,144 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Closed manually by web console", "ถูกปิดโดยเว็บคอนโซล"),
|
||||
("Local keyboard type", "ประเภทคีย์บอร์ด"),
|
||||
("Select local keyboard type", "เลือกประเภทคีย์บอร์ด"),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", ""),
|
||||
("config_input", ""),
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", ""),
|
||||
("Elevation Error", ""),
|
||||
("Ask the remote user for authentication", ""),
|
||||
("Choose this if the remote account is administrator", ""),
|
||||
("Transmit the username and password of administrator", ""),
|
||||
("still_click_uac_tip", ""),
|
||||
("Request Elevation", ""),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", ""),
|
||||
("uppercase", ""),
|
||||
("lowercase", ""),
|
||||
("digit", ""),
|
||||
("special character", ""),
|
||||
("length>=8", ""),
|
||||
("Weak", ""),
|
||||
("Medium", ""),
|
||||
("Strong", ""),
|
||||
("Switch Sides", ""),
|
||||
("Please confirm if you want to share your desktop?", ""),
|
||||
("Display", ""),
|
||||
("Default View Style", ""),
|
||||
("Default Scroll Style", ""),
|
||||
("Default Image Quality", ""),
|
||||
("Default Codec", ""),
|
||||
("software_render_tip", "ถ้าคุณใช้กราฟิกการ์ดกับระบบ Linux และหน้าต่างของเครื่องปลายทางปิดในทันทีหลังจากการเชื่อมต่อ การเปลี่ยนไปใช้ไดรเวอร์ Nouveau และเลือกใช้โหมดการเรนเดอร์แบบซอฟท์แวร์อาจช่วยได้ (ต้องรีสตาร์ทโปรแกรม)"),
|
||||
("Always use software rendering", "ใช้การเรนเดอร์แบบซอฟท์แวร์เสมอ"),
|
||||
("config_input", "เพื่อที่จะควบคุมเครื่องเดสก์ท็อปปลายทางด้วยคีย์บอร์ด คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การตรวจสอบ Input\" ให้แก่ Rustdesk"),
|
||||
("config_microphone", "เพื่อที่จะส่งเสียงพูดไปยังปลายทาง คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การบันทึกเสียง\" ให้แก่ Rustdesk"),
|
||||
("request_elevation_tip", "คุณสามารถขอยกระดับสิทธิ์การใช้งานได้ หากมีผู้ใช้งานอยู่ในฝั่งเครื่องปลายทาง"),
|
||||
("Wait", "รอ"),
|
||||
("Elevation Error", "การยกระดับสิทธิ์การใช้งานผิดพลาด"),
|
||||
("Ask the remote user for authentication", "ขอความช่วยเหลือผู้ใช้งานปลายทางเพื่อพิสูจน์ตัวตน"),
|
||||
("Choose this if the remote account is administrator", "เลือกข้อนี้ถ้าบัญชีผู้ใช้งานปลายทางเป็นผู้ดูแลระบบ"),
|
||||
("Transmit the username and password of administrator", "ส่งข้อมูลผู้ใช้งานและรหัสผ่านของผู้ดูแลระบบ"),
|
||||
("still_click_uac_tip", "ผู้ใช้งานปลายทางยังจำเป็นที่จะต้องกดปุ่ม ตกลง บนหน้าต่าง UAC ของ Rustdesk"),
|
||||
("Request Elevation", "ขอยกระดับสิทธิ์การใช้งาน"),
|
||||
("wait_accept_uac_tip", "กรุณารอผู้ใช้งานปลายทางกดยินยอมหน้าต่าง UAC"),
|
||||
("Elevate successfully", "การยกระดับสิทธิ์การใช้งานสำเร็จ"),
|
||||
("uppercase", "พิมพ์ใหญ่"),
|
||||
("lowercase", "พิมพ์เล็ก"),
|
||||
("digit", "หลัก"),
|
||||
("special character", "อักขระพิเศษ"),
|
||||
("length>=8", "ความยาวมากกว่า 8"),
|
||||
("Weak", "ไม่ปลอดภัย"),
|
||||
("Medium", "กลาง"),
|
||||
("Strong", "ปลอดภัย"),
|
||||
("Switch Sides", "สลับฝั่ง"),
|
||||
("Please confirm if you want to share your desktop?", "กรุณายืนยันว่าคุณต้องการแบ่งปันหน้าเดสก์ท็อปของคุณ"),
|
||||
("Display", "จอแสดงผล"),
|
||||
("Default View Style", "แสดงผลแบบเริ่มต้น"),
|
||||
("Default Scroll Style", "การเลื่อนแบบเริ่มต้น"),
|
||||
("Default Image Quality", "คุณภาพของภาพแบบเริ่มต้น"),
|
||||
("Default Codec", "Codec เริ่มต้น"),
|
||||
("Bitrate", ""),
|
||||
("FPS", ""),
|
||||
("Auto", ""),
|
||||
("Other Default Options", ""),
|
||||
("Voice call", ""),
|
||||
("Text chat", ""),
|
||||
("Stop voice call", ""),
|
||||
("relay_hint_tip", ""),
|
||||
("Reconnect", ""),
|
||||
("Auto", "อัตโนมัติ"),
|
||||
("Other Default Options", "ตัวเลือกเริ่มต้นอื่นๆ"),
|
||||
("Voice call", "การโทรด้วยเสียง"),
|
||||
("Text chat", "การสนทนาด้วยข้อความ"),
|
||||
("Stop voice call", "หยุดการโทรด้วยเสียง"),
|
||||
("relay_hint_tip", "การเชื่อมต่อโดยตรงอาจเป็นไปไม่ได้ ดังนั้นคุณสามารถลองเชื่อมต่อผ่าน Relay หรือตั้งค่าให้เชื่อมต่อผ่าน Relay เป็นค่าเริ่มต้น คุณสามารถเพิ่ม \"/r\" ต่อท้ายไปยัง ID หรือเลือกตัวเลือก \"เชื่อมต่อผ่าน Relay เสมอ\" ในการ์ดของการเชื่อมต่อล่าสุด (ถ้ามี)"),
|
||||
("Reconnect", "เชื่อมต่ออีกครั้ง"),
|
||||
("Codec", ""),
|
||||
("Resolution", ""),
|
||||
("No transfers in progress", ""),
|
||||
("Set one-time password length", ""),
|
||||
("install_cert_tip", ""),
|
||||
("confirm_install_cert_tip", ""),
|
||||
("RDP Settings", ""),
|
||||
("Sort by", ""),
|
||||
("New Connection", ""),
|
||||
("Restore", ""),
|
||||
("Minimize", ""),
|
||||
("Maximize", ""),
|
||||
("Your Device", ""),
|
||||
("empty_recent_tip", ""),
|
||||
("empty_favorite_tip", ""),
|
||||
("empty_lan_tip", ""),
|
||||
("empty_address_book_tip", ""),
|
||||
("eg: admin", ""),
|
||||
("Empty Username", ""),
|
||||
("Empty Password", ""),
|
||||
("Me", ""),
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", ""),
|
||||
("login_linux_tip", ""),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", ""),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", ""),
|
||||
("System Sound", ""),
|
||||
("Default", ""),
|
||||
("New RDP", ""),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
("resolution_original_tip", ""),
|
||||
("resolution_fit_local_tip", ""),
|
||||
("resolution_custom_tip", ""),
|
||||
("Collapse toolbar", ""),
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
("logout_tip", ""),
|
||||
("Service", ""),
|
||||
("Start", ""),
|
||||
("Stop", ""),
|
||||
("exceed_max_devices", ""),
|
||||
("Sync with recent sessions", ""),
|
||||
("Sort tags", ""),
|
||||
("Open connection in new tab", ""),
|
||||
("Move tab to new window", ""),
|
||||
("Can not be empty", ""),
|
||||
("Already exists", ""),
|
||||
("Change Password", ""),
|
||||
("Refresh Password", ""),
|
||||
("Resolution", "ความละเอียด"),
|
||||
("No transfers in progress", "ไม่มีการถ่ายโอนในขณะนี้"),
|
||||
("Set one-time password length", "ตั้งค่าความยาวรหัสผ่านครั้งเดียว"),
|
||||
("install_cert_tip", "ติดตั้งใบรับรองของ Rustdesk"),
|
||||
("confirm_install_cert_tip", "นี่คือใบรับของทดสอบของ Rustdesk ซึ่งสามารถเชื่อถือได้ ใบรับรองนี้จะถูกใช้ในการติดตั้ง Driver ของ Rustdesk เมื่อจำเป็น"),
|
||||
("RDP Settings", "การตั้งค่า RDP"),
|
||||
("Sort by", "เรียงลำดับโดย"),
|
||||
("New Connection", "การเชื่อมต่อใหม่"),
|
||||
("Restore", "คืนค่า"),
|
||||
("Minimize", "ย่อ"),
|
||||
("Maximize", "ขยาย"),
|
||||
("Your Device", "อุปกรณ์ของคุณ"),
|
||||
("empty_recent_tip", "คุณยังไม่มีการเชื่อมต่อล่าสุด ได้เวลาวางแผนเพื่อเริ่มต้นแล้ว"),
|
||||
("empty_favorite_tip", "ยังไม่มีการเชื่อมต่อรายการโปรดเหรอ? มาเริ่มต้นหาใครซักคนเพื่อเชื่อมต่อด้วย และเพิ่มเข้าไปยังรายการโปรดของคุณกัน"),
|
||||
("empty_lan_tip", "ไม่นะ ดูเหมือนว่าเราจะยังไม่พบใครตรงนี้"),
|
||||
("empty_address_book_tip", "ดูเหมือนว่าคุณยังไม่มีใครถูกบันทึกในสมุดรายชื่อของคุณ"),
|
||||
("eg: admin", "เช่น ผู้ดูแลระบบ"),
|
||||
("Empty Username", "ชื่อผู้ใช้งานว่างเปล่า"),
|
||||
("Empty Password", "รหัสผ่านว่างเปล่า"),
|
||||
("Me", "ฉัน"),
|
||||
("identical_file_tip", "ไฟล์นี้เหมือนกับไฟล์ของอีกฝั่ง"),
|
||||
("show_monitors_tip", "แสดงหน้าจอในแถบเครื่องมือ"),
|
||||
("View Mode", "โหมดการดู"),
|
||||
("login_linux_tip", "คุณจำเป็นจะต้องเข้าสู่ระบบไปยังบัญชีลินุกซ์ปลายทางเพื่อใช้งานเดสก์ท็อปเซสชัน X"),
|
||||
("verify_rustdesk_password_tip", "ยืนยันความถูกต้องรหัสผ่านของ Rustdesk"),
|
||||
("remember_account_tip", "จดจำบัญชีนี้"),
|
||||
("os_account_desk_tip", "บัญชีนี้จะถูกใช้ในการเข้าสู่ระบบเครื่องปลายทางและเริ่มใช้งานเดสก์ท็อปเซสชันแบบ headless"),
|
||||
("OS Account", "บัญชีระบบปฏิบัติการ"),
|
||||
("another_user_login_title_tip", "ผู้ใช้งานอื่นเข้าสู่ระบบอยู่แล้ว"),
|
||||
("another_user_login_text_tip", "ยกเลิกการเชื่อมต่อ"),
|
||||
("xorg_not_found_title_tip", "ไม่พบ Xorg"),
|
||||
("xorg_not_found_text_tip", "กรุณาติดตั้ง Xorg"),
|
||||
("no_desktop_title_tip", "ไม่มีหน้าเดสก์ท็อปที่ใช้งานได้"),
|
||||
("no_desktop_text_tip", "กรุณาติดตั้ง GNOME เดสกท็อป"),
|
||||
("No need to elevate", "ไม่จำเป็นต้องยกระดับสิทธิ์การใช้งาน"),
|
||||
("System Sound", "เสียงของระบบ"),
|
||||
("Default", "ค่าเริ่มต้น"),
|
||||
("New RDP", "RDP ใหม่"),
|
||||
("Fingerprint", "ลายนิ้วมือ"),
|
||||
("Copy Fingerprint", "คัดลอกลายนิ้วมือ"),
|
||||
("no fingerprints", "ไม่มีลายนิ้วมือ"),
|
||||
("Select a peer", "เลือกผู้ใช้งาน"),
|
||||
("Select peers", "เลือกผู้ใช้งาน"),
|
||||
("Plugins", "ปลั๊กอิน"),
|
||||
("Uninstall", "ถอนการติดตั้ง"),
|
||||
("Update", "อัปเดต"),
|
||||
("Enable", "เปิดใช้งาน"),
|
||||
("Disable", "ปิดใช้งาน"),
|
||||
("Options", "ตัวเลือก"),
|
||||
("resolution_original_tip", "ความละเอียดดั้งเดิม"),
|
||||
("resolution_fit_local_tip", "ความละเอียดตามต้นทาง"),
|
||||
("resolution_custom_tip", "ความละเอียดแบบกำหนดเอง"),
|
||||
("Collapse toolbar", "พับแถบเครื่องมือ"),
|
||||
("Accept and Elevate", "ยอมรับ และยกระดับสิทธิ์การใช้งาน"),
|
||||
("accept_and_elevate_btn_tooltip", "ยอมรับการเชื่อมต่อ และยกระดับสิทธิ์ UAC"),
|
||||
("clipboard_wait_response_timeout_tip", "หมดเวลารอการตอบสนองของการคัดลอก"),
|
||||
("Incoming connection", "การเชื่อมต่อขาเข้า"),
|
||||
("Outgoing connection", "การเชื่อมต่อขาออก"),
|
||||
("Exit", "ออก"),
|
||||
("Open", "เปิด"),
|
||||
("logout_tip", "คุณแน่ใจที่จะออกจากระบบหรือไม่?"),
|
||||
("Service", "เซอร์วิส"),
|
||||
("Start", "เริ่ม"),
|
||||
("Stop", "หยุด"),
|
||||
("exceed_max_devices", "จำนวนอุปกรณ์ที่จัดการของคุณเต็มจำนวนแล้ว"),
|
||||
("Sync with recent sessions", "Sync กับเซสชันล่าสุด"),
|
||||
("Sort tags", "เรียงแท็ก"),
|
||||
("Open connection in new tab", "เริ่มการเชื่อมต่อในแท็บใหม่"),
|
||||
("Move tab to new window", "ย้ายแท็บไปหน้าต่างใหม่"),
|
||||
("Can not be empty", "ไม่สามารถเว้นว่างได้"),
|
||||
("Already exists", "มีอยู่แล้ว"),
|
||||
("Change Password", "เปลี่ยนรหัสผ่าน"),
|
||||
("Refresh Password", "รีเฟรชรหัสผ่าน"),
|
||||
("ID", ""),
|
||||
("Grid View", ""),
|
||||
("List View", ""),
|
||||
("Select", ""),
|
||||
("Toggle Tags", ""),
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
("Installation Successful!", ""),
|
||||
("Installation failed!", ""),
|
||||
("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", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("Grid View", "มุมมองแบบช่อง"),
|
||||
("List View", "มุมมองแบบรายการ"),
|
||||
("Select", "เลือก"),
|
||||
("Toggle Tags", "สลับแท็ก"),
|
||||
("pull_ab_failed_tip", "การรีเฟรชสมุดรายชื่อล้มเหลว"),
|
||||
("push_ab_failed_tip", "การ Sync สมุดรายชื่อไปยังเซิร์ฟเวอร์ล้มเหลว"),
|
||||
("synced_peer_readded_tip", "อุปกรณ์ที่อยู่ในรายการล่าสุดจะถูก sync กลับไปยังสมุดรายชื่อ"),
|
||||
("Change Color", "เปลี่ยนสี"),
|
||||
("Primary Color", "สีหลัก"),
|
||||
("HSV Color", "สี HSV"),
|
||||
("Installation Successful!", "การติดตั้งเสร็จสมบูรณ์"),
|
||||
("Installation failed!", "การติดตั้งล้มเหลว"),
|
||||
("Reverse mouse wheel", "เลื่อมลูกกลิ้งเมาส์แบบกลับด้าน"),
|
||||
("{} sessions", "{} เซสชัน"),
|
||||
("scam_title", "คุณอาจกำลังถูกหลอกลวง!"),
|
||||
("scam_text1", "ถ้าคุณกำลังคุยโทรศัพท์กับคนที่คุณไม่รู้จักและไม่ไว้ใจ และคนๆนั้นกำลังขอให้คุณเปิดใช้งาน Rustdesk อย่าทำตามและรีบวางสายในทันที"),
|
||||
("scam_text2", "เขาเหล่านั้นอาจเป็นมิจฉาชีพที่กำลังพยายามจะขโมยเงินและข้อมูลส่วนตัวของคุณ"),
|
||||
("Don't show again", "อย่าแสดงอีก"),
|
||||
("I Agree", "ยอมรับ"),
|
||||
("Decline", "ปฏิเสธ"),
|
||||
("Timeout in minutes", "หมดเวลาในอีกซักครู่"),
|
||||
("auto_disconnect_option_tip", "ยกเลิกการเชื่อมต่ออัตโนมัติในกรณีที่ผู้ใช้งานไม่มีการเคลื่อนไหว"),
|
||||
("Connection failed due to inactivity", "การเชื่อมต่อล้มเหลวเนื่องจากไม่มีการเคลื่อนไหว"),
|
||||
("Check for software update on startup", "ตรวจสอบการอัปเดตโปรแกรมเมื่อเริ่มต้นใช้งาน"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "กรุณาอัปเดต Rustdesk Server Pro ไปยังเวอร์ชัน {} หรือใหม่กว่า!"),
|
||||
("pull_group_failed_tip", "การเรียกใช้งานกลุ่มล้มเหลว"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "最佳化反應時間"),
|
||||
("Custom", "自訂"),
|
||||
("Show remote cursor", "顯示遠端游標"),
|
||||
("Show quality monitor", "顯示質量監測"),
|
||||
("Show quality monitor", "顯示品質監測"),
|
||||
("Disable clipboard", "停用剪貼簿"),
|
||||
("Lock after session end", "工作階段結束後鎖定電腦"),
|
||||
("Insert", "插入"),
|
||||
@ -445,7 +445,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Display", "顯示"),
|
||||
("Default View Style", "預設顯示方式"),
|
||||
("Default Scroll Style", "預設滾動方式"),
|
||||
("Default Image Quality", "預設圖像質量"),
|
||||
("Default Image Quality", "預設圖像品質"),
|
||||
("Default Codec", "預設編解碼器"),
|
||||
("Bitrate", "位元速率"),
|
||||
("FPS", "幀率"),
|
||||
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection failed due to inactivity", ""),
|
||||
("Check for software update on startup", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ use hbb_common::{
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::OsStr,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::{Child, Command},
|
||||
@ -195,17 +196,29 @@ fn start_uinput_service() {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_start_server_(user: Option<(String, String)>) -> ResultType<Option<Child>> {
|
||||
if user.is_some() {
|
||||
run_as_user(vec!["--server"], user)
|
||||
} else {
|
||||
Ok(Some(crate::run_me(vec!["--server"])?))
|
||||
fn try_start_server_(desktop: Option<&Desktop>) -> ResultType<Option<Child>> {
|
||||
match desktop {
|
||||
Some(desktop) => {
|
||||
let mut envs = vec![];
|
||||
if !desktop.display.is_empty() {
|
||||
envs.push(("DISPLAY", desktop.display.clone()));
|
||||
}
|
||||
if !desktop.xauth.is_empty() {
|
||||
envs.push(("XAUTHORITY", desktop.xauth.clone()));
|
||||
}
|
||||
run_as_user(
|
||||
vec!["--server"],
|
||||
Some((desktop.uid.clone(), desktop.username.clone())),
|
||||
envs,
|
||||
)
|
||||
}
|
||||
None => Ok(Some(crate::run_me(vec!["--server"])?)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_server(user: Option<(String, String)>, server: &mut Option<Child>) {
|
||||
match try_start_server_(user) {
|
||||
fn start_server(desktop: Option<&Desktop>, server: &mut Option<Child>) {
|
||||
match try_start_server_(desktop) {
|
||||
Ok(ps) => *server = ps,
|
||||
Err(err) => {
|
||||
log::error!("Failed to start server: {}", err);
|
||||
@ -257,6 +270,7 @@ fn stop_subprocess() {
|
||||
|
||||
fn should_start_server(
|
||||
try_x11: bool,
|
||||
is_display_changed: bool,
|
||||
uid: &mut String,
|
||||
desktop: &Desktop,
|
||||
cm0: &mut bool,
|
||||
@ -273,7 +287,7 @@ fn should_start_server(
|
||||
*uid = "".to_owned();
|
||||
should_kill = true;
|
||||
}
|
||||
} else if desktop.uid != *uid && !desktop.uid.is_empty() {
|
||||
} else if is_display_changed || desktop.uid != *uid && !desktop.uid.is_empty() {
|
||||
*uid = desktop.uid.clone();
|
||||
if try_x11 {
|
||||
set_x11_env(&desktop);
|
||||
@ -335,6 +349,7 @@ pub fn start_os_service() {
|
||||
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
let (mut display, mut xauth): (String, String) = ("".to_owned(), "".to_owned());
|
||||
let mut desktop = Desktop::default();
|
||||
let mut sid = "".to_owned();
|
||||
let mut uid = "".to_owned();
|
||||
@ -357,8 +372,10 @@ pub fn start_os_service() {
|
||||
// try kill subprocess "--server"
|
||||
stop_server(&mut user_server);
|
||||
// try start subprocess "--server"
|
||||
// No need to check is_display_changed here.
|
||||
if should_start_server(
|
||||
true,
|
||||
false,
|
||||
&mut uid,
|
||||
&desktop,
|
||||
&mut cm0,
|
||||
@ -373,9 +390,14 @@ pub fn start_os_service() {
|
||||
// try kill subprocess "--server"
|
||||
stop_server(&mut server);
|
||||
|
||||
let is_display_changed = desktop.display != display || desktop.xauth != xauth;
|
||||
display = desktop.display.clone();
|
||||
xauth = desktop.xauth.clone();
|
||||
|
||||
// try start subprocess "--server"
|
||||
if should_start_server(
|
||||
false,
|
||||
is_display_changed,
|
||||
&mut uid,
|
||||
&desktop,
|
||||
&mut cm0,
|
||||
@ -384,10 +406,7 @@ pub fn start_os_service() {
|
||||
) {
|
||||
stop_subprocess();
|
||||
force_stop_server();
|
||||
start_server(
|
||||
Some((desktop.uid.clone(), desktop.username.clone())),
|
||||
&mut user_server,
|
||||
);
|
||||
start_server(Some(&desktop), &mut user_server);
|
||||
}
|
||||
} else {
|
||||
force_stop_server();
|
||||
@ -541,7 +560,16 @@ fn is_opensuse() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn run_as_user(arg: Vec<&str>, user: Option<(String, String)>) -> ResultType<Option<Child>> {
|
||||
pub fn run_as_user<I, K, V>(
|
||||
arg: Vec<&str>,
|
||||
user: Option<(String, String)>,
|
||||
envs: I,
|
||||
) -> ResultType<Option<Child>>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: AsRef<OsStr>,
|
||||
V: AsRef<OsStr>,
|
||||
{
|
||||
let (uid, username) = match user {
|
||||
Some(id_name) => id_name,
|
||||
None => get_active_user_id_name(),
|
||||
@ -558,7 +586,7 @@ pub fn run_as_user(arg: Vec<&str>, user: Option<(String, String)>) -> ResultType
|
||||
args.insert(0, "-E");
|
||||
}
|
||||
|
||||
let task = Command::new("sudo").args(args).spawn()?;
|
||||
let task = Command::new("sudo").envs(envs).args(args).spawn()?;
|
||||
Ok(Some(task))
|
||||
}
|
||||
|
||||
@ -639,20 +667,9 @@ pub fn is_installed() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_env_tries(name: &str, uid: &str, process: &str, n: usize) -> String {
|
||||
for _ in 0..n {
|
||||
let x = get_env(name, uid, process);
|
||||
if !x.is_empty() {
|
||||
return x;
|
||||
}
|
||||
sleep_millis(300);
|
||||
}
|
||||
"".to_owned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_env(name: &str, uid: &str, process: &str) -> String {
|
||||
let cmd = format!("ps -u {} -f | grep '{}' | grep -v 'grep' | tail -1 | awk '{{print $2}}' | xargs -I__ cat /proc/__/environ 2>/dev/null | tr '\\0' '\\n' | grep '^{}=' | tail -1 | sed 's/{}=//g'", uid, process, name, name);
|
||||
let cmd = format!("ps -u {} -f | grep -E '{}' | grep -v 'grep' | tail -1 | awk '{{print $2}}' | xargs -I__ cat /proc/__/environ 2>/dev/null | tr '\\0' '\\n' | grep '^{}=' | tail -1 | sed 's/{}=//g'", uid, process, name, name);
|
||||
if let Ok(x) = run_cmds(&cmd) {
|
||||
x.trim_end().to_string()
|
||||
} else {
|
||||
@ -883,13 +900,25 @@ pub fn change_resolution_directly(name: &str, width: usize, height: usize) -> Re
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_xwayland_running() -> bool {
|
||||
if let Ok(output) = run_cmds("pgrep -a Xwayland") {
|
||||
return output.contains("Xwayland");
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
mod desktop {
|
||||
use super::*;
|
||||
|
||||
pub const XFCE4_PANEL: &str = "xfce4-panel";
|
||||
pub const GNOME_SESSION_BINARY: &str = "gnome-session-binary";
|
||||
pub const SDDM_GREETER: &str = "sddm-greeter";
|
||||
pub const PLASMA_X11: &str = "startplasma-x11";
|
||||
|
||||
const XWAYLAND: &str = "Xwayland";
|
||||
const IBUS_DAEMON: &str = "ibus-daemon";
|
||||
const PLASMA_KDED5: &str = "kded5";
|
||||
const GNOME_GOA_DAEMON: &str = "goa-daemon";
|
||||
const RUSTDESK_TRAY: &str = "rustdesk +--tray";
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Desktop {
|
||||
@ -918,13 +947,43 @@ mod desktop {
|
||||
self.sid.is_empty() || self.is_rustdesk_subprocess
|
||||
}
|
||||
|
||||
fn get_display(&mut self) {
|
||||
let display_envs = vec![GNOME_SESSION_BINARY, XFCE4_PANEL, SDDM_GREETER, PLASMA_X11];
|
||||
for diplay_env in display_envs {
|
||||
self.display = get_env_tries("DISPLAY", &self.uid, diplay_env, 10);
|
||||
if !self.display.is_empty() {
|
||||
break;
|
||||
fn get_display_xauth_xwayland(&mut self) {
|
||||
for _ in 0..5 {
|
||||
let display_proc = vec![
|
||||
XWAYLAND,
|
||||
IBUS_DAEMON,
|
||||
GNOME_GOA_DAEMON,
|
||||
PLASMA_KDED5,
|
||||
RUSTDESK_TRAY,
|
||||
];
|
||||
for proc in display_proc {
|
||||
self.display = get_env("DISPLAY", &self.uid, proc);
|
||||
self.xauth = get_env("XAUTHORITY", &self.uid, proc);
|
||||
if !self.display.is_empty() && !self.xauth.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleep_millis(300);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_display_x11(&mut self) {
|
||||
for _ in 0..10 {
|
||||
let display_proc = vec![
|
||||
XWAYLAND,
|
||||
IBUS_DAEMON,
|
||||
GNOME_GOA_DAEMON,
|
||||
PLASMA_KDED5,
|
||||
XFCE4_PANEL,
|
||||
SDDM_GREETER,
|
||||
];
|
||||
for proc in display_proc {
|
||||
self.display = get_env("DISPLAY", &self.uid, proc);
|
||||
if !self.display.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleep_millis(300);
|
||||
}
|
||||
|
||||
if self.display.is_empty() {
|
||||
@ -980,14 +1039,24 @@ mod desktop {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_xauth(&mut self) {
|
||||
fn get_xauth_x11(&mut self) {
|
||||
// try by direct access to window manager process by name
|
||||
let display_envs = vec![GNOME_SESSION_BINARY, XFCE4_PANEL, SDDM_GREETER, PLASMA_X11];
|
||||
for diplay_env in display_envs {
|
||||
self.xauth = get_env_tries("XAUTHORITY", &self.uid, diplay_env, 10);
|
||||
if !self.xauth.is_empty() {
|
||||
break;
|
||||
for _ in 0..10 {
|
||||
let display_proc = vec![
|
||||
XWAYLAND,
|
||||
IBUS_DAEMON,
|
||||
GNOME_GOA_DAEMON,
|
||||
PLASMA_KDED5,
|
||||
XFCE4_PANEL,
|
||||
SDDM_GREETER,
|
||||
];
|
||||
for proc in display_proc {
|
||||
self.xauth = get_env("XAUTHORITY", &self.uid, proc);
|
||||
if !self.xauth.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleep_millis(300);
|
||||
}
|
||||
|
||||
// get from Xorg process, parameter and environment
|
||||
@ -1074,6 +1143,11 @@ mod desktop {
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
if !self.sid.is_empty() && is_active_and_seat0(&self.sid) {
|
||||
// Xwayland display and xauth may not be available in a short time after login.
|
||||
if is_xwayland_running() && !self.is_login_wayland() {
|
||||
self.get_display_xauth_xwayland();
|
||||
self.is_rustdesk_subprocess = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1095,9 +1169,19 @@ mod desktop {
|
||||
return;
|
||||
}
|
||||
|
||||
self.get_display();
|
||||
self.get_xauth();
|
||||
self.set_is_subprocess();
|
||||
if self.is_wayland() {
|
||||
if is_xwayland_running() {
|
||||
self.get_display_xauth_xwayland();
|
||||
} else {
|
||||
self.display = "".to_owned();
|
||||
self.xauth = "".to_owned();
|
||||
}
|
||||
self.is_rustdesk_subprocess = false;
|
||||
} else {
|
||||
self.get_display_x11();
|
||||
self.get_xauth_x11();
|
||||
self.set_is_subprocess();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1587,29 +1587,6 @@ pub fn is_elevated(process_id: Option<DWORD>) -> ResultType<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn filter_foreground_window(process_id: DWORD) -> ResultType<bool> {
|
||||
if let Ok(output) = std::process::Command::new("tasklist")
|
||||
.args(vec![
|
||||
"/SVC",
|
||||
"/NH",
|
||||
"/FI",
|
||||
&format!("PID eq {}", process_id),
|
||||
])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()
|
||||
{
|
||||
let s = String::from_utf8_lossy(&output.stdout)
|
||||
.to_string()
|
||||
.to_lowercase();
|
||||
Ok(["Taskmgr", "mmc", "regedit"]
|
||||
.iter()
|
||||
.any(|name| s.contains(&name.to_string().to_lowercase())))
|
||||
} else {
|
||||
bail!("run tasklist failed");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_foreground_window_elevated() -> ResultType<bool> {
|
||||
unsafe {
|
||||
let mut process_id: DWORD = 0;
|
||||
@ -1617,12 +1594,7 @@ pub fn is_foreground_window_elevated() -> ResultType<bool> {
|
||||
if process_id == 0 {
|
||||
bail!("Failed to get processId, errno {}", GetLastError())
|
||||
}
|
||||
let elevated = is_elevated(Some(process_id))?;
|
||||
if elevated {
|
||||
filter_foreground_window(process_id)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
is_elevated(Some(process_id))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,7 @@ use std::{
|
||||
|
||||
use flutter_rust_bridge::StreamSink;
|
||||
|
||||
use crate::{
|
||||
define_method_prefix, flutter::FlutterHandler, flutter_ffi::EventToUI,
|
||||
plugin::MSG_TO_UI_TYPE_PLUGIN_EVENT, ui_session_interface::Session,
|
||||
};
|
||||
use crate::{define_method_prefix, flutter_ffi::EventToUI};
|
||||
|
||||
const MSG_TO_UI_TYPE_SESSION_CREATED: &str = "session_created";
|
||||
|
||||
@ -28,7 +25,7 @@ pub type OnSessionRgbaCallback = unsafe extern "C" fn(
|
||||
#[derive(Default)]
|
||||
/// Session related handler for librustdesk core.
|
||||
pub struct PluginNativeSessionHandler {
|
||||
sessions: Arc<RwLock<Vec<Session<FlutterHandler>>>>,
|
||||
sessions: Arc<RwLock<Vec<crate::flutter::FlutterSession>>>,
|
||||
cbs: Arc<RwLock<HashMap<String, OnSessionRgbaCallback>>>,
|
||||
}
|
||||
|
||||
@ -61,7 +58,9 @@ impl PluginNativeHandler for PluginNativeSessionHandler {
|
||||
let sessions = SESSION_HANDLER.sessions.read().unwrap();
|
||||
for session in sessions.iter() {
|
||||
if session.id == id {
|
||||
crate::ui_session_interface::io_loop(session.clone());
|
||||
let round =
|
||||
session.connection_round_state.lock().unwrap().new_round();
|
||||
crate::ui_session_interface::io_loop(session.clone(), round);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ pub async fn listen(
|
||||
let interface = interface.clone();
|
||||
tokio::spawn(async move {
|
||||
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);
|
||||
});
|
||||
@ -121,7 +121,6 @@ async fn connect_and_login(
|
||||
let (mut stream, direct, _pk) =
|
||||
Client::start(id, key, token, conn_type, interface.clone()).await?;
|
||||
interface.update_direct(Some(direct));
|
||||
let mut interface = interface;
|
||||
let mut buffer = Vec::new();
|
||||
let mut received = false;
|
||||
loop {
|
||||
|
@ -1330,10 +1330,8 @@ impl Connection {
|
||||
return Config::get_option(enable_prefix_option).is_empty();
|
||||
}
|
||||
|
||||
async fn handle_login_request_without_validation(&mut self, lr: &LoginRequest) {
|
||||
self.lr = lr.clone();
|
||||
fn update_codec_on_login(&self, lr: &LoginRequest) {
|
||||
if let Some(o) = lr.option.as_ref() {
|
||||
self.options_in_login = Some(o.clone());
|
||||
if let Some(q) = o.supported_decoding.clone().take() {
|
||||
scrap::codec::Encoder::update(
|
||||
self.inner.id(),
|
||||
@ -1351,6 +1349,16 @@ impl Connection {
|
||||
scrap::codec::EncodingUpdate::NewOnlyVP9,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_login_request_without_validation(&mut self, lr: &LoginRequest) {
|
||||
self.lr = lr.clone();
|
||||
if let Some(o) = lr.option.as_ref() {
|
||||
self.options_in_login = Some(o.clone());
|
||||
}
|
||||
if lr.union.is_none() {
|
||||
self.update_codec_on_login(&lr);
|
||||
}
|
||||
self.video_ack_required = lr.video_ack_required;
|
||||
}
|
||||
|
||||
@ -2039,12 +2047,18 @@ impl Connection {
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn handle_elevation_request(&mut self, para: portable_client::StartPara) {
|
||||
let mut err = "No need to elevate".to_string();
|
||||
if !crate::platform::is_installed() && !portable_client::running() {
|
||||
err = portable_client::start_portable_service(para)
|
||||
.err()
|
||||
.map_or("".to_string(), |e| e.to_string());
|
||||
let mut err;
|
||||
if !self.keyboard {
|
||||
err = "No permission".to_string();
|
||||
} else {
|
||||
err = "No need to elevate".to_string();
|
||||
if !crate::platform::is_installed() && !portable_client::running() {
|
||||
err = portable_client::start_portable_service(para)
|
||||
.err()
|
||||
.map_or("".to_string(), |e| e.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let mut misc = Misc::new();
|
||||
misc.set_elevation_response(err);
|
||||
let mut msg = Message::new();
|
||||
@ -2361,6 +2375,7 @@ impl Connection {
|
||||
if self.portable.is_installed
|
||||
|| self.file_transfer.is_some()
|
||||
|| self.port_forward_socket.is_some()
|
||||
|| !self.keyboard
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -2371,8 +2386,8 @@ impl Connection {
|
||||
));
|
||||
if self.authorized {
|
||||
let p = &mut self.portable;
|
||||
if running != p.last_running {
|
||||
p.last_running = running;
|
||||
if Some(running) != p.last_running {
|
||||
p.last_running = Some(running);
|
||||
let mut misc = Misc::new();
|
||||
misc.set_portable_service_running(running);
|
||||
let mut msg = Message::new();
|
||||
@ -2507,7 +2522,11 @@ async fn start_ipc(
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
log::debug!("Start cm");
|
||||
res = crate::platform::run_as_user(args.clone(), user.clone());
|
||||
res = crate::platform::run_as_user(
|
||||
args.clone(),
|
||||
user.clone(),
|
||||
None::<(&str, &str)>,
|
||||
);
|
||||
}
|
||||
if res.is_ok() {
|
||||
break;
|
||||
@ -2652,7 +2671,7 @@ pub enum FileAuditType {
|
||||
pub struct PortableState {
|
||||
pub last_uac: bool,
|
||||
pub last_foreground_window_elevated: bool,
|
||||
pub last_running: bool,
|
||||
pub last_running: Option<bool>,
|
||||
pub is_installed: bool,
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ use super::*;
|
||||
use hbb_common::{allow_err, platform::linux::DISTRO};
|
||||
use scrap::{is_cursor_embedded, set_map_err, Capturer, Display, Frame, TraitCapturer};
|
||||
use std::io;
|
||||
use std::process::{Command, Output};
|
||||
|
||||
use crate::client::{
|
||||
SCRAP_OTHER_VERSION_OR_X11_REQUIRED, SCRAP_UBUNTU_HIGHER_REQUIRED, SCRAP_X11_REQUIRED,
|
||||
@ -115,6 +116,22 @@ pub(super) fn is_inited() -> Option<Message> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_max_desktop_resolution() -> Option<String> {
|
||||
// works with Xwayland
|
||||
let output: Output = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("xrandr | awk '/current/ { print $8,$9,$10 }'")
|
||||
.output()
|
||||
.ok()?;
|
||||
|
||||
if output.status.success() {
|
||||
let result = String::from_utf8_lossy(&output.stdout);
|
||||
Some(result.trim().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn check_init() -> ResultType<()> {
|
||||
if !scrap::is_x11() {
|
||||
let mut minx = 0;
|
||||
@ -151,10 +168,20 @@ pub(super) async fn check_init() -> ResultType<()> {
|
||||
num_cpus::get(),
|
||||
);
|
||||
|
||||
minx = origin.0;
|
||||
maxx = origin.0 + width as i32;
|
||||
miny = origin.1;
|
||||
maxy = origin.1 + height as i32;
|
||||
let (max_width, max_height) = match get_max_desktop_resolution() {
|
||||
Some(result) if !result.is_empty() => {
|
||||
let resolution: Vec<&str> = result.split(" ").collect();
|
||||
let w: i32 = resolution[0].parse().unwrap_or(origin.0 + width as i32);
|
||||
let h: i32 = resolution[2].trim_end_matches(",").parse().unwrap_or(origin.1 + height as i32);
|
||||
(w, h)
|
||||
}
|
||||
_ => (origin.0 + width as i32, origin.1 + height as i32)
|
||||
};
|
||||
|
||||
minx = 0;
|
||||
maxx = max_width;
|
||||
miny = 0;
|
||||
maxy = max_height;
|
||||
|
||||
let capturer = Box::into_raw(Box::new(
|
||||
Capturer::new(display, true).with_context(|| "Failed to create capturer")?,
|
||||
|
@ -1,4 +1,7 @@
|
||||
use crate::input::{MOUSE_BUTTON_LEFT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP, MOUSE_TYPE_WHEEL};
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use rdev::{Event, EventType::*, KeyCode};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use std::{collections::HashMap, sync::atomic::AtomicBool};
|
||||
use std::{
|
||||
@ -10,10 +13,6 @@ use std::{
|
||||
},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use rdev::{Event, EventType::*, KeyCode};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
@ -62,6 +61,7 @@ pub struct Session<T: InvokeUiSession> {
|
||||
pub server_file_transfer_enabled: Arc<RwLock<bool>>,
|
||||
pub server_clipboard_enabled: Arc<RwLock<bool>>,
|
||||
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
|
||||
pub connection_round_state: Arc<Mutex<ConnectionRoundState>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -79,6 +79,56 @@ pub struct ChangeDisplayRecord {
|
||||
height: i32,
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
/// ConnectionRoundState is used to control the reconnecting logic.
|
||||
pub struct ConnectionRoundState {
|
||||
round: u32,
|
||||
state: ConnectionState,
|
||||
}
|
||||
|
||||
impl ConnectionRoundState {
|
||||
pub fn new_round(&mut self) -> u32 {
|
||||
self.round += 1;
|
||||
self.state = ConnectionState::Connecting;
|
||||
self.round
|
||||
}
|
||||
|
||||
pub fn set_connected(&mut self) {
|
||||
self.state = ConnectionState::Connected;
|
||||
}
|
||||
|
||||
pub fn is_round_gt(&self, round: u32) -> bool {
|
||||
if round == u32::MAX && self.round == 0 {
|
||||
true
|
||||
} else {
|
||||
round < self.round
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_disconnected(&mut self, round: u32) -> bool {
|
||||
if self.is_round_gt(round) {
|
||||
false
|
||||
} else {
|
||||
self.state = ConnectionState::Disconnected;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConnectionRoundState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
round: 0,
|
||||
state: ConnectionState::Connecting,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChangeDisplayRecord {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -181,7 +231,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_keyboard_mode(&mut self, value: String) {
|
||||
pub fn save_keyboard_mode(&self, value: String) {
|
||||
self.lc.write().unwrap().save_keyboard_mode(value);
|
||||
}
|
||||
|
||||
@ -189,19 +239,19 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.lc.read().unwrap().reverse_mouse_wheel.clone()
|
||||
}
|
||||
|
||||
pub fn save_reverse_mouse_wheel(&mut self, value: String) {
|
||||
pub fn save_reverse_mouse_wheel(&self, value: String) {
|
||||
self.lc.write().unwrap().save_reverse_mouse_wheel(value);
|
||||
}
|
||||
|
||||
pub fn save_view_style(&mut self, value: String) {
|
||||
pub fn save_view_style(&self, value: String) {
|
||||
self.lc.write().unwrap().save_view_style(value);
|
||||
}
|
||||
|
||||
pub fn save_scroll_style(&mut self, value: String) {
|
||||
pub fn save_scroll_style(&self, value: String) {
|
||||
self.lc.write().unwrap().save_scroll_style(value);
|
||||
}
|
||||
|
||||
pub fn save_flutter_option(&mut self, k: String, v: String) {
|
||||
pub fn save_flutter_option(&self, k: String, v: String) {
|
||||
self.lc.write().unwrap().save_ui_flutter(k, v);
|
||||
}
|
||||
|
||||
@ -209,7 +259,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.lc.read().unwrap().get_ui_flutter(&k)
|
||||
}
|
||||
|
||||
pub fn toggle_option(&mut self, name: String) {
|
||||
pub fn toggle_option(&self, name: String) {
|
||||
let msg = self.lc.write().unwrap().toggle_option(name.clone());
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
if name == "enable-file-transfer" {
|
||||
@ -252,7 +302,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
pub fn save_custom_image_quality(&mut self, custom_image_quality: i32) {
|
||||
pub fn save_custom_image_quality(&self, custom_image_quality: i32) {
|
||||
let msg = self
|
||||
.lc
|
||||
.write()
|
||||
@ -261,14 +311,14 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
pub fn save_image_quality(&mut self, value: String) {
|
||||
pub fn save_image_quality(&self, value: String) {
|
||||
let msg = self.lc.write().unwrap().save_image_quality(value);
|
||||
if let Some(msg) = msg {
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_custom_fps(&mut self, custom_fps: i32) {
|
||||
pub fn set_custom_fps(&self, custom_fps: i32) {
|
||||
let msg = self.lc.write().unwrap().set_custom_fps(custom_fps);
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
@ -372,7 +422,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.send(Data::RemovePortForward(port));
|
||||
}
|
||||
|
||||
pub fn add_port_forward(&mut self, port: i32, remote_host: String, remote_port: i32) {
|
||||
pub fn add_port_forward(&self, port: i32, remote_host: String, remote_port: i32) {
|
||||
let mut config = self.load_config();
|
||||
if config
|
||||
.port_forwards
|
||||
@ -833,16 +883,30 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
|
||||
pub fn reconnect(&self, force_relay: bool) {
|
||||
self.send(Data::Close);
|
||||
// 1. If current session is connecting, do not reconnect.
|
||||
// 2. If the connection is established, send `Data::Close`.
|
||||
// 3. If the connection is disconnected, do nothing.
|
||||
let mut connection_round_state_lock = self.connection_round_state.lock().unwrap();
|
||||
if self.thread.lock().unwrap().is_some() {
|
||||
match connection_round_state_lock.state {
|
||||
ConnectionState::Connecting => return,
|
||||
ConnectionState::Connected => self.send(Data::Close),
|
||||
ConnectionState::Disconnected => {}
|
||||
}
|
||||
}
|
||||
let round = connection_round_state_lock.new_round();
|
||||
drop(connection_round_state_lock);
|
||||
|
||||
let cloned = self.clone();
|
||||
// override only if true
|
||||
if true == force_relay {
|
||||
cloned.lc.write().unwrap().force_relay = true;
|
||||
self.lc.write().unwrap().force_relay = true;
|
||||
}
|
||||
let mut lock = self.thread.lock().unwrap();
|
||||
lock.take().map(|t| t.join());
|
||||
// No need to join the previous thread, because it will exit automatically.
|
||||
// And the previous thread will not change important states.
|
||||
*lock = Some(std::thread::spawn(move || {
|
||||
io_loop(cloned);
|
||||
io_loop(cloned, round);
|
||||
}));
|
||||
}
|
||||
|
||||
@ -1151,11 +1215,11 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
self.ui_handler.msgbox(msgtype, title, text, link, retry);
|
||||
}
|
||||
|
||||
fn handle_login_error(&mut self, err: &str) -> bool {
|
||||
fn handle_login_error(&self, err: &str) -> bool {
|
||||
handle_login_error(self.lc.clone(), err, self)
|
||||
}
|
||||
|
||||
fn handle_peer_info(&mut self, mut pi: PeerInfo) {
|
||||
fn handle_peer_info(&self, mut pi: PeerInfo) {
|
||||
log::debug!("handle_peer_info :{:?}", pi);
|
||||
pi.username = self.lc.read().unwrap().get_username(&pi);
|
||||
if pi.current_display as usize >= pi.displays.len() {
|
||||
@ -1217,12 +1281,12 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream) {
|
||||
async fn handle_hash(&self, pass: &str, hash: Hash, peer: &mut Stream) {
|
||||
handle_hash(self.lc.clone(), pass, hash, self, peer).await;
|
||||
}
|
||||
|
||||
async fn handle_login_from_ui(
|
||||
&mut self,
|
||||
&self,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: String,
|
||||
@ -1240,7 +1304,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {
|
||||
async fn handle_test_delay(&self, t: TestDelay, peer: &mut Stream) {
|
||||
if !t.from_client {
|
||||
self.update_quality_status(QualityStatus {
|
||||
delay: Some(t.last_delay as _),
|
||||
@ -1283,7 +1347,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
|
||||
pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>, round: u32) {
|
||||
// It is ok to call this function multiple times.
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
if !handler.is_file_transfer() && !handler.is_port_forward() {
|
||||
@ -1402,7 +1466,7 @@ pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
|
||||
frame_count,
|
||||
decode_fps,
|
||||
);
|
||||
remote.io_loop(&key, &token).await;
|
||||
remote.io_loop(&key, &token, round).await;
|
||||
remote.sync_jobs_status_to_local().await;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user