Merge remote-tracking branch 'origin/master' into feat/x11/clipboard-file/init
This commit is contained in:
commit
d2a5edda46
1
.github/workflows/flutter-build.yml
vendored
1
.github/workflows/flutter-build.yml
vendored
@ -82,6 +82,7 @@ jobs:
|
||||
|
||||
- name: Install flutter rust bridge deps
|
||||
run: |
|
||||
git config --global core.longpaths true
|
||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
||||
Push-Location flutter ; flutter pub get ; Pop-Location
|
||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
|
||||
|
18
.github/workflows/vcpkg-deps-linux.yml
vendored
18
.github/workflows/vcpkg-deps-linux.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
path: /opt/artifacts
|
||||
key: vcpkg-${{ matrix.job.arch }}
|
||||
|
||||
- uses: Kingtous/run-on-arch-action@amd64-support
|
||||
- uses: rustdesk-org/run-on-arch-action@amd64-support
|
||||
name: Run vcpkg install on ${{ matrix.job.arch }}
|
||||
id: vcpkg
|
||||
with:
|
||||
@ -40,12 +40,16 @@ jobs:
|
||||
apt update -y
|
||||
case "${{ matrix.job.arch }}" in
|
||||
x86_64)
|
||||
# CMake 3.15+
|
||||
apt install -y gpg wget ca-certificates
|
||||
echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ bionic main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
|
||||
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
|
||||
apt update -y
|
||||
apt install -y curl zip unzip tar git cmake g++ gcc build-essential pkg-config wget nasm yasm ninja-build libjpeg8-dev
|
||||
apt install -y curl zip unzip tar git g++ gcc build-essential pkg-config wget nasm yasm ninja-build libjpeg8-dev libssl-dev
|
||||
wget https://github.com/Kitware/CMake/releases/download/v3.27.5/cmake-3.27.5.tar.gz
|
||||
apt remove -y --purge cmake
|
||||
tar -zxvf cmake-3.27.5.tar.gz
|
||||
cd cmake-3.27.5
|
||||
./bootstrap
|
||||
make
|
||||
make install
|
||||
cd -
|
||||
cmake --version
|
||||
gcc -v
|
||||
;;
|
||||
@ -85,4 +89,4 @@ jobs:
|
||||
with:
|
||||
name: vcpkg-artifact-${{ matrix.job.arch }}
|
||||
path: |
|
||||
/opt/artifacts/vcpkg/installed
|
||||
/opt/artifacts/vcpkg/installed
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -4946,7 +4946,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rdev"
|
||||
version = "0.5.0-2"
|
||||
source = "git+https://github.com/fufesou/rdev#ee3057bd97c91529e8b9daf2ca133a5c49f0c0eb"
|
||||
source = "git+https://github.com/fufesou/rdev#2e8221d653f4995c831ad52966e79a514516b1fa"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"core-foundation",
|
||||
|
BIN
flutter/assets/scam.png
Normal file
BIN
flutter/assets/scam.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 627 KiB |
@ -101,6 +101,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
required this.highlight,
|
||||
required this.drag_indicator,
|
||||
required this.shadow,
|
||||
required this.errorBannerBg,
|
||||
});
|
||||
|
||||
final Color? border;
|
||||
@ -108,6 +109,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
final Color? highlight;
|
||||
final Color? drag_indicator;
|
||||
final Color? shadow;
|
||||
final Color? errorBannerBg;
|
||||
|
||||
static final light = ColorThemeExtension(
|
||||
border: Color(0xFFCCCCCC),
|
||||
@ -115,6 +117,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
highlight: Color(0xFFE5E5E5),
|
||||
drag_indicator: Colors.grey[800],
|
||||
shadow: Colors.black,
|
||||
errorBannerBg: Color(0xFFFDEEEB),
|
||||
);
|
||||
|
||||
static final dark = ColorThemeExtension(
|
||||
@ -123,6 +126,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
highlight: Color(0xFF3F3F3F),
|
||||
drag_indicator: Colors.grey,
|
||||
shadow: Colors.grey,
|
||||
errorBannerBg: Color(0xFF470F2D),
|
||||
);
|
||||
|
||||
@override
|
||||
@ -132,6 +136,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
Color? highlight,
|
||||
Color? drag_indicator,
|
||||
Color? shadow,
|
||||
Color? errorBannerBg,
|
||||
}) {
|
||||
return ColorThemeExtension(
|
||||
border: border ?? this.border,
|
||||
@ -139,6 +144,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
highlight: highlight ?? this.highlight,
|
||||
drag_indicator: drag_indicator ?? this.drag_indicator,
|
||||
shadow: shadow ?? this.shadow,
|
||||
errorBannerBg: errorBannerBg ?? this.errorBannerBg,
|
||||
);
|
||||
}
|
||||
|
||||
@ -154,6 +160,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
highlight: Color.lerp(highlight, other.highlight, t),
|
||||
drag_indicator: Color.lerp(drag_indicator, other.drag_indicator, t),
|
||||
shadow: Color.lerp(shadow, other.shadow, t),
|
||||
errorBannerBg: Color.lerp(shadow, other.errorBannerBg, t),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -258,6 +265,14 @@ class MyTheme {
|
||||
? EdgeInsets.only(left: dialogPadding)
|
||||
: EdgeInsets.only(left: dialogPadding / 3);
|
||||
|
||||
static ScrollbarThemeData scrollbarTheme = ScrollbarThemeData(
|
||||
thickness: MaterialStateProperty.all(kScrollbarThickness),
|
||||
);
|
||||
|
||||
static ScrollbarThemeData scrollbarThemeDark = scrollbarTheme.copyWith(
|
||||
thumbColor: MaterialStateProperty.all(Colors.grey[500]),
|
||||
);
|
||||
|
||||
static ThemeData lightTheme = ThemeData(
|
||||
brightness: Brightness.light,
|
||||
hoverColor: Color.fromARGB(255, 224, 224, 224),
|
||||
@ -273,6 +288,7 @@ class MyTheme {
|
||||
),
|
||||
),
|
||||
),
|
||||
scrollbarTheme: scrollbarTheme,
|
||||
inputDecorationTheme: isDesktop
|
||||
? InputDecorationTheme(
|
||||
fillColor: grayBg,
|
||||
@ -357,6 +373,7 @@ class MyTheme {
|
||||
),
|
||||
),
|
||||
),
|
||||
scrollbarTheme: scrollbarThemeDark,
|
||||
inputDecorationTheme: isDesktop
|
||||
? InputDecorationTheme(
|
||||
fillColor: Color(0xFF24252B),
|
||||
@ -383,9 +400,6 @@ class MyTheme {
|
||||
tabBarTheme: const TabBarTheme(
|
||||
labelColor: Colors.white70,
|
||||
),
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbColor: MaterialStateProperty.all(Colors.grey[500]),
|
||||
),
|
||||
tooltipTheme: tooltipTheme(),
|
||||
splashColor: isDesktop ? Colors.transparent : null,
|
||||
highlightColor: isDesktop ? Colors.transparent : null,
|
||||
@ -2466,3 +2480,59 @@ String toCapitalized(String s) {
|
||||
}
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
|
||||
Widget buildErrorBanner(BuildContext context,
|
||||
{required RxBool loading,
|
||||
required RxString err,
|
||||
required Function? retry,
|
||||
required Function close}) {
|
||||
const double height = 25;
|
||||
return Obx(() => Offstage(
|
||||
offstage: !(!loading.value && err.value.isNotEmpty),
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: height,
|
||||
color: MyTheme.color(context).errorBannerBg,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
FittedBox(
|
||||
child: Icon(
|
||||
Icons.info,
|
||||
color: Color.fromARGB(255, 249, 81, 81),
|
||||
),
|
||||
).marginAll(4),
|
||||
Flexible(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Tooltip(
|
||||
message: translate(err.value),
|
||||
child: Text(
|
||||
translate(err.value),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)).marginSymmetric(vertical: 2),
|
||||
),
|
||||
if (retry != null)
|
||||
InkWell(
|
||||
onTap: () {
|
||||
retry.call();
|
||||
},
|
||||
child: Text(
|
||||
translate("Retry"),
|
||||
style: TextStyle(color: MyTheme.accent),
|
||||
)).marginSymmetric(horizontal: 5),
|
||||
FittedBox(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
close.call();
|
||||
},
|
||||
child: Icon(Icons.close).marginSymmetric(horizontal: 5),
|
||||
),
|
||||
).marginAll(4)
|
||||
],
|
||||
),
|
||||
)).marginOnly(bottom: 14),
|
||||
));
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
|
||||
@ -48,11 +49,18 @@ class UserPayload {
|
||||
};
|
||||
return map;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toGroupCacheJson() {
|
||||
final Map<String, dynamic> map = {
|
||||
'name': name,
|
||||
};
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
class PeerPayload {
|
||||
String id = '';
|
||||
String info = '';
|
||||
Map<String, dynamic> info = {};
|
||||
int? status;
|
||||
String user = '';
|
||||
String user_name = '';
|
||||
@ -60,14 +68,45 @@ class PeerPayload {
|
||||
|
||||
PeerPayload.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'] ?? '',
|
||||
info = json['info'] ?? '',
|
||||
info = (json['info'] is Map<String, dynamic>) ? json['info'] : {},
|
||||
status = json['status'],
|
||||
user = json['user'] ?? '',
|
||||
user_name = json['user_name'] ?? '',
|
||||
note = json['note'] ?? '';
|
||||
|
||||
static Peer toPeer(PeerPayload p) {
|
||||
return Peer.fromJson({"id": p.id, "username": p.user_name});
|
||||
return Peer.fromJson({
|
||||
"id": p.id,
|
||||
'loginName': p.user_name,
|
||||
"username": p.info['username'] ?? '',
|
||||
"platform": _platform(p.info['os']),
|
||||
"hostname": p.info['device_name'],
|
||||
});
|
||||
}
|
||||
|
||||
static String? _platform(dynamic field) {
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
final fieldStr = field.toString();
|
||||
List<String> list = fieldStr.split(' / ');
|
||||
if (list.isEmpty) return null;
|
||||
final os = list[0];
|
||||
switch (os.toLowerCase()) {
|
||||
case 'windows':
|
||||
return kPeerPlatformWindows;
|
||||
case 'linux':
|
||||
return kPeerPlatformLinux;
|
||||
case 'macos':
|
||||
return kPeerPlatformMacOS;
|
||||
case 'android':
|
||||
return kPeerPlatformAndroid;
|
||||
default:
|
||||
if (fieldStr.toLowerCase().contains('linux')) {
|
||||
return kPeerPlatformLinux;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ class _AddressBookState extends State<AddressBook> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Obx(() {
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
if (!gFFI.userModel.isLogin) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: loginDialog, child: Text(translate("Login"))));
|
||||
@ -49,11 +49,13 @@ class _AddressBookState extends State<AddressBook> {
|
||||
children: [
|
||||
// NOT use Offstage to wrap LinearProgressIndicator
|
||||
if (gFFI.abModel.retrying.value) LinearProgressIndicator(),
|
||||
_buildErrorBanner(
|
||||
buildErrorBanner(context,
|
||||
loading: gFFI.abModel.abLoading,
|
||||
err: gFFI.abModel.pullError,
|
||||
retry: null,
|
||||
close: () => gFFI.abModel.pullError.value = ''),
|
||||
_buildErrorBanner(
|
||||
buildErrorBanner(context,
|
||||
loading: gFFI.abModel.abLoading,
|
||||
err: gFFI.abModel.pushError,
|
||||
retry: () => gFFI.abModel.pushAb(isRetry: true),
|
||||
close: () => gFFI.abModel.pushError.value = ''),
|
||||
@ -66,61 +68,6 @@ class _AddressBookState extends State<AddressBook> {
|
||||
}
|
||||
});
|
||||
|
||||
Widget _buildErrorBanner(
|
||||
{required RxString err,
|
||||
required Function? retry,
|
||||
required Function close}) {
|
||||
const double height = 25;
|
||||
return Obx(() => Offstage(
|
||||
offstage: !(!gFFI.abModel.abLoading.value && err.value.isNotEmpty),
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: height,
|
||||
color: Color.fromARGB(255, 253, 238, 235),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
FittedBox(
|
||||
child: Icon(
|
||||
Icons.info,
|
||||
color: Color.fromARGB(255, 249, 81, 81),
|
||||
),
|
||||
).marginAll(4),
|
||||
Flexible(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Tooltip(
|
||||
message: translate(err.value),
|
||||
child: Text(
|
||||
translate(err.value),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)).marginSymmetric(vertical: 2),
|
||||
),
|
||||
if (retry != null)
|
||||
InkWell(
|
||||
onTap: () {
|
||||
retry.call();
|
||||
},
|
||||
child: Text(
|
||||
translate("Retry"),
|
||||
style: TextStyle(color: MyTheme.accent),
|
||||
)).marginSymmetric(horizontal: 5),
|
||||
FittedBox(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
close.call();
|
||||
},
|
||||
child: Icon(Icons.close).marginSymmetric(horizontal: 5),
|
||||
),
|
||||
).marginAll(4)
|
||||
],
|
||||
),
|
||||
)).marginOnly(bottom: 14),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildAddressBookDesktop() {
|
||||
return Row(
|
||||
children: [
|
||||
@ -230,11 +177,10 @@ class _AddressBookState extends State<AddressBook> {
|
||||
return Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() => AddressBookPeersView(
|
||||
menuPadding: widget.menuPadding,
|
||||
// ignore: invalid_use_of_protected_member
|
||||
initPeers: gFFI.abModel.peers.value,
|
||||
))),
|
||||
child: AddressBookPeersView(
|
||||
menuPadding: widget.menuPadding,
|
||||
initPeers: gFFI.abModel.peers,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -302,6 +302,53 @@ Future<String> changeDirectAccessPort(
|
||||
return controller.text;
|
||||
}
|
||||
|
||||
Future<String> changeAutoDisconnectTimeout(String old) async {
|
||||
final controller = TextEditingController(text: old);
|
||||
await gFFI.dialogManager.show((setState, close, context) {
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Timeout in minutes")),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8.0),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
hintText: '10',
|
||||
isCollapsed: true,
|
||||
suffix: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.clear, size: 16),
|
||||
onPressed: () => controller.clear())),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(
|
||||
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
||||
],
|
||||
controller: controller,
|
||||
autofocus: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: () async {
|
||||
await bind.mainSetOption(
|
||||
key: 'auto-disconnect-timeout', value: controller.text);
|
||||
close();
|
||||
}),
|
||||
],
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
return controller.text;
|
||||
}
|
||||
|
||||
class DialogTextField extends StatelessWidget {
|
||||
final String title;
|
||||
final String? hintText;
|
||||
|
@ -29,49 +29,28 @@ class _MyGroupState extends State<MyGroup> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
// use username to be same with ab
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
if (!gFFI.userModel.isLogin) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: loginDialog, child: Text(translate("Login"))));
|
||||
}
|
||||
return buildBody(context);
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildBody(BuildContext context) {
|
||||
return Obx(() {
|
||||
if (gFFI.groupModel.groupLoading.value) {
|
||||
} else if (gFFI.groupModel.groupLoading.value && gFFI.groupModel.emtpy) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
if (gFFI.groupModel.groupLoadError.isNotEmpty) {
|
||||
return _buildShowError(gFFI.groupModel.groupLoadError.value);
|
||||
}
|
||||
if (isDesktop) {
|
||||
return _buildDesktop();
|
||||
} else {
|
||||
return _buildMobile();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
buildErrorBanner(context,
|
||||
loading: gFFI.groupModel.groupLoading,
|
||||
err: gFFI.groupModel.groupLoadError,
|
||||
retry: null,
|
||||
close: () => gFFI.groupModel.groupLoadError.value = ''),
|
||||
Expanded(child: isDesktop ? _buildDesktop() : _buildMobile())
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildShowError(String error) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(translate(error)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
gFFI.groupModel.pull();
|
||||
},
|
||||
child: Text(translate("Retry")))
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildDesktop() {
|
||||
return Row(
|
||||
children: [
|
||||
@ -100,10 +79,9 @@ class _MyGroupState extends State<MyGroup> {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() => MyGroupPeerView(
|
||||
child: MyGroupPeerView(
|
||||
menuPadding: widget.menuPadding,
|
||||
// ignore: invalid_use_of_protected_member
|
||||
initPeers: gFFI.groupModel.peersShow.value))),
|
||||
initPeers: gFFI.groupModel.peers)),
|
||||
)
|
||||
],
|
||||
);
|
||||
@ -133,10 +111,9 @@ class _MyGroupState extends State<MyGroup> {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() => MyGroupPeerView(
|
||||
child: MyGroupPeerView(
|
||||
menuPadding: widget.menuPadding,
|
||||
// ignore: invalid_use_of_protected_member
|
||||
initPeers: gFFI.groupModel.peersShow.value))),
|
||||
initPeers: gFFI.groupModel.peers)),
|
||||
)
|
||||
],
|
||||
);
|
||||
@ -195,6 +172,7 @@ class _MyGroupState extends State<MyGroup> {
|
||||
}, child: Obx(
|
||||
() {
|
||||
bool selected = selectedUser.value == username;
|
||||
final isMe = username == gFFI.userModel.userName.value;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? MyTheme.color(context).highlight : null,
|
||||
@ -208,7 +186,7 @@ class _MyGroupState extends State<MyGroup> {
|
||||
children: [
|
||||
Icon(Icons.person_rounded, color: Colors.grey, size: 16)
|
||||
.marginOnly(right: 4),
|
||||
Expanded(child: Text(username)),
|
||||
Expanded(child: Text(isMe ? translate('Me') : username)),
|
||||
],
|
||||
).paddingSymmetric(vertical: 4),
|
||||
),
|
||||
|
@ -727,7 +727,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
MenuEntryBase<String> _unrememberPasswordAction(String id) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Unremember Password'),
|
||||
translate('Forget Password'),
|
||||
style: style,
|
||||
),
|
||||
proc: () async {
|
||||
@ -1093,7 +1093,7 @@ class MyGroupPeerCard extends BasePeerCard {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
// menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
@ -1101,9 +1101,14 @@ class MyGroupPeerCard extends BasePeerCard {
|
||||
menuItems.add(_createShortCutAction(peer.id));
|
||||
}
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_renameAction(peer.id));
|
||||
if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
// menuItems.add(_renameAction(peer.id));
|
||||
// if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
// menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
// }
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/address_book.dart';
|
||||
import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
@ -6,6 +9,9 @@ import 'package:flutter_hbb/common/widgets/peers_view.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
|
||||
as mod_menu;
|
||||
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';
|
||||
@ -61,6 +67,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
({dynamic hint}) => gFFI.groupModel.pull(force: hint == null),
|
||||
),
|
||||
];
|
||||
RelativeRect? mobileTabContextMenuPos;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -100,9 +107,15 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
child: selectionWrap(Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(child: _createSwitchBar(context)),
|
||||
Expanded(
|
||||
child:
|
||||
visibleContextMenuListener(_createSwitchBar(context))),
|
||||
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
|
||||
_createRefresh(),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.group,
|
||||
loading: gFFI.groupModel.groupLoading),
|
||||
_createMultiSelection(),
|
||||
Offstage(
|
||||
offstage: !isDesktop,
|
||||
@ -145,7 +158,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
return ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: model.indexs.map((t) {
|
||||
children: model.visibleIndexs.map((t) {
|
||||
final selected = model.currentTab == t;
|
||||
final color = selected
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
@ -161,11 +174,13 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
));
|
||||
return Obx(() => InkWell(
|
||||
child: Container(
|
||||
decoration:
|
||||
selected ? decoBorder : (hover.value ? deco : null),
|
||||
decoration: (hover.value
|
||||
? (selected ? decoBorder : deco)
|
||||
: (selected ? decoBorder : null)),
|
||||
child: Tooltip(
|
||||
message:
|
||||
model.tabTooltip(t, gFFI.groupModel.groupName.value),
|
||||
preferBelow: false,
|
||||
message: model.tabTooltip(t),
|
||||
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
||||
child: Icon(model.tabIcon(t), color: color),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
@ -182,14 +197,15 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
Widget _createPeersView() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
Widget child;
|
||||
if (model.indexs.isEmpty) {
|
||||
child = Center(
|
||||
child: Text(translate('Right click to select tabs')),
|
||||
);
|
||||
if (model.visibleIndexs.isEmpty) {
|
||||
child = visibleContextMenuListener(Row(
|
||||
children: [Expanded(child: InkWell())],
|
||||
));
|
||||
} else {
|
||||
if (model.indexs.contains(model.currentTab)) {
|
||||
if (model.visibleIndexs.contains(model.currentTab)) {
|
||||
child = entries[model.currentTab].widget;
|
||||
} else {
|
||||
debugPrint("should not happen! currentTab not in visibleIndexs");
|
||||
Future.delayed(Duration.zero, () {
|
||||
model.setCurrentTab(model.indexs[0]);
|
||||
});
|
||||
@ -200,17 +216,19 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0));
|
||||
}
|
||||
|
||||
Widget _createRefresh() {
|
||||
Widget _createRefresh(
|
||||
{required PeerTabIndex index, required RxBool loading}) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return Offstage(
|
||||
offstage: gFFI.peerTabModel.currentTab != PeerTabIndex.ab.index,
|
||||
offstage: model.currentTab != index.index,
|
||||
child: RefreshWidget(
|
||||
onPressed: () {
|
||||
if (gFFI.peerTabModel.currentTab < entries.length) {
|
||||
entries[gFFI.peerTabModel.currentTab].load();
|
||||
}
|
||||
},
|
||||
spinning: gFFI.abModel.abLoading,
|
||||
spinning: loading,
|
||||
child: RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: Tooltip(
|
||||
@ -268,6 +286,94 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
);
|
||||
}
|
||||
|
||||
void mobileShowTabVisibilityMenu() {
|
||||
final model = gFFI.peerTabModel;
|
||||
final items = List<PopupMenuItem>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
items.add(PopupMenuItem(
|
||||
height: kMinInteractiveDimension * 0.8,
|
||||
onTap: () => model.setTabVisible(i, !model.isVisible[i]),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: model.isVisible[i],
|
||||
onChanged: (_) {
|
||||
model.setTabVisible(i, !model.isVisible[i]);
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}),
|
||||
Expanded(child: Text(model.tabTooltip(i))),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
if (mobileTabContextMenuPos != null) {
|
||||
showMenu(
|
||||
context: context, position: mobileTabContextMenuPos!, items: items);
|
||||
}
|
||||
}
|
||||
|
||||
Widget visibleContextMenuListener(Widget child) {
|
||||
if (isMobile) {
|
||||
return GestureDetector(
|
||||
onLongPressDown: (e) {
|
||||
final x = e.globalPosition.dx;
|
||||
final y = e.globalPosition.dy;
|
||||
mobileTabContextMenuPos = RelativeRect.fromLTRB(x, y, x, y);
|
||||
},
|
||||
onLongPressUp: () {
|
||||
mobileShowTabVisibilityMenu();
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return Listener(
|
||||
onPointerDown: (e) {
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||
return;
|
||||
}
|
||||
if (e.buttons == 2) {
|
||||
showRightMenu(
|
||||
(CancelFunc cancelFunc) {
|
||||
return visibleContextMenu(cancelFunc);
|
||||
},
|
||||
target: e.position,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: child);
|
||||
}
|
||||
}
|
||||
|
||||
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final menu = List<MenuEntrySwitch>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
menu.add(MenuEntrySwitch(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: model.tabTooltip(i),
|
||||
getter: () async {
|
||||
return model.isVisible[i];
|
||||
},
|
||||
setter: (show) async {
|
||||
model.setTabVisible(i, show);
|
||||
cancelFunc();
|
||||
}));
|
||||
}
|
||||
return mod_menu.PopupMenu(
|
||||
items: menu
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: MyTheme.accent,
|
||||
height: 20.0,
|
||||
dividerHeight: 12.0,
|
||||
)))
|
||||
.expand((i) => i)
|
||||
.toList());
|
||||
}
|
||||
|
||||
Widget createMultiSelectionBar() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return Row(
|
||||
@ -286,6 +392,9 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
Widget deleteSelection() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
if (model.currentTab == PeerTabIndex.group.index) {
|
||||
return Offstage();
|
||||
}
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
onTap: () {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:dynamic_layouts/dynamic_layouts.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -35,6 +36,7 @@ class LoadEvent {
|
||||
static const String favorite = 'load_fav_peers';
|
||||
static const String lan = 'load_lan_peers';
|
||||
static const String addressBook = 'load_address_book_peers';
|
||||
static const String group = 'load_group_peers';
|
||||
}
|
||||
|
||||
/// for peer search text, global obs value
|
||||
@ -176,26 +178,29 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
if (snapshot.hasData) {
|
||||
final peers = snapshot.data!;
|
||||
gFFI.peerTabModel.setCurrentTabCachedPeers(peers);
|
||||
final cards = <Widget>[];
|
||||
for (final peer in peers) {
|
||||
final visibilityChild = VisibilityDetector(
|
||||
key: ValueKey(_cardId(peer.id)),
|
||||
onVisibilityChanged: onVisibilityChanged,
|
||||
child: widget.peerCardBuilder(peer),
|
||||
);
|
||||
cards.add(isDesktop
|
||||
? Obx(
|
||||
() => SizedBox(
|
||||
width: 220,
|
||||
height:
|
||||
peerCardUiType.value == PeerUiType.grid ? 140 : 42,
|
||||
child: visibilityChild,
|
||||
),
|
||||
)
|
||||
: SizedBox(width: mobileWidth, child: visibilityChild));
|
||||
}
|
||||
final child =
|
||||
Wrap(spacing: space, runSpacing: space, children: cards);
|
||||
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);
|
||||
},
|
||||
);
|
||||
if (updateEvent == UpdateEvent.load) {
|
||||
_curPeers.clear();
|
||||
_curPeers.addAll(peers.map((e) => e.id));
|
||||
@ -312,7 +317,7 @@ abstract class BasePeersView extends StatelessWidget {
|
||||
final String loadEvent;
|
||||
final PeerFilter? peerFilter;
|
||||
final PeerCardBuilder peerCardBuilder;
|
||||
final List<Peer> initPeers;
|
||||
final RxList<Peer>? initPeers;
|
||||
|
||||
const BasePeersView({
|
||||
Key? key,
|
||||
@ -326,7 +331,7 @@ abstract class BasePeersView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _PeersView(
|
||||
peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers),
|
||||
peers: Peers(name: name, loadEvent: loadEvent, initPeers: initPeers),
|
||||
peerFilter: peerFilter,
|
||||
peerCardBuilder: peerCardBuilder);
|
||||
}
|
||||
@ -343,7 +348,7 @@ class RecentPeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
initPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -365,7 +370,7 @@ class FavoritePeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
initPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -387,7 +392,7 @@ class DiscoveredPeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
initPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -403,7 +408,7 @@ class AddressBookPeersView extends BasePeersView {
|
||||
{Key? key,
|
||||
EdgeInsets? menuPadding,
|
||||
ScrollController? scrollController,
|
||||
required List<Peer> initPeers})
|
||||
required RxList<Peer> initPeers})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'address book peer',
|
||||
@ -435,11 +440,11 @@ class MyGroupPeerView extends BasePeersView {
|
||||
{Key? key,
|
||||
EdgeInsets? menuPadding,
|
||||
ScrollController? scrollController,
|
||||
required List<Peer> initPeers})
|
||||
required RxList<Peer> initPeers})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'my group peer',
|
||||
loadEvent: 'load_my_group_peers',
|
||||
name: 'group peer',
|
||||
loadEvent: LoadEvent.group,
|
||||
peerFilter: filter,
|
||||
peerCardBuilder: (Peer peer) => MyGroupPeerCard(
|
||||
peer: peer,
|
||||
@ -450,12 +455,12 @@ class MyGroupPeerView extends BasePeersView {
|
||||
|
||||
static bool filter(Peer peer) {
|
||||
if (gFFI.groupModel.searchUserText.isNotEmpty) {
|
||||
if (!peer.username.contains(gFFI.groupModel.searchUserText)) {
|
||||
if (!peer.loginName.contains(gFFI.groupModel.searchUserText)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (gFFI.groupModel.selectedUser.isNotEmpty) {
|
||||
if (gFFI.groupModel.selectedUser.value != peer.username) {
|
||||
if (gFFI.groupModel.selectedUser.value != peer.loginName) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ const String kPeerPlatformLinux = "Linux";
|
||||
const String kPeerPlatformMacOS = "Mac OS";
|
||||
const String kPeerPlatformAndroid = "Android";
|
||||
|
||||
const double kScrollbarThickness = 12.0;
|
||||
|
||||
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page"
|
||||
const String kAppTypeMain = "main";
|
||||
|
||||
@ -72,10 +74,6 @@ const int kDesktopDefaultDisplayHeight = 720;
|
||||
const int kMobileMaxDisplaySize = 1280;
|
||||
const int kDesktopMaxDisplaySize = 3840;
|
||||
|
||||
const double kDesktopFileTransferNameColWidth = 200;
|
||||
const double kDesktopFileTransferModifiedColWidth = 120;
|
||||
const double kDesktopFileTransferMinimumWidth = 100;
|
||||
const double kDesktopFileTransferMaximumWidth = 300;
|
||||
const double kDesktopFileTransferRowHeight = 30.0;
|
||||
const double kDesktopFileTransferHeaderHeight = 25.0;
|
||||
|
||||
@ -138,6 +136,12 @@ const kRemoteScrollStyleAuto = 'scrollauto';
|
||||
/// [kRemoteScrollStyleBar] Scroll image with scroll bar.
|
||||
const kRemoteScrollStyleBar = 'scrollbar';
|
||||
|
||||
/// [kScrollModeDefault] Mouse or touchpad, the default scroll mode.
|
||||
const kScrollModeDefault = 'default';
|
||||
|
||||
/// [kScrollModeReverse] Mouse or touchpad, the reverse scroll mode.
|
||||
const kScrollModeReverse = 'reverse';
|
||||
|
||||
/// [kRemoteImageQualityBest] Best image quality.
|
||||
const kRemoteImageQualityBest = 'best';
|
||||
|
||||
|
@ -138,7 +138,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
Divider().paddingOnly(right: 12),
|
||||
])),
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
hasScrollBody: true,
|
||||
child: PeerTabPage().paddingOnly(right: 12.0),
|
||||
)
|
||||
],
|
||||
|
@ -48,6 +48,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
var watchIsInputMonitoring = false;
|
||||
var watchIsCanRecordAudio = false;
|
||||
Timer? _updateTimer;
|
||||
bool isCardClosed = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -321,14 +322,15 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
}
|
||||
|
||||
Future<Widget> buildHelpCards() async {
|
||||
if (updateUrl.isNotEmpty) {
|
||||
if (updateUrl.isNotEmpty && !isCardClosed) {
|
||||
return buildInstallCard(
|
||||
"Status",
|
||||
"There is a newer version of ${bind.mainGetAppNameSync()} ${bind.mainGetNewVersion()} available.",
|
||||
"Click to download", () async {
|
||||
final Uri url = Uri.parse('https://rustdesk.com/download');
|
||||
await launchUrl(url);
|
||||
});
|
||||
},
|
||||
closeButton: true);
|
||||
}
|
||||
if (systemError.isNotEmpty) {
|
||||
return buildInstallCard("", systemError, "", () {});
|
||||
@ -394,11 +396,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
|
||||
Widget buildInstallCard(String title, String content, String btnText,
|
||||
GestureTapCallback onPressed,
|
||||
{String? help, String? link}) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 20),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
{String? help, String? link, bool? closeButton}) {
|
||||
|
||||
void closeCard() {
|
||||
setState(() {
|
||||
isCardClosed = true;
|
||||
});
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 20),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
@ -467,6 +478,21 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
)).marginOnly(top: 6)),
|
||||
]
|
||||
: <Widget>[]))),
|
||||
),
|
||||
if (closeButton != null && closeButton == true)
|
||||
Positioned(
|
||||
top: 18,
|
||||
right: 0,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: closeCard,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -329,6 +329,10 @@ class _GeneralState extends State<_General> {
|
||||
message: translate('software_render_tip'),
|
||||
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,
|
||||
));
|
||||
if (bind.mainShowOption(key: 'allow-linux-headless')) {
|
||||
children.add(_OptionCheckBox(
|
||||
@ -728,6 +732,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
reverse: true, enabled: enabled),
|
||||
...directIp(context),
|
||||
whitelist(),
|
||||
...autoDisconnect(context),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -906,6 +911,63 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
));
|
||||
}));
|
||||
}
|
||||
|
||||
List<Widget> autoDisconnect(BuildContext context) {
|
||||
TextEditingController controller = TextEditingController();
|
||||
update() => setState(() {});
|
||||
RxBool applyEnabled = false.obs;
|
||||
final optionKey = 'allow-auto-disconnect';
|
||||
final timeoutKey = 'auto-disconnect-timeout';
|
||||
return [
|
||||
_OptionCheckBox(context, 'auto_disconnect_option_tip', optionKey,
|
||||
update: update, enabled: !locked),
|
||||
() {
|
||||
bool enabled =
|
||||
option2bool(optionKey, bind.mainGetOptionSync(key: optionKey));
|
||||
if (!enabled) applyEnabled.value = false;
|
||||
controller.text = bind.mainGetOptionSync(key: timeoutKey);
|
||||
return Offstage(
|
||||
offstage: !enabled,
|
||||
child: _SubLabeledWidget(
|
||||
context,
|
||||
'Timeout in minutes',
|
||||
Row(children: [
|
||||
SizedBox(
|
||||
width: 95,
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
enabled: enabled && !locked,
|
||||
onChanged: (_) => applyEnabled.value = true,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(
|
||||
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
hintText: '10',
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||
),
|
||||
).marginOnly(right: 15),
|
||||
),
|
||||
Obx(() => ElevatedButton(
|
||||
onPressed: applyEnabled.value && enabled && !locked
|
||||
? () async {
|
||||
applyEnabled.value = false;
|
||||
await bind.mainSetOption(
|
||||
key: timeoutKey, value: controller.text);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
translate('Apply'),
|
||||
),
|
||||
))
|
||||
]),
|
||||
enabled: enabled && !locked,
|
||||
),
|
||||
);
|
||||
}(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class _Network extends StatefulWidget {
|
||||
@ -1216,6 +1278,7 @@ class _DisplayState extends State<_Display> {
|
||||
otherRow('Disable clipboard', 'disable_clipboard'),
|
||||
otherRow('Lock after session end', 'lock_after_session_end'),
|
||||
otherRow('Privacy mode', 'privacy_mode'),
|
||||
otherRow('Reverse mouse wheel', 'reverse_mouse_wheel'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -1536,9 +1599,14 @@ Widget _OptionCheckBox(BuildContext context, String label, String key,
|
||||
isServer
|
||||
? await mainSetBoolOption(key, option)
|
||||
: await mainSetLocalBoolOption(key, option);
|
||||
ref.value = isServer
|
||||
final readOption = isServer
|
||||
? mainGetBoolOptionSync(key)
|
||||
: mainGetLocalBoolOptionSync(key);
|
||||
if (reverse) {
|
||||
ref.value = !readOption;
|
||||
} else {
|
||||
ref.value = readOption;
|
||||
}
|
||||
update?.call();
|
||||
}
|
||||
}
|
||||
|
@ -364,15 +364,20 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
final _breadCrumbScroller = ScrollController();
|
||||
final _keyboardNode = FocusNode();
|
||||
final _listSearchBuffer = TimeoutStringBuffer();
|
||||
final _nameColWidth = kDesktopFileTransferNameColWidth.obs;
|
||||
final _modifiedColWidth = kDesktopFileTransferModifiedColWidth.obs;
|
||||
final _nameColWidth = 0.0.obs;
|
||||
final _modifiedColWidth = 0.0.obs;
|
||||
final _sizeColWidth = 0.0.obs;
|
||||
final _fileListScrollController = ScrollController();
|
||||
final _globalHeaderKey = GlobalKey();
|
||||
|
||||
/// [_lastClickTime], [_lastClickEntry] help to handle double click
|
||||
var _lastClickTime =
|
||||
DateTime.now().millisecondsSinceEpoch - bind.getDoubleClickTime() - 1000;
|
||||
Entry? _lastClickEntry;
|
||||
|
||||
double? _windowWidthPrev;
|
||||
double _fileTransferMinimumWidth = 0.0;
|
||||
|
||||
FileController get controller => widget.controller;
|
||||
bool get isLocal => widget.controller.isLocal;
|
||||
FFI get _ffi => widget._ffi;
|
||||
@ -398,6 +403,7 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_handleColumnPorportions();
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
@ -429,6 +435,27 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
);
|
||||
}
|
||||
|
||||
void _handleColumnPorportions() {
|
||||
final windowWidthNow = MediaQuery.of(context).size.width;
|
||||
if (_windowWidthPrev == null) {
|
||||
_windowWidthPrev = windowWidthNow;
|
||||
final defaultColumnWidth = windowWidthNow * 0.115;
|
||||
_fileTransferMinimumWidth = defaultColumnWidth / 3;
|
||||
_nameColWidth.value = defaultColumnWidth;
|
||||
_modifiedColWidth.value = defaultColumnWidth;
|
||||
_sizeColWidth.value = defaultColumnWidth;
|
||||
}
|
||||
|
||||
if (_windowWidthPrev != windowWidthNow) {
|
||||
final difference = windowWidthNow / _windowWidthPrev!;
|
||||
_windowWidthPrev = windowWidthNow;
|
||||
_fileTransferMinimumWidth *= difference;
|
||||
_nameColWidth.value *= difference;
|
||||
_modifiedColWidth.value *= difference;
|
||||
_sizeColWidth.value *= difference;
|
||||
}
|
||||
}
|
||||
|
||||
void onLocationFocusChanged() {
|
||||
debugPrint("focus changed on local");
|
||||
if (_locationNode.hasFocus) {
|
||||
@ -1143,9 +1170,21 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
return false;
|
||||
}
|
||||
|
||||
void _onDrag(double dx, RxDouble column1, RxDouble column2) {
|
||||
if (column1.value + dx <= _fileTransferMinimumWidth ||
|
||||
column2.value - dx <= _fileTransferMinimumWidth) {
|
||||
return;
|
||||
}
|
||||
column1.value += dx;
|
||||
column2.value -= dx;
|
||||
column1.value = max(_fileTransferMinimumWidth, column1.value);
|
||||
column2.value = max(_fileTransferMinimumWidth, column2.value);
|
||||
}
|
||||
|
||||
Widget _buildFileBrowserHeader(BuildContext context) {
|
||||
final padding = EdgeInsets.all(1.0);
|
||||
return SizedBox(
|
||||
key: _globalHeaderKey,
|
||||
height: kDesktopFileTransferHeaderHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
@ -1155,11 +1194,8 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
),
|
||||
DraggableDivider(
|
||||
axis: Axis.vertical,
|
||||
onPointerMove: (dx) {
|
||||
_nameColWidth.value += dx;
|
||||
_nameColWidth.value = min(kDesktopFileTransferMaximumWidth,
|
||||
max(kDesktopFileTransferMinimumWidth, _nameColWidth.value));
|
||||
},
|
||||
onPointerMove: (dx) =>
|
||||
_onDrag(dx, _nameColWidth, _modifiedColWidth),
|
||||
padding: padding,
|
||||
),
|
||||
Obx(
|
||||
@ -1168,15 +1204,12 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
),
|
||||
DraggableDivider(
|
||||
axis: Axis.vertical,
|
||||
onPointerMove: (dx) {
|
||||
_modifiedColWidth.value += dx;
|
||||
_modifiedColWidth.value = min(
|
||||
kDesktopFileTransferMaximumWidth,
|
||||
max(kDesktopFileTransferMinimumWidth,
|
||||
_modifiedColWidth.value));
|
||||
},
|
||||
onPointerMove: (dx) =>
|
||||
_onDrag(dx, _modifiedColWidth, _sizeColWidth),
|
||||
padding: padding),
|
||||
Expanded(child: headerItemFunc(null, SortBy.size, translate("Size")))
|
||||
Expanded(
|
||||
child: headerItemFunc(
|
||||
_sizeColWidth.value, SortBy.size, translate("Size")))
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -1201,23 +1234,20 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
height: kDesktopFileTransferHeaderHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 2,
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
style: headerTextStyle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).marginSymmetric(horizontal: 4),
|
||||
).marginOnly(left: 4),
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: ascending.value != null
|
||||
? Icon(
|
||||
ascending.value!
|
||||
? Icons.keyboard_arrow_up_rounded
|
||||
: Icons.keyboard_arrow_down_rounded,
|
||||
)
|
||||
: const Offstage())
|
||||
ascending.value != null
|
||||
? Icon(
|
||||
ascending.value!
|
||||
? Icons.keyboard_arrow_up_rounded
|
||||
: Icons.keyboard_arrow_down_rounded,
|
||||
)
|
||||
: SizedBox()
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -409,7 +409,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
keyboardEnabled: _keyboardEnabled,
|
||||
remoteCursorMoved: _remoteCursorMoved,
|
||||
textureId: _renderTexture.textureId,
|
||||
useTextureRender: _renderTexture.useTextureRender,
|
||||
useTextureRender: RenderTexture.useTextureRender,
|
||||
listenerBuilder: (child) =>
|
||||
_buildRawTouchAndPointerRegion(child, enterView, leaveView),
|
||||
);
|
||||
@ -485,21 +485,20 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
var c = Provider.of<CanvasModel>(context);
|
||||
final s = c.scale;
|
||||
|
||||
bool isViewAdaptive() => c.viewStyle.style == kRemoteViewStyleAdaptive;
|
||||
bool isViewOriginal() => c.viewStyle.style == kRemoteViewStyleOriginal;
|
||||
|
||||
mouseRegion({child}) => Obx(() {
|
||||
double getCursorScale() {
|
||||
var c = Provider.of<CanvasModel>(context);
|
||||
var cursorScale = 1.0;
|
||||
if (Platform.isWindows) {
|
||||
// debug win10
|
||||
final isViewAdaptive =
|
||||
c.viewStyle.style == kRemoteViewStyleAdaptive;
|
||||
if (zoomCursor.value && isViewAdaptive) {
|
||||
if (zoomCursor.value && isViewAdaptive()) {
|
||||
cursorScale = s * c.devicePixelRatio;
|
||||
}
|
||||
} else {
|
||||
final isViewOriginal =
|
||||
c.viewStyle.style == kRemoteViewStyleOriginal;
|
||||
if (zoomCursor.value || isViewOriginal) {
|
||||
if (zoomCursor.value || isViewOriginal()) {
|
||||
cursorScale = s;
|
||||
}
|
||||
}
|
||||
@ -539,7 +538,11 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
imageWidget = SizedBox(
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
child: Obx(() => Texture(textureId: widget.textureId.value)),
|
||||
child: Obx(() => Texture(
|
||||
textureId: widget.textureId.value,
|
||||
filterQuality:
|
||||
isViewOriginal() ? FilterQuality.none : FilterQuality.low,
|
||||
)),
|
||||
);
|
||||
} else {
|
||||
imageWidget = CustomPaint(
|
||||
@ -573,14 +576,20 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
late final Widget imageWidget;
|
||||
if (c.size.width > 0 && c.size.height > 0) {
|
||||
if (widget.useTextureRender) {
|
||||
final x = Platform.isLinux ? c.x.toInt().toDouble() : c.x;
|
||||
final y = Platform.isLinux ? c.y.toInt().toDouble() : c.y;
|
||||
imageWidget = Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: c.x.toInt().toDouble(),
|
||||
top: c.y.toInt().toDouble(),
|
||||
left: x,
|
||||
top: y,
|
||||
width: c.getDisplayWidth() * s,
|
||||
height: c.getDisplayHeight() * s,
|
||||
child: Texture(textureId: widget.textureId.value),
|
||||
child: Texture(
|
||||
textureId: widget.textureId.value,
|
||||
filterQuality:
|
||||
isViewOriginal() ? FilterQuality.none : FilterQuality.low,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
@ -694,6 +703,7 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
|
||||
customMouseWheelScrollConfig: scrollConfig,
|
||||
child: RawScrollbar(
|
||||
thickness: kScrollbarThickness,
|
||||
thumbColor: Colors.grey,
|
||||
controller: _horizontal,
|
||||
thumbVisibility: false,
|
||||
@ -711,6 +721,7 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
|
||||
customMouseWheelScrollConfig: scrollConfig,
|
||||
child: RawScrollbar(
|
||||
thickness: kScrollbarThickness,
|
||||
thumbColor: Colors.grey,
|
||||
controller: _vertical,
|
||||
thumbVisibility: false,
|
||||
|
@ -101,7 +101,7 @@ class ToolbarState {
|
||||
class _ToolbarTheme {
|
||||
static const Color blueColor = MyTheme.button;
|
||||
static const Color hoverBlueColor = MyTheme.accent;
|
||||
static Color inactiveColor = Colors.grey[800]!;
|
||||
static Color inactiveColor = Colors.grey[800]!;
|
||||
static Color hoverInactiveColor = Colors.grey[850]!;
|
||||
|
||||
static const Color redColor = Colors.redAccent;
|
||||
@ -546,9 +546,11 @@ class _PinMenu extends StatelessWidget {
|
||||
assetName: state.pin ? "assets/pinned.svg" : "assets/unpinned.svg",
|
||||
tooltip: state.pin ? 'Unpin Toolbar' : 'Pin Toolbar',
|
||||
onPressed: state.switchPin,
|
||||
color: state.pin ? _ToolbarTheme.blueColor : _ToolbarTheme.inactiveColor,
|
||||
hoverColor:
|
||||
state.pin ? _ToolbarTheme.hoverBlueColor : _ToolbarTheme.hoverInactiveColor,
|
||||
color:
|
||||
state.pin ? _ToolbarTheme.blueColor : _ToolbarTheme.inactiveColor,
|
||||
hoverColor: state.pin
|
||||
? _ToolbarTheme.hoverBlueColor
|
||||
: _ToolbarTheme.hoverInactiveColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -561,15 +563,18 @@ class _MobileActionMenu extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!ffi.ffiModel.isPeerAndroid) return Offstage();
|
||||
return Obx(()=>_IconMenuButton(
|
||||
assetName: 'assets/actions_mobile.svg',
|
||||
tooltip: 'Mobile Actions',
|
||||
onPressed: () => ffi.dialogManager.toggleMobileActionsOverlay(ffi: ffi),
|
||||
color: ffi.dialogManager.mobileActionsOverlayVisible.isTrue
|
||||
? _ToolbarTheme.blueColor : _ToolbarTheme.inactiveColor,
|
||||
hoverColor: ffi.dialogManager.mobileActionsOverlayVisible.isTrue
|
||||
? _ToolbarTheme.hoverBlueColor : _ToolbarTheme.hoverInactiveColor,
|
||||
));
|
||||
return Obx(() => _IconMenuButton(
|
||||
assetName: 'assets/actions_mobile.svg',
|
||||
tooltip: 'Mobile Actions',
|
||||
onPressed: () =>
|
||||
ffi.dialogManager.toggleMobileActionsOverlay(ffi: ffi),
|
||||
color: ffi.dialogManager.mobileActionsOverlayVisible.isTrue
|
||||
? _ToolbarTheme.blueColor
|
||||
: _ToolbarTheme.inactiveColor,
|
||||
hoverColor: ffi.dialogManager.mobileActionsOverlayVisible.isTrue
|
||||
? _ToolbarTheme.hoverBlueColor
|
||||
: _ToolbarTheme.hoverInactiveColor,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -770,6 +775,7 @@ class ScreenAdjustor {
|
||||
}
|
||||
await WindowController.fromWindowId(windowId)
|
||||
.setFrame(Rect.fromLTWH(left, top, width, height));
|
||||
stateGlobal.setMaximized(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1304,23 +1310,25 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
color: _ToolbarTheme.blueColor,
|
||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||
menuChildren: [
|
||||
mode(modeOnly),
|
||||
keyboardMode(modeOnly),
|
||||
localKeyboardType(),
|
||||
Divider(),
|
||||
view_mode(),
|
||||
viewMode(),
|
||||
Divider(),
|
||||
reverseMouseWheel(),
|
||||
]);
|
||||
}
|
||||
|
||||
mode(String? modeOnly) {
|
||||
keyboardMode(String? modeOnly) {
|
||||
return futureBuilder(future: () async {
|
||||
return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ??
|
||||
_kKeyLegacyMode;
|
||||
}(), hasData: (data) {
|
||||
final groupValue = data as String;
|
||||
List<KeyboardModeMenu> modes = [
|
||||
KeyboardModeMenu(key: _kKeyLegacyMode, menu: 'Legacy mode'),
|
||||
KeyboardModeMenu(key: _kKeyMapMode, menu: 'Map mode'),
|
||||
KeyboardModeMenu(key: _kKeyTranslateMode, menu: 'Translate mode'),
|
||||
List<InputModeMenu> modes = [
|
||||
InputModeMenu(key: _kKeyLegacyMode, menu: 'Legacy mode'),
|
||||
InputModeMenu(key: _kKeyMapMode, menu: 'Map mode'),
|
||||
InputModeMenu(key: _kKeyTranslateMode, menu: 'Translate mode'),
|
||||
];
|
||||
List<RdoMenuButton> list = [];
|
||||
final enabled = !ffi.ffiModel.viewOnly;
|
||||
@ -1330,7 +1338,7 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
sessionId: ffi.sessionId, value: value);
|
||||
}
|
||||
|
||||
for (KeyboardModeMenu mode in modes) {
|
||||
for (InputModeMenu mode in modes) {
|
||||
if (modeOnly != null && mode.key != modeOnly) {
|
||||
continue;
|
||||
} else if (!bind.sessionIsKeyboardModeSupported(
|
||||
@ -1379,7 +1387,7 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
view_mode() {
|
||||
viewMode() {
|
||||
final ffiModel = ffi.ffiModel;
|
||||
final enabled = version_cmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard;
|
||||
return CkbMenuButton(
|
||||
@ -1395,6 +1403,30 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
ffi: ffi,
|
||||
child: Text(translate('View Mode')));
|
||||
}
|
||||
|
||||
reverseMouseWheel() {
|
||||
return futureBuilder(future: () async {
|
||||
final v =
|
||||
await bind.sessionGetReverseMouseWheel(sessionId: ffi.sessionId);
|
||||
if (v != null && v != '') {
|
||||
return v;
|
||||
}
|
||||
return bind.mainGetUserDefaultOption(key: 'reverse_mouse_wheel');
|
||||
}(), hasData: (data) {
|
||||
final enabled = !ffi.ffiModel.viewOnly;
|
||||
onChanged(bool? value) async {
|
||||
if (value == null) return;
|
||||
await bind.sessionSetReverseMouseWheel(
|
||||
sessionId: ffi.sessionId, value: value ? 'Y' : 'N');
|
||||
}
|
||||
|
||||
return CkbMenuButton(
|
||||
value: data == 'Y',
|
||||
onChanged: enabled ? onChanged : null,
|
||||
child: Text(translate('Reverse mouse wheel')),
|
||||
ffi: ffi);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _ChatMenu extends StatefulWidget {
|
||||
@ -1592,26 +1624,26 @@ class _IconMenuButtonState extends State<_IconMenuButton> {
|
||||
width: _ToolbarTheme.buttonSize,
|
||||
height: _ToolbarTheme.buttonSize,
|
||||
child: MenuItemButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(Colors.transparent),
|
||||
padding: MaterialStatePropertyAll(EdgeInsets.zero),
|
||||
overlayColor: MaterialStatePropertyAll(Colors.transparent)),
|
||||
onHover: (value) => setState(() {
|
||||
hover = value;
|
||||
}),
|
||||
onPressed: widget.onPressed,
|
||||
child: Tooltip(
|
||||
message: translate(widget.tooltip),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(_ToolbarTheme.iconRadius),
|
||||
color: hover ? widget.hoverColor : widget.color,
|
||||
),
|
||||
child: icon)),
|
||||
)
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(Colors.transparent),
|
||||
padding: MaterialStatePropertyAll(EdgeInsets.zero),
|
||||
overlayColor: MaterialStatePropertyAll(Colors.transparent)),
|
||||
onHover: (value) => setState(() {
|
||||
hover = value;
|
||||
}),
|
||||
onPressed: widget.onPressed,
|
||||
child: Tooltip(
|
||||
message: translate(widget.tooltip),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(_ToolbarTheme.iconRadius),
|
||||
color: hover ? widget.hoverColor : widget.color,
|
||||
),
|
||||
child: icon)),
|
||||
)),
|
||||
).marginSymmetric(
|
||||
horizontal: widget.hMargin ?? _ToolbarTheme.buttonHMargin,
|
||||
vertical: widget.vMargin ?? _ToolbarTheme.buttonVMargin);
|
||||
@ -1675,18 +1707,17 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
|
||||
onHover: (value) => setState(() {
|
||||
hover = value;
|
||||
}),
|
||||
child: Tooltip(
|
||||
message: translate(widget.tooltip),
|
||||
child: Material(
|
||||
child: Tooltip(
|
||||
message: translate(widget.tooltip),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(_ToolbarTheme.iconRadius),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(_ToolbarTheme.iconRadius),
|
||||
color: hover ? widget.hoverColor : widget.color,
|
||||
),
|
||||
child: icon))
|
||||
),
|
||||
),
|
||||
child: icon))),
|
||||
menuChildren: widget.menuChildren
|
||||
.map((e) => _buildPointerTrackWidget(e, widget.ffi))
|
||||
.toList()));
|
||||
@ -1973,11 +2004,11 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
}
|
||||
}
|
||||
|
||||
class KeyboardModeMenu {
|
||||
class InputModeMenu {
|
||||
final String key;
|
||||
final String menu;
|
||||
|
||||
KeyboardModeMenu({required this.key, required this.menu});
|
||||
InputModeMenu({required this.key, required this.menu});
|
||||
}
|
||||
|
||||
_menuDismissCallback(FFI ffi) => ffi.inputModel.refreshMousePos();
|
||||
|
@ -126,6 +126,7 @@ void runMainApp(bool startService) async {
|
||||
bind.pluginListReload();
|
||||
}
|
||||
gFFI.abModel.loadCache();
|
||||
gFFI.groupModel.loadCache();
|
||||
gFFI.userModel.refreshCurrentUser();
|
||||
runApp(App());
|
||||
// Set window option.
|
||||
@ -154,6 +155,7 @@ void runMobileApp() async {
|
||||
if (isAndroid) androidChannelInit();
|
||||
platformFFI.syncAndroidServiceAppDirConfigPath();
|
||||
gFFI.abModel.loadCache();
|
||||
gFFI.groupModel.loadCache();
|
||||
gFFI.userModel.refreshCurrentUser();
|
||||
runApp(App());
|
||||
}
|
||||
|
@ -275,7 +275,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
return Offstage();
|
||||
}(),
|
||||
_bottomWidget(),
|
||||
gFFI.ffiModel.pi.isSet.isFalse ? emptyOverlay(MyTheme.canvasColor) : Offstage(),
|
||||
gFFI.ffiModel.pi.isSet.isFalse
|
||||
? emptyOverlay(MyTheme.canvasColor)
|
||||
: Offstage(),
|
||||
],
|
||||
)),
|
||||
body: Overlay(
|
||||
@ -316,12 +318,17 @@ class _RemotePageState extends State<RemotePage> {
|
||||
Widget getRawPointerAndKeyBody(Widget child) {
|
||||
final keyboard = gFFI.ffiModel.permissions['keyboard'] != false;
|
||||
return RawPointerMouseRegion(
|
||||
cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer,
|
||||
inputModel: inputModel,
|
||||
child: RawKeyFocusScope(
|
||||
focusNode: _physicalFocusNode,
|
||||
inputModel: inputModel,
|
||||
child: child));
|
||||
cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer,
|
||||
inputModel: inputModel,
|
||||
// Disable RawKeyFocusScope before the connecting is established.
|
||||
// The "Delete" key on the soft keyboard may be grabbed when inputting the password dialog.
|
||||
child: gFFI.ffiModel.pi.isSet.isTrue
|
||||
? RawKeyFocusScope(
|
||||
focusNode: _physicalFocusNode,
|
||||
inputModel: inputModel,
|
||||
child: child)
|
||||
: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget getBottomAppBar(bool keyboard) {
|
||||
|
@ -210,11 +210,199 @@ class ServiceNotRunningNotification extends StatelessWidget {
|
||||
.marginOnly(bottom: 8),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
onPressed: serverModel.toggleService,
|
||||
onPressed: () {
|
||||
if (gFFI.userModel.userName.value.isEmpty && bind.mainGetLocalOption(key: "show-scam-warning") != "N") {
|
||||
_showScamWarning(context, serverModel);
|
||||
} else {
|
||||
serverModel.toggleService();
|
||||
}
|
||||
},
|
||||
label: Text(translate("Start Service")))
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
void _showScamWarning(BuildContext context, ServerModel serverModel) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return ScamWarningDialog(serverModel: serverModel);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ScamWarningDialog extends StatefulWidget {
|
||||
final ServerModel serverModel;
|
||||
|
||||
ScamWarningDialog({required this.serverModel});
|
||||
|
||||
@override
|
||||
_ScamWarningDialogState createState() => _ScamWarningDialogState();
|
||||
}
|
||||
|
||||
class _ScamWarningDialogState extends State<ScamWarningDialog> {
|
||||
int _countdown = 12;
|
||||
bool show_warning = false;
|
||||
late Timer _timer;
|
||||
late ServerModel _serverModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_serverModel = widget.serverModel;
|
||||
startCountdown();
|
||||
}
|
||||
|
||||
void startCountdown() {
|
||||
const oneSecond = Duration(seconds: 1);
|
||||
_timer = Timer.periodic(oneSecond, (timer) {
|
||||
setState(() {
|
||||
_countdown--;
|
||||
if (_countdown <= 0) {
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isButtonLocked = _countdown > 0;
|
||||
|
||||
return AlertDialog(
|
||||
content: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
colors: [
|
||||
Color(0xffe242bc),
|
||||
Color(0xfff4727c),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
),
|
||||
padding: EdgeInsets.all(25.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber_sharp,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
translate("Warning"),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Center(
|
||||
child: Image.asset('assets/scam.png',
|
||||
width: 180,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 18),
|
||||
Text(
|
||||
translate("scam_title"),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 18),
|
||||
Text(
|
||||
translate("scam_text1")+"\n\n"
|
||||
+translate("scam_text2")+"\n",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
value: show_warning,
|
||||
onChanged: (value) {
|
||||
setState((){
|
||||
show_warning = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
translate("Don't show again"),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: isButtonLocked
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).pop();
|
||||
_serverModel.toggleService();
|
||||
if (show_warning) {
|
||||
bind.mainSetLocalOption(key: "show-scam-warning", value: "N");
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.blueAccent,
|
||||
),
|
||||
child: Text(
|
||||
isButtonLocked ? translate("I Agree")+" (${_countdown}s)" : translate("I Agree"),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 15),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.blueAccent,
|
||||
),
|
||||
child: Text(
|
||||
translate("Decline"),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)])),
|
||||
contentPadding: EdgeInsets.all(0.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ServerInfo extends StatelessWidget {
|
||||
|
@ -45,10 +45,12 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
var _enableDirectIPAccess = false;
|
||||
var _enableRecordSession = false;
|
||||
var _autoRecordIncomingSession = false;
|
||||
var _allowAutoDisconnect = false;
|
||||
var _localIP = "";
|
||||
var _directAccessPort = "";
|
||||
var _fingerprint = "";
|
||||
var _buildDate = "";
|
||||
var _autoDisconnectTimeout = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -151,6 +153,20 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
_buildDate = buildDate;
|
||||
}
|
||||
|
||||
final allowAutoDisconnect = option2bool('allow-auto-disconnect',
|
||||
await bind.mainGetOption(key: 'allow-auto-disconnect'));
|
||||
if (allowAutoDisconnect != _allowAutoDisconnect) {
|
||||
update = true;
|
||||
_allowAutoDisconnect = allowAutoDisconnect;
|
||||
}
|
||||
|
||||
final autoDisconnectTimeout =
|
||||
await bind.mainGetOption(key: 'auto-disconnect-timeout');
|
||||
if (autoDisconnectTimeout != _autoDisconnectTimeout) {
|
||||
update = true;
|
||||
_autoDisconnectTimeout = autoDisconnectTimeout;
|
||||
}
|
||||
|
||||
if (update) {
|
||||
setState(() {});
|
||||
}
|
||||
@ -306,6 +322,48 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
await bind.mainSetOption(key: 'direct-server', value: value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
SettingsTile.switchTile(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(translate("auto_disconnect_option_tip")),
|
||||
Offstage(
|
||||
offstage: !_allowAutoDisconnect,
|
||||
child: Text(
|
||||
'${_autoDisconnectTimeout.isEmpty ? '10' : _autoDisconnectTimeout} min',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
)),
|
||||
])),
|
||||
Offstage(
|
||||
offstage: !_allowAutoDisconnect,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Icon(
|
||||
Icons.edit,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () async {
|
||||
final timeout = await changeAutoDisconnectTimeout(
|
||||
_autoDisconnectTimeout);
|
||||
setState(() {
|
||||
_autoDisconnectTimeout = timeout;
|
||||
});
|
||||
}))
|
||||
]),
|
||||
initialValue: _allowAutoDisconnect,
|
||||
onToggle: (_) async {
|
||||
_allowAutoDisconnect = !_allowAutoDisconnect;
|
||||
String value =
|
||||
bool2option('allow-auto-disconnect', _allowAutoDisconnect);
|
||||
await bind.mainSetOption(key: 'allow-auto-disconnect', value: value);
|
||||
setState(() {});
|
||||
},
|
||||
)
|
||||
];
|
||||
if (_hasIgnoreBattery) {
|
||||
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
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';
|
||||
@ -115,9 +116,10 @@ class AbModel {
|
||||
_timerCounter = 0;
|
||||
if (pullError.isNotEmpty) {
|
||||
if (statusCode == 401) {
|
||||
gFFI.userModel.reset(clearAbCache: true);
|
||||
gFFI.userModel.reset(resetOther: true);
|
||||
}
|
||||
}
|
||||
platformFFI.tryHandle({'name': LoadEvent.addressBook});
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,7 +243,8 @@ class AbModel {
|
||||
ret = true;
|
||||
_saveCache();
|
||||
} else {
|
||||
Map<String, dynamic> json = _jsonDecodeResp(resp.body, resp.statusCode);
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
} else if (resp.statusCode == 200) {
|
||||
@ -479,11 +482,12 @@ class AbModel {
|
||||
|
||||
loadCache() async {
|
||||
try {
|
||||
if (_cacheLoadOnceFlag || abLoading.value) return;
|
||||
if (_cacheLoadOnceFlag || abLoading.value || initialized) return;
|
||||
_cacheLoadOnceFlag = true;
|
||||
final access_token = bind.mainGetLocalOption(key: 'access_token');
|
||||
if (access_token.isEmpty) return;
|
||||
final cache = await bind.mainLoadAb();
|
||||
if (abLoading.value) return;
|
||||
final data = jsonDecode(cache);
|
||||
if (data == null || data['access_token'] != access_token) return;
|
||||
_deserialize(data);
|
||||
@ -561,4 +565,12 @@ class AbModel {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reset() async {
|
||||
pullError.value = '';
|
||||
pushError.value = '';
|
||||
tags.clear();
|
||||
peers.clear();
|
||||
await bind.mainClearAb();
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ class RenderTexture {
|
||||
final RxInt textureId = RxInt(-1);
|
||||
int _textureKey = -1;
|
||||
SessionID? _sessionId;
|
||||
final useTextureRender = bind.mainUseTextureRender();
|
||||
static final useTextureRender = bind.mainUseTextureRender();
|
||||
|
||||
final textureRenderer = TextureRgbaRenderer();
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/common/hbbs/hbbs.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/platform_model.dart';
|
||||
@ -11,57 +12,74 @@ import 'package:http/http.dart' as http;
|
||||
class GroupModel {
|
||||
final RxBool groupLoading = false.obs;
|
||||
final RxString groupLoadError = "".obs;
|
||||
final RxString groupId = ''.obs;
|
||||
RxString groupName = ''.obs;
|
||||
final RxList<UserPayload> users = RxList.empty(growable: true);
|
||||
final RxList<Peer> peersShow = RxList.empty(growable: true);
|
||||
final RxList<Peer> peers = RxList.empty(growable: true);
|
||||
final RxString selectedUser = ''.obs;
|
||||
final RxString searchUserText = ''.obs;
|
||||
WeakReference<FFI> parent;
|
||||
var initialized = false;
|
||||
var _cacheLoadOnceFlag = false;
|
||||
var _statusCode = 200;
|
||||
|
||||
bool get emtpy => users.isEmpty && peers.isEmpty;
|
||||
|
||||
GroupModel(this.parent);
|
||||
|
||||
reset() {
|
||||
groupName.value = '';
|
||||
groupId.value = '';
|
||||
users.clear();
|
||||
peersShow.clear();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
Future<void> pull({force = true, quiet = false}) async {
|
||||
/*
|
||||
if (!gFFI.userModel.isLogin || groupLoading.value) return;
|
||||
if (!force && initialized) return;
|
||||
if (!quiet) {
|
||||
groupLoading.value = true;
|
||||
groupLoadError.value = "";
|
||||
}
|
||||
await _pull();
|
||||
try {
|
||||
await _pull();
|
||||
} catch (_) {}
|
||||
groupLoading.value = false;
|
||||
initialized = true;
|
||||
*/
|
||||
platformFFI.tryHandle({'name': LoadEvent.group});
|
||||
if (_statusCode == 401) {
|
||||
gFFI.userModel.reset(resetOther: true);
|
||||
} else {
|
||||
_saveCache();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pull() async {
|
||||
reset();
|
||||
if (bind.mainGetLocalOption(key: 'access_token') == '') {
|
||||
List<UserPayload> tmpUsers = List.empty(growable: true);
|
||||
if (!await _getUsers(tmpUsers)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!await _getGroup()) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('$e');
|
||||
reset();
|
||||
List<Peer> tmpPeers = List.empty(growable: true);
|
||||
if (!await _getPeers(tmpPeers)) {
|
||||
return;
|
||||
}
|
||||
// me first
|
||||
var index = tmpUsers
|
||||
.indexWhere((user) => user.name == gFFI.userModel.userName.value);
|
||||
if (index != -1) {
|
||||
var user = tmpUsers.removeAt(index);
|
||||
tmpUsers.insert(0, user);
|
||||
}
|
||||
users.value = tmpUsers;
|
||||
if (!users.any((u) => u.name == selectedUser.value)) {
|
||||
selectedUser.value = '';
|
||||
}
|
||||
// recover online
|
||||
final oldOnlineIDs = peers.where((e) => e.online).map((e) => e.id).toList();
|
||||
peers.value = tmpPeers;
|
||||
peers
|
||||
.where((e) => oldOnlineIDs.contains(e.id))
|
||||
.map((e) => e.online = true)
|
||||
.toList();
|
||||
groupLoadError.value = '';
|
||||
}
|
||||
|
||||
Future<bool> _getUsers(List<UserPayload> tmpUsers) async {
|
||||
final api = "${await bind.mainGetApiServer()}/api/users";
|
||||
try {
|
||||
var uri0 = Uri.parse(api);
|
||||
final pageSize = 20;
|
||||
final pageSize = 100;
|
||||
var total = 0;
|
||||
int current = 0;
|
||||
do {
|
||||
@ -74,86 +92,67 @@ class GroupModel {
|
||||
queryParameters: {
|
||||
'current': current.toString(),
|
||||
'pageSize': pageSize.toString(),
|
||||
if (gFFI.userModel.isAdmin.isFalse) 'grp': groupId.value,
|
||||
'accessible': '',
|
||||
'status': '1',
|
||||
});
|
||||
final resp = await http.get(uri, headers: getHttpHeaders());
|
||||
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
|
||||
Map<String, dynamic> json = jsonDecode(utf8.decode(resp.bodyBytes));
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
_statusCode = resp.statusCode;
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
if (json['error'] == 'Admin required!' ||
|
||||
json['error']
|
||||
.toString()
|
||||
.contains('ambiguous column name: status')) {
|
||||
throw translate('upgrade_rustdesk_server_pro_to_{1.1.10}_tip');
|
||||
} else {
|
||||
if (json.containsKey('total')) {
|
||||
if (total == 0) total = json['total'];
|
||||
if (json.containsKey('data')) {
|
||||
final data = json['data'];
|
||||
if (data is List) {
|
||||
for (final user in data) {
|
||||
final u = UserPayload.fromJson(user);
|
||||
if (!users.any((e) => e.name == u.name)) {
|
||||
users.add(u);
|
||||
}
|
||||
}
|
||||
throw json['error'];
|
||||
}
|
||||
}
|
||||
if (resp.statusCode != 200) {
|
||||
throw 'HTTP ${resp.statusCode}';
|
||||
}
|
||||
if (json.containsKey('total')) {
|
||||
if (total == 0) total = json['total'];
|
||||
if (json.containsKey('data')) {
|
||||
final data = json['data'];
|
||||
if (data is List) {
|
||||
for (final user in data) {
|
||||
final u = UserPayload.fromJson(user);
|
||||
int index = tmpUsers.indexWhere((e) => e.name == u.name);
|
||||
if (index < 0) {
|
||||
tmpUsers.add(u);
|
||||
} else {
|
||||
tmpUsers[index] = u;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (current * pageSize < total);
|
||||
return true;
|
||||
} catch (err) {
|
||||
debugPrint('$err');
|
||||
debugPrint('get accessible users: $err');
|
||||
groupLoadError.value = err.toString();
|
||||
} finally {
|
||||
_pullUserPeers();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _getGroup() async {
|
||||
final url = await bind.mainGetApiServer();
|
||||
final body = {
|
||||
'id': await bind.mainGetMyId(),
|
||||
'uuid': await bind.mainGetUuid()
|
||||
};
|
||||
try {
|
||||
final response = await http.post(Uri.parse('$url/api/currentGroup'),
|
||||
headers: getHttpHeaders(), body: json.encode(body));
|
||||
final status = response.statusCode;
|
||||
if (status == 401 || status == 400) {
|
||||
return false;
|
||||
}
|
||||
final data = json.decode(utf8.decode(response.bodyBytes));
|
||||
final error = data['error'];
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
groupName.value = data['name'] ?? '';
|
||||
groupId.value = data['guid'] ?? '';
|
||||
return groupId.value.isNotEmpty && groupName.isNotEmpty;
|
||||
} catch (e) {
|
||||
debugPrint('$e');
|
||||
groupLoadError.value = e.toString();
|
||||
} finally {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> _pullUserPeers() async {
|
||||
peersShow.clear();
|
||||
final api = "${await bind.mainGetApiServer()}/api/peers";
|
||||
Future<bool> _getPeers(List<Peer> tmpPeers) async {
|
||||
try {
|
||||
final api = "${await bind.mainGetApiServer()}/api/peers";
|
||||
var uri0 = Uri.parse(api);
|
||||
final pageSize =
|
||||
20; // ????????????????????????????????????????????????????? stupid stupis, how about >20 peers
|
||||
final pageSize = 100;
|
||||
var total = 0;
|
||||
int current = 0;
|
||||
var queryParameters = {
|
||||
'current': current.toString(),
|
||||
'pageSize': pageSize.toString(),
|
||||
};
|
||||
if (!gFFI.userModel.isAdmin.value) {
|
||||
queryParameters.addAll({'grp': groupId.value});
|
||||
}
|
||||
do {
|
||||
current += 1;
|
||||
var queryParameters = {
|
||||
'current': current.toString(),
|
||||
'pageSize': pageSize.toString(),
|
||||
'accessible': '',
|
||||
'status': '1',
|
||||
};
|
||||
var uri = Uri(
|
||||
scheme: uri0.scheme,
|
||||
host: uri0.host,
|
||||
@ -161,32 +160,107 @@ class GroupModel {
|
||||
port: uri0.port,
|
||||
queryParameters: queryParameters);
|
||||
final resp = await http.get(uri, headers: getHttpHeaders());
|
||||
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
|
||||
Map<String, dynamic> json = jsonDecode(utf8.decode(resp.bodyBytes));
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
} else {
|
||||
if (json.containsKey('total')) {
|
||||
if (total == 0) total = json['total'];
|
||||
if (json.containsKey('data')) {
|
||||
final data = json['data'];
|
||||
if (data is List) {
|
||||
for (final p in data) {
|
||||
final peerPayload = PeerPayload.fromJson(p);
|
||||
final peer = PeerPayload.toPeer(peerPayload);
|
||||
if (!peersShow.any((e) => e.id == peer.id)) {
|
||||
peersShow.add(peer);
|
||||
}
|
||||
}
|
||||
_statusCode = resp.statusCode;
|
||||
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
}
|
||||
if (resp.statusCode != 200) {
|
||||
throw 'HTTP ${resp.statusCode}';
|
||||
}
|
||||
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) {
|
||||
for (final p in data) {
|
||||
final peerPayload = PeerPayload.fromJson(p);
|
||||
final peer = PeerPayload.toPeer(peerPayload);
|
||||
int index = tmpPeers.indexWhere((e) => e.id == peer.id);
|
||||
if (index < 0) {
|
||||
tmpPeers.add(peer);
|
||||
} else {
|
||||
tmpPeers[index] = peer;
|
||||
}
|
||||
if (tmpPeers.length >= 1000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (current * pageSize < total);
|
||||
return true;
|
||||
} catch (err) {
|
||||
debugPrint('$err');
|
||||
debugPrint('get accessible peers: $err');
|
||||
groupLoadError.value = err.toString();
|
||||
} finally {}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _jsonDecodeResp(String body, int statusCode) {
|
||||
try {
|
||||
Map<String, dynamic> json = jsonDecode(body);
|
||||
return json;
|
||||
} catch (e) {
|
||||
final err = body.isNotEmpty && body.length < 128 ? body : e.toString();
|
||||
if (statusCode != 200) {
|
||||
throw 'HTTP $statusCode, $err';
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
void _saveCache() {
|
||||
try {
|
||||
final map = (<String, dynamic>{
|
||||
"access_token": bind.mainGetLocalOption(key: 'access_token'),
|
||||
"users": users.map((e) => e.toGroupCacheJson()).toList(),
|
||||
'peers': peers.map((e) => e.toGroupCacheJson()).toList()
|
||||
});
|
||||
bind.mainSaveGroup(json: jsonEncode(map));
|
||||
} catch (e) {
|
||||
debugPrint('group save:$e');
|
||||
}
|
||||
}
|
||||
|
||||
loadCache() async {
|
||||
try {
|
||||
if (_cacheLoadOnceFlag || groupLoading.value || initialized) return;
|
||||
_cacheLoadOnceFlag = true;
|
||||
final access_token = bind.mainGetLocalOption(key: 'access_token');
|
||||
if (access_token.isEmpty) return;
|
||||
final cache = await bind.mainLoadGroup();
|
||||
if (groupLoading.value) return;
|
||||
final data = jsonDecode(cache);
|
||||
if (data == null || data['access_token'] != access_token) return;
|
||||
users.clear();
|
||||
peers.clear();
|
||||
if (data['users'] is List) {
|
||||
for (var u in data['users']) {
|
||||
users.add(UserPayload.fromJson(u));
|
||||
}
|
||||
}
|
||||
if (data['peers'] is List) {
|
||||
for (final peer in data['peers']) {
|
||||
peers.add(Peer.fromJson(peer));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("load group cache: $e");
|
||||
}
|
||||
}
|
||||
|
||||
reset() async {
|
||||
groupLoadError.value = '';
|
||||
users.clear();
|
||||
peers.clear();
|
||||
selectedUser.value = '';
|
||||
await bind.mainClearGroup();
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,8 @@ class PlatformFFI {
|
||||
|
||||
int getRgbaSize(SessionID sessionId) =>
|
||||
_ffiBind.sessionGetRgbaSize(sessionId: sessionId);
|
||||
void nextRgba(SessionID sessionId) => _ffiBind.sessionNextRgba(sessionId: sessionId);
|
||||
void nextRgba(SessionID sessionId) =>
|
||||
_ffiBind.sessionNextRgba(sessionId: sessionId);
|
||||
void registerTexture(SessionID sessionId, int ptr) =>
|
||||
_ffiBind.sessionRegisterTexture(sessionId: sessionId, ptr: ptr);
|
||||
|
||||
@ -198,7 +199,7 @@ class PlatformFFI {
|
||||
version = await getVersion();
|
||||
}
|
||||
|
||||
Future<bool> _tryHandle(Map<String, dynamic> evt) async {
|
||||
Future<bool> tryHandle(Map<String, dynamic> evt) async {
|
||||
final name = evt['name'];
|
||||
if (name != null) {
|
||||
final handlers = _eventHandlers[name];
|
||||
@ -216,14 +217,15 @@ class PlatformFFI {
|
||||
|
||||
/// Start listening to the Rust core's events and frames.
|
||||
void _startListenEvent(RustdeskImpl rustdeskImpl) {
|
||||
final appType = _appType == kAppTypeDesktopRemote ? '$_appType,$kWindowId' : _appType;
|
||||
final appType =
|
||||
_appType == kAppTypeDesktopRemote ? '$_appType,$kWindowId' : _appType;
|
||||
var sink = rustdeskImpl.startGlobalEventStream(appType: appType);
|
||||
sink.listen((message) {
|
||||
() async {
|
||||
try {
|
||||
Map<String, dynamic> event = json.decode(message);
|
||||
// _tryHandle here may be more flexible than _eventCallback
|
||||
if (!await _tryHandle(event)) {
|
||||
if (!await tryHandle(event)) {
|
||||
if (_eventCallback != null) {
|
||||
await _eventCallback!(event);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'platform_model.dart';
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:collection/collection.dart';
|
||||
@ -7,7 +8,7 @@ import 'package:collection/collection.dart';
|
||||
class Peer {
|
||||
final String id;
|
||||
String hash;
|
||||
String username;
|
||||
String username; // pc username
|
||||
String hostname;
|
||||
String platform;
|
||||
String alias;
|
||||
@ -16,6 +17,7 @@ class Peer {
|
||||
String rdpPort;
|
||||
String rdpUsername;
|
||||
bool online = false;
|
||||
String loginName; //login username
|
||||
|
||||
String getId() {
|
||||
if (alias != '') {
|
||||
@ -34,7 +36,8 @@ class Peer {
|
||||
tags = json['tags'] ?? [],
|
||||
forceAlwaysRelay = json['forceAlwaysRelay'] == 'true',
|
||||
rdpPort = json['rdpPort'] ?? '',
|
||||
rdpUsername = json['rdpUsername'] ?? '';
|
||||
rdpUsername = json['rdpUsername'] ?? '',
|
||||
loginName = json['loginName'] ?? '';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
@ -48,6 +51,7 @@ class Peer {
|
||||
"forceAlwaysRelay": forceAlwaysRelay.toString(),
|
||||
"rdpPort": rdpPort,
|
||||
"rdpUsername": rdpUsername,
|
||||
'loginName': loginName,
|
||||
};
|
||||
}
|
||||
|
||||
@ -63,6 +67,16 @@ class Peer {
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> toGroupCacheJson() {
|
||||
return <String, dynamic>{
|
||||
"id": id,
|
||||
"username": username,
|
||||
"hostname": hostname,
|
||||
"platform": platform,
|
||||
"login_name": loginName,
|
||||
};
|
||||
}
|
||||
|
||||
Peer({
|
||||
required this.id,
|
||||
required this.hash,
|
||||
@ -74,6 +88,7 @@ class Peer {
|
||||
required this.forceAlwaysRelay,
|
||||
required this.rdpPort,
|
||||
required this.rdpUsername,
|
||||
required this.loginName,
|
||||
});
|
||||
|
||||
Peer.loading()
|
||||
@ -88,6 +103,7 @@ class Peer {
|
||||
forceAlwaysRelay: false,
|
||||
rdpPort: '',
|
||||
rdpUsername: '',
|
||||
loginName: '',
|
||||
);
|
||||
bool equal(Peer other) {
|
||||
return id == other.id &&
|
||||
@ -99,21 +115,24 @@ class Peer {
|
||||
tags.equals(other.tags) &&
|
||||
forceAlwaysRelay == other.forceAlwaysRelay &&
|
||||
rdpPort == other.rdpPort &&
|
||||
rdpUsername == other.rdpUsername;
|
||||
rdpUsername == other.rdpUsername &&
|
||||
loginName == other.loginName;
|
||||
}
|
||||
|
||||
Peer.copy(Peer other)
|
||||
: this(
|
||||
id: other.id,
|
||||
hash: other.hash,
|
||||
username: other.username,
|
||||
hostname: other.hostname,
|
||||
platform: other.platform,
|
||||
alias: other.alias,
|
||||
tags: other.tags.toList(),
|
||||
forceAlwaysRelay: other.forceAlwaysRelay,
|
||||
rdpPort: other.rdpPort,
|
||||
rdpUsername: other.rdpUsername);
|
||||
id: other.id,
|
||||
hash: other.hash,
|
||||
username: other.username,
|
||||
hostname: other.hostname,
|
||||
platform: other.platform,
|
||||
alias: other.alias,
|
||||
tags: other.tags.toList(),
|
||||
forceAlwaysRelay: other.forceAlwaysRelay,
|
||||
rdpPort: other.rdpPort,
|
||||
rdpUsername: other.rdpUsername,
|
||||
loginName: other.loginName,
|
||||
);
|
||||
}
|
||||
|
||||
enum UpdateEvent { online, load }
|
||||
@ -121,11 +140,14 @@ enum UpdateEvent { online, load }
|
||||
class Peers extends ChangeNotifier {
|
||||
final String name;
|
||||
final String loadEvent;
|
||||
List<Peer> peers;
|
||||
List<Peer> peers = List.empty(growable: true);
|
||||
final RxList<Peer>? initPeers;
|
||||
UpdateEvent event = UpdateEvent.load;
|
||||
static const _cbQueryOnlines = 'callback_query_onlines';
|
||||
|
||||
Peers({required this.name, required this.peers, required this.loadEvent}) {
|
||||
Peers(
|
||||
{required this.name, required this.initPeers, required this.loadEvent}) {
|
||||
peers = initPeers ?? [];
|
||||
platformFFI.registerEventHandler(_cbQueryOnlines, name, (evt) async {
|
||||
_updateOnlineState(evt);
|
||||
});
|
||||
@ -176,7 +198,11 @@ class Peers extends ChangeNotifier {
|
||||
|
||||
void _updatePeers(Map<String, dynamic> evt) {
|
||||
final onlineStates = _getOnlineStates();
|
||||
peers = _decodePeers(evt['peers']);
|
||||
if (initPeers != null) {
|
||||
peers = initPeers!;
|
||||
} else {
|
||||
peers = _decodePeers(evt['peers']);
|
||||
}
|
||||
for (var peer in peers) {
|
||||
final state = onlineStates[peer.id];
|
||||
peer.online = state != null && state != false;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@ -16,8 +17,6 @@ enum PeerTabIndex {
|
||||
group,
|
||||
}
|
||||
|
||||
const String defaultGroupTabname = 'Group';
|
||||
|
||||
class PeerTabModel with ChangeNotifier {
|
||||
WeakReference<FFI> parent;
|
||||
int get currentTab => _currentTab;
|
||||
@ -27,7 +26,7 @@ class PeerTabModel with ChangeNotifier {
|
||||
'Favorites',
|
||||
'Discovered',
|
||||
'Address Book',
|
||||
//defaultGroupTabname,
|
||||
'Group',
|
||||
];
|
||||
final List<IconData> icons = [
|
||||
Icons.access_time_filled,
|
||||
@ -36,7 +35,10 @@ class PeerTabModel with ChangeNotifier {
|
||||
IconFont.addressBook,
|
||||
Icons.group,
|
||||
];
|
||||
final List<bool> _isVisible = List.filled(5, true, growable: false);
|
||||
List<bool> get isVisible => _isVisible;
|
||||
List<int> get indexs => List.generate(tabNames.length, (index) => index);
|
||||
List<int> get visibleIndexs => indexs.where((e) => _isVisible[e]).toList();
|
||||
List<Peer> _selectedPeers = List.empty(growable: true);
|
||||
List<Peer> get selectedPeers => _selectedPeers;
|
||||
bool _multiSelectionMode = false;
|
||||
@ -49,12 +51,29 @@ class PeerTabModel with ChangeNotifier {
|
||||
String get lastId => _lastId;
|
||||
|
||||
PeerTabModel(this.parent) {
|
||||
// visible
|
||||
try {
|
||||
final option = bind.getLocalFlutterOption(k: 'peer-tab-visible');
|
||||
if (option.isNotEmpty) {
|
||||
List<dynamic> decodeList = jsonDecode(option);
|
||||
if (decodeList.length == _isVisible.length) {
|
||||
for (int i = 0; i < _isVisible.length; i++) {
|
||||
if (decodeList[i] is bool) {
|
||||
_isVisible[i] = decodeList[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("failed to get peer tab visible list:$e");
|
||||
}
|
||||
// init currentTab
|
||||
_currentTab =
|
||||
int.tryParse(bind.getLocalFlutterOption(k: 'peer-tab-index')) ?? 0;
|
||||
if (_currentTab < 0 || _currentTab >= tabNames.length) {
|
||||
_currentTab = 0;
|
||||
}
|
||||
_trySetCurrentTabToFirstVisible();
|
||||
}
|
||||
|
||||
setCurrentTab(int index) {
|
||||
@ -64,17 +83,9 @@ class PeerTabModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
String tabTooltip(int index, String groupName) {
|
||||
String tabTooltip(int index) {
|
||||
if (index >= 0 && index < tabNames.length) {
|
||||
if (index == PeerTabIndex.group.index) {
|
||||
if (gFFI.userModel.isAdmin.value || groupName.isEmpty) {
|
||||
return translate(defaultGroupTabname);
|
||||
} else {
|
||||
return '${translate('Group')}: $groupName';
|
||||
}
|
||||
} else {
|
||||
return translate(tabNames[index]);
|
||||
}
|
||||
return translate(tabNames[index]);
|
||||
}
|
||||
assert(false);
|
||||
return index.toString();
|
||||
@ -158,4 +169,31 @@ class PeerTabModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTabVisible(int index, bool visible) {
|
||||
if (index >= 0 && index < _isVisible.length) {
|
||||
if (_isVisible[index] != visible) {
|
||||
_isVisible[index] = visible;
|
||||
if (index == _currentTab && !visible) {
|
||||
_trySetCurrentTabToFirstVisible();
|
||||
} else if (visible && visibleIndexs.length == 1) {
|
||||
_currentTab = index;
|
||||
}
|
||||
try {
|
||||
bind.setLocalFlutterOption(
|
||||
k: 'peer-tab-visible', v: jsonEncode(_isVisible));
|
||||
} catch (_) {}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_trySetCurrentTabToFirstVisible() {
|
||||
if (!_isVisible[_currentTab]) {
|
||||
int firstVisible = _isVisible.indexWhere((e) => e);
|
||||
if (firstVisible >= 0) {
|
||||
_currentTab = firstVisible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ class UserModel {
|
||||
refreshingUser = false;
|
||||
final status = response.statusCode;
|
||||
if (status == 401 || status == 400) {
|
||||
reset(clearAbCache: status == 401);
|
||||
reset(resetOther: status == 401);
|
||||
return;
|
||||
}
|
||||
final data = json.decode(utf8.decode(response.bodyBytes));
|
||||
@ -84,11 +84,13 @@ class UserModel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reset({bool clearAbCache = false}) async {
|
||||
Future<void> reset({bool resetOther = false}) async {
|
||||
await bind.mainSetLocalOption(key: 'access_token', value: '');
|
||||
await bind.mainSetLocalOption(key: 'user_info', value: '');
|
||||
if (clearAbCache) await bind.mainClearAb();
|
||||
await gFFI.groupModel.reset();
|
||||
if (resetOther) {
|
||||
await gFFI.abModel.reset();
|
||||
await gFFI.groupModel.reset();
|
||||
}
|
||||
userName.value = '';
|
||||
}
|
||||
|
||||
@ -120,7 +122,7 @@ class UserModel {
|
||||
} catch (e) {
|
||||
debugPrint("request /api/logout failed: err=$e");
|
||||
} finally {
|
||||
await reset(clearAbCache: true);
|
||||
await reset(resetOther: true);
|
||||
gFFI.dialogManager.dismissByTag(tag);
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +328,7 @@ packages:
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "6c4181330f4ed80c1cb5670bd61aa75115f9f748"
|
||||
resolved-ref: e51fddf7f3b46d4423b7aa79ba824a45a1ea1b7a
|
||||
url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
@ -396,6 +396,15 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
dynamic_layouts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/dynamic_layouts"
|
||||
ref: "0023d01996576e494094793a6552463f01c5627a"
|
||||
resolved-ref: "0023d01996576e494094793a6552463f01c5627a"
|
||||
url: "https://github.com/flutter/packages.git"
|
||||
source: git
|
||||
version: "0.0.1+1"
|
||||
event_bus:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -100,6 +100,11 @@ dependencies:
|
||||
uuid: ^3.0.7
|
||||
auto_size_text_field: ^2.2.1
|
||||
flex_color_picker: ^3.3.0
|
||||
dynamic_layouts:
|
||||
git:
|
||||
url: https://github.com/flutter/packages.git
|
||||
path: packages/dynamic_layouts
|
||||
ref: 0023d01996576e494094793a6552463f01c5627a
|
||||
|
||||
dev_dependencies:
|
||||
icons_launcher: ^2.0.4
|
||||
|
@ -70,6 +70,8 @@ pub use win::ENIGO_INPUT_EXTRA_VALUE;
|
||||
mod macos;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::Enigo;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::ENIGO_INPUT_EXTRA_VALUE;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
|
@ -37,6 +37,9 @@ const kUCKeyActionDisplay: u16 = 3;
|
||||
const kUCKeyTranslateDeadKeysBit: OptionBits = 1 << 31;
|
||||
const BUF_LEN: usize = 4;
|
||||
|
||||
/// The event source user data value of cgevent.
|
||||
pub const ENIGO_INPUT_EXTRA_VALUE: i64 = 100;
|
||||
|
||||
#[allow(improper_ctypes)]
|
||||
#[allow(non_snake_case)]
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
@ -131,6 +134,7 @@ impl Enigo {
|
||||
|
||||
fn post(&self, event: CGEvent) {
|
||||
event.set_flags(self.flags);
|
||||
event.set_integer_value_field(EventField::EVENT_SOURCE_USER_DATA, ENIGO_INPUT_EXTRA_VALUE);
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
mod macos_impl;
|
||||
|
||||
pub mod keycodes;
|
||||
pub use self::macos_impl::Enigo;
|
||||
pub use self::macos_impl::{Enigo, ENIGO_INPUT_EXTRA_VALUE};
|
||||
|
@ -1,5 +1,4 @@
|
||||
mod win_impl;
|
||||
|
||||
pub mod keycodes;
|
||||
pub use self::win_impl::Enigo;
|
||||
pub use self::win_impl::ENIGO_INPUT_EXTRA_VALUE;
|
||||
pub use self::win_impl::{Enigo, ENIGO_INPUT_EXTRA_VALUE};
|
||||
|
@ -624,6 +624,8 @@ message BackNotification {
|
||||
PrivacyModeState privacy_mode_state = 1;
|
||||
BlockInputState block_input_state = 2;
|
||||
}
|
||||
// Supplementary message, for "PrvOnFailed" and "PrvOffFailed"
|
||||
string details = 3;
|
||||
}
|
||||
|
||||
message ElevationRequestWithLogon {
|
||||
|
@ -230,6 +230,7 @@ pub struct PeerConfig {
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub view_style: String,
|
||||
// Image scroll style, scrollbar or scroll auto
|
||||
#[serde(
|
||||
default = "PeerConfig::default_scroll_style",
|
||||
deserialize_with = "PeerConfig::deserialize_scroll_style",
|
||||
@ -276,6 +277,13 @@ pub struct PeerConfig {
|
||||
pub keyboard_mode: String,
|
||||
#[serde(flatten)]
|
||||
pub view_only: ViewOnly,
|
||||
// Mouse wheel or touchpad scroll mode
|
||||
#[serde(
|
||||
default = "PeerConfig::default_reverse_mouse_wheel",
|
||||
deserialize_with = "PeerConfig::deserialize_reverse_mouse_wheel",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub reverse_mouse_wheel: String,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
@ -319,6 +327,7 @@ impl Default for PeerConfig {
|
||||
show_quality_monitor: Default::default(),
|
||||
keyboard_mode: Default::default(),
|
||||
view_only: Default::default(),
|
||||
reverse_mouse_wheel: Self::default_reverse_mouse_wheel(),
|
||||
custom_resolutions: Default::default(),
|
||||
options: Self::default_options(),
|
||||
ui_flutter: Default::default(),
|
||||
@ -1130,6 +1139,11 @@ impl PeerConfig {
|
||||
deserialize_image_quality,
|
||||
UserDefaultConfig::read().get("image_quality")
|
||||
);
|
||||
serde_field_string!(
|
||||
default_reverse_mouse_wheel,
|
||||
deserialize_reverse_mouse_wheel,
|
||||
UserDefaultConfig::read().get("reverse_mouse_wheel")
|
||||
);
|
||||
|
||||
fn default_custom_image_quality() -> Vec<i32> {
|
||||
let f: f64 = UserDefaultConfig::read()
|
||||
@ -1483,7 +1497,11 @@ impl UserDefaultConfig {
|
||||
}
|
||||
|
||||
pub fn set(&mut self, key: String, value: String) {
|
||||
self.options.insert(key, value);
|
||||
if value.is_empty() {
|
||||
self.options.remove(&key);
|
||||
} else {
|
||||
self.options.insert(key, value);
|
||||
}
|
||||
self.store();
|
||||
}
|
||||
|
||||
@ -1632,6 +1650,106 @@ macro_rules! deserialize_default {
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct GroupPeer {
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub id: String,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub username: String,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub hostname: String,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub platform: String,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub login_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct GroupUser {
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct Group {
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub access_token: String,
|
||||
#[serde(default, deserialize_with = "deserialize_vec_groupuser")]
|
||||
pub users: Vec<GroupUser>,
|
||||
#[serde(default, deserialize_with = "deserialize_vec_grouppeer")]
|
||||
pub peers: Vec<GroupPeer>,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
fn path() -> PathBuf {
|
||||
let filename = format!("{}_group", APP_NAME.read().unwrap().clone());
|
||||
Config::path(filename)
|
||||
}
|
||||
|
||||
pub fn store(json: String) {
|
||||
if let Ok(mut file) = std::fs::File::create(Self::path()) {
|
||||
let data = compress(json.as_bytes());
|
||||
let max_len = 64 * 1024 * 1024;
|
||||
if data.len() > max_len {
|
||||
// maxlen of function decompress
|
||||
return;
|
||||
}
|
||||
if let Ok(data) = symmetric_crypt(&data, true) {
|
||||
file.write_all(&data).ok();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn load() -> Self {
|
||||
if let Ok(mut file) = std::fs::File::open(Self::path()) {
|
||||
let mut data = vec![];
|
||||
if file.read_to_end(&mut data).is_ok() {
|
||||
if let Ok(data) = symmetric_crypt(&data, false) {
|
||||
let data = decompress(&data);
|
||||
if let Ok(group) = serde_json::from_str::<Self>(&String::from_utf8_lossy(&data))
|
||||
{
|
||||
return group;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Self::remove();
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn remove() {
|
||||
std::fs::remove_file(Self::path()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
deserialize_default!(deserialize_string, String);
|
||||
deserialize_default!(deserialize_bool, bool);
|
||||
deserialize_default!(deserialize_i32, i32);
|
||||
@ -1640,6 +1758,8 @@ deserialize_default!(deserialize_vec_string, Vec<String>);
|
||||
deserialize_default!(deserialize_vec_i32_string_i32, Vec<(i32, String, i32)>);
|
||||
deserialize_default!(deserialize_vec_discoverypeer, Vec<DiscoveryPeer>);
|
||||
deserialize_default!(deserialize_vec_abpeer, Vec<AbPeer>);
|
||||
deserialize_default!(deserialize_vec_groupuser, Vec<GroupUser>);
|
||||
deserialize_default!(deserialize_vec_grouppeer, Vec<GroupPeer>);
|
||||
deserialize_default!(deserialize_keypair, KeyPair);
|
||||
deserialize_default!(deserialize_size, Size);
|
||||
deserialize_default!(deserialize_hashmap_string_string, HashMap<String, String>);
|
||||
|
@ -1195,6 +1195,17 @@ impl LoginConfigHandler {
|
||||
self.save_config(config);
|
||||
}
|
||||
|
||||
/// Save reverse mouse wheel ("", "Y") to the current config.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - The reverse mouse wheel ("", "Y").
|
||||
pub fn save_reverse_mouse_wheel(&mut self, value: String) {
|
||||
let mut config = self.load_config();
|
||||
config.reverse_mouse_wheel = value;
|
||||
self.save_config(config);
|
||||
}
|
||||
|
||||
/// Save scroll style to the current config.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -1485,6 +1485,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
Some(back_notification::Union::BlockInputState(state)) => {
|
||||
self.handle_back_msg_block_input(
|
||||
state.enum_value_or(back_notification::BlockInputState::BlkStateUnknown),
|
||||
notification.details,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@ -1492,6 +1493,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
if !self
|
||||
.handle_back_msg_privacy_mode(
|
||||
state.enum_value_or(back_notification::PrivacyModeState::PrvStateUnknown),
|
||||
notification.details,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@ -1508,22 +1510,42 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler.update_block_input_state(on);
|
||||
}
|
||||
|
||||
async fn handle_back_msg_block_input(&mut self, state: back_notification::BlockInputState) {
|
||||
async fn handle_back_msg_block_input(
|
||||
&mut self,
|
||||
state: back_notification::BlockInputState,
|
||||
details: String,
|
||||
) {
|
||||
match state {
|
||||
back_notification::BlockInputState::BlkOnSucceeded => {
|
||||
self.update_block_input_state(true);
|
||||
}
|
||||
back_notification::BlockInputState::BlkOnFailed => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Block user input", "Failed", "");
|
||||
self.handler.msgbox(
|
||||
"custom-error",
|
||||
"Block user input",
|
||||
if details.is_empty() {
|
||||
"Failed"
|
||||
} else {
|
||||
&details
|
||||
},
|
||||
"",
|
||||
);
|
||||
self.update_block_input_state(false);
|
||||
}
|
||||
back_notification::BlockInputState::BlkOffSucceeded => {
|
||||
self.update_block_input_state(false);
|
||||
}
|
||||
back_notification::BlockInputState::BlkOffFailed => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Unblock user input", "Failed", "");
|
||||
self.handler.msgbox(
|
||||
"custom-error",
|
||||
"Unblock user input",
|
||||
if details.is_empty() {
|
||||
"Failed"
|
||||
} else {
|
||||
&details
|
||||
},
|
||||
"",
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -1541,6 +1563,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
async fn handle_back_msg_privacy_mode(
|
||||
&mut self,
|
||||
state: back_notification::PrivacyModeState,
|
||||
details: String,
|
||||
) -> bool {
|
||||
match state {
|
||||
back_notification::PrivacyModeState::PrvOnByOther => {
|
||||
@ -1573,8 +1596,16 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.update_privacy_mode(false);
|
||||
}
|
||||
back_notification::PrivacyModeState::PrvOnFailed => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Privacy mode", "Failed", "");
|
||||
self.handler.msgbox(
|
||||
"custom-error",
|
||||
"Privacy mode",
|
||||
if details.is_empty() {
|
||||
"Failed"
|
||||
} else {
|
||||
&details
|
||||
},
|
||||
"",
|
||||
);
|
||||
self.update_privacy_mode(false);
|
||||
}
|
||||
back_notification::PrivacyModeState::PrvOffSucceeded => {
|
||||
@ -1588,8 +1619,16 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.update_privacy_mode(false);
|
||||
}
|
||||
back_notification::PrivacyModeState::PrvOffFailed => {
|
||||
self.handler
|
||||
.msgbox("custom-error", "Privacy mode", "Failed to turn off", "");
|
||||
self.handler.msgbox(
|
||||
"custom-error",
|
||||
"Privacy mode",
|
||||
if details.is_empty() {
|
||||
"Failed to turn off"
|
||||
} else {
|
||||
&details
|
||||
},
|
||||
"",
|
||||
);
|
||||
}
|
||||
back_notification::PrivacyModeState::PrvOffUnknown => {
|
||||
self.handler
|
||||
|
@ -940,9 +940,12 @@ pub async fn post_request_sync(url: String, body: String, header: &str) -> Resul
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Message {
|
||||
pub fn make_privacy_mode_msg_with_details(state: back_notification::PrivacyModeState, details: String) -> Message {
|
||||
let mut misc = Misc::new();
|
||||
let mut back_notification = BackNotification::new();
|
||||
let mut back_notification = BackNotification {
|
||||
details,
|
||||
..Default::default()
|
||||
};
|
||||
back_notification.set_privacy_mode_state(state);
|
||||
misc.set_back_notification(back_notification);
|
||||
let mut msg_out = Message::new();
|
||||
@ -950,6 +953,11 @@ pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Mess
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Message {
|
||||
make_privacy_mode_msg_with_details(state, "".to_owned())
|
||||
}
|
||||
|
||||
pub fn is_keyboard_mode_supported(keyboard_mode: &KeyboardMode, version_number: i64) -> bool {
|
||||
match keyboard_mode {
|
||||
KeyboardMode::Legacy => true,
|
||||
@ -1042,24 +1050,6 @@ pub async fn get_key(sync: bool) -> String {
|
||||
key
|
||||
}
|
||||
|
||||
pub fn is_peer_version_ge(v: &str) -> bool {
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
if let Some(session) = crate::ui::CUR_SESSION.lock().unwrap().as_ref() {
|
||||
return session.get_peer_version() >= hbb_common::get_version_number(v);
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = crate::flutter::SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*crate::flutter::CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
return session.get_peer_version() >= hbb_common::get_version_number(v);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn pk_to_fingerprint(pk: Vec<u8>) -> String {
|
||||
let s: String = pk.iter().map(|u| format!("{:02x}", u)).collect();
|
||||
s.chars()
|
||||
|
@ -404,7 +404,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
crate::ui_interface::start_option_status_sync();
|
||||
} else if args[0] == "--cm-no-ui" {
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "windows")))]
|
||||
crate::flutter::connection_manager::start_cm_no_ui();
|
||||
return None;
|
||||
} else {
|
||||
|
@ -21,8 +21,6 @@ use hbb_common::{
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicI32, Ordering},
|
||||
@ -293,12 +291,26 @@ 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) {
|
||||
session.save_keyboard_mode(value);
|
||||
session.save_keyboard_mode(value.clone());
|
||||
_mode_updated = true;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if _mode_updated {
|
||||
crate::keyboard::update_grab_get_key_name();
|
||||
crate::keyboard::update_grab_get_key_name(&value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String> {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
Some(session.get_reverse_mouse_wheel())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) {
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
session.save_reverse_mouse_wheel(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,7 +376,9 @@ pub fn session_handle_flutter_key_event(
|
||||
down_or_up: bool,
|
||||
) {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
let keyboard_mode = session.get_keyboard_mode();
|
||||
session.handle_flutter_key_event(
|
||||
&keyboard_mode,
|
||||
&name,
|
||||
platform_code,
|
||||
position_code,
|
||||
@ -383,11 +397,12 @@ pub fn session_handle_flutter_key_event(
|
||||
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) {
|
||||
let keyboard_mode = session.get_keyboard_mode();
|
||||
if _enter {
|
||||
set_cur_session_id(_session_id);
|
||||
session.enter();
|
||||
set_cur_session_id_(_session_id, &keyboard_mode);
|
||||
session.enter(keyboard_mode);
|
||||
} else {
|
||||
session.leave();
|
||||
session.leave(keyboard_mode);
|
||||
}
|
||||
}
|
||||
SyncReturn(())
|
||||
@ -1062,6 +1077,9 @@ pub fn main_get_last_remote_id() -> String {
|
||||
}
|
||||
|
||||
pub fn main_get_software_update_url() -> String {
|
||||
if get_local_option("enable-check-update".to_string()) != "N" {
|
||||
crate::common::check_software_update();
|
||||
}
|
||||
crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
@ -1170,6 +1188,24 @@ pub fn main_load_ab() -> String {
|
||||
serde_json::to_string(&config::Ab::load()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn main_save_group(json: String) {
|
||||
if json.len() > 1024 {
|
||||
std::thread::spawn(|| {
|
||||
config::Group::store(json);
|
||||
});
|
||||
} else {
|
||||
config::Group::store(json);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_clear_group() {
|
||||
config::Group::remove();
|
||||
}
|
||||
|
||||
pub fn main_load_group() -> String {
|
||||
serde_json::to_string(&config::Group::load()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
||||
super::flutter::session_send_pointer(session_id, msg);
|
||||
}
|
||||
@ -1492,9 +1528,15 @@ 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) {
|
||||
set_cur_session_id_(session_id, &session.get_keyboard_mode())
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cur_session_id_(session_id: SessionID, keyboard_mode: &str) {
|
||||
super::flutter::set_cur_session_id(session_id);
|
||||
#[cfg(windows)]
|
||||
crate::keyboard::update_grab_get_key_name();
|
||||
crate::keyboard::update_grab_get_key_name(keyboard_mode);
|
||||
}
|
||||
|
||||
pub fn install_show_run_without_install() -> SyncReturn<bool> {
|
||||
|
@ -157,6 +157,7 @@ fn handle_config_options(config_options: HashMap<String, String>) {
|
||||
Config::set_options(options);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub fn is_pro() -> bool {
|
||||
PRO.lock().unwrap().clone()
|
||||
|
15
src/ipc.rs
15
src/ipc.rs
@ -31,11 +31,10 @@ use hbb_common::{
|
||||
use crate::rendezvous_mediator::RendezvousMediator;
|
||||
|
||||
// State with timestamp, because std::time::Instant cannot be serialized
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum PrivacyModeState {
|
||||
OffSucceeded,
|
||||
OffFailed,
|
||||
OffByPeer,
|
||||
OffUnknown,
|
||||
}
|
||||
@ -234,6 +233,8 @@ pub enum Data {
|
||||
#[cfg(windows)]
|
||||
SyncWinCpuUsage(Option<f64>),
|
||||
FileTransferLog(String),
|
||||
#[cfg(windows)]
|
||||
ControlledSessionCount(usize),
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
@ -482,6 +483,16 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Data::Plugin(plugin) => crate::plugin::ipc::handle_plugin(plugin, stream).await,
|
||||
#[cfg(windows)]
|
||||
Data::ControlledSessionCount(_) => {
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&Data::ControlledSessionCount(
|
||||
crate::Connection::alive_conns().len()
|
||||
))
|
||||
.await
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -56,22 +56,6 @@ pub mod client {
|
||||
static ref IS_GRAB_STARTED: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
|
||||
}
|
||||
|
||||
pub fn get_keyboard_mode() -> String {
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
return session.get_keyboard_mode();
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
return session.get_keyboard_mode();
|
||||
}
|
||||
"legacy".to_string()
|
||||
}
|
||||
|
||||
pub fn start_grab_loop() {
|
||||
let mut lock = IS_GRAB_STARTED.lock().unwrap();
|
||||
if *lock {
|
||||
@ -82,12 +66,12 @@ pub mod client {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn change_grab_status(state: GrabState) {
|
||||
pub fn change_grab_status(state: GrabState, keyboard_mode: &str) {
|
||||
match state {
|
||||
GrabState::Ready => {}
|
||||
GrabState::Run => {
|
||||
#[cfg(windows)]
|
||||
update_grab_get_key_name();
|
||||
update_grab_get_key_name(keyboard_mode);
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
KEYBOARD_HOOKED.swap(true, Ordering::SeqCst);
|
||||
|
||||
@ -95,7 +79,10 @@ pub mod client {
|
||||
rdev::enable_grab();
|
||||
}
|
||||
GrabState::Wait => {
|
||||
release_remote_keys();
|
||||
#[cfg(windows)]
|
||||
rdev::set_get_key_unicode(false);
|
||||
|
||||
release_remote_keys(keyboard_mode);
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
KEYBOARD_HOOKED.swap(false, Ordering::SeqCst);
|
||||
@ -110,17 +97,16 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_event(event: &Event, lock_modes: Option<i32>) -> KeyboardMode {
|
||||
let keyboard_mode = get_keyboard_mode_enum();
|
||||
pub fn process_event(keyboard_mode: &str, event: &Event, lock_modes: Option<i32>) {
|
||||
let keyboard_mode = get_keyboard_mode_enum(keyboard_mode);
|
||||
|
||||
if is_long_press(&event) {
|
||||
return keyboard_mode;
|
||||
return;
|
||||
}
|
||||
|
||||
for key_event in event_to_key_events(&event, keyboard_mode, lock_modes) {
|
||||
send_key_event(&key_event);
|
||||
}
|
||||
keyboard_mode
|
||||
}
|
||||
|
||||
pub fn get_modifiers_state(
|
||||
@ -215,10 +201,11 @@ pub mod client {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn update_grab_get_key_name() {
|
||||
match get_keyboard_mode_enum() {
|
||||
KeyboardMode::Map => rdev::set_get_key_unicode(false),
|
||||
KeyboardMode::Translate => rdev::set_get_key_unicode(true),
|
||||
pub fn update_grab_get_key_name(keyboard_mode: &str) {
|
||||
match keyboard_mode {
|
||||
"map" => rdev::set_get_key_unicode(false),
|
||||
"translate" => rdev::set_get_key_unicode(true),
|
||||
"legacy" => rdev::set_get_key_unicode(true),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
@ -229,6 +216,22 @@ static mut IS_0X021D_DOWN: bool = false;
|
||||
#[cfg(target_os = "macos")]
|
||||
static mut IS_LEFT_OPTION_DOWN: bool = false;
|
||||
|
||||
fn get_keyboard_mode() -> String {
|
||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||
return session.get_keyboard_mode();
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(session) = SESSIONS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&*CUR_SESSION_ID.read().unwrap())
|
||||
{
|
||||
return session.get_keyboard_mode();
|
||||
}
|
||||
"legacy".to_string()
|
||||
}
|
||||
|
||||
pub fn start_grab_loop() {
|
||||
std::env::set_var("KEYBOARD_ONLY", "y");
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
@ -239,11 +242,10 @@ pub fn start_grab_loop() {
|
||||
return Some(event);
|
||||
}
|
||||
|
||||
let mut _keyboard_mode = KeyboardMode::Map;
|
||||
let _scan_code = event.position_code;
|
||||
let _code = event.platform_code as KeyCode;
|
||||
let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) {
|
||||
_keyboard_mode = client::process_event(&event, None);
|
||||
client::process_event(&get_keyboard_mode(), &event, None);
|
||||
if is_press {
|
||||
None
|
||||
} else {
|
||||
@ -304,7 +306,7 @@ pub fn start_grab_loop() {
|
||||
if let Key::Unknown(keycode) = key {
|
||||
log::error!("rdev get unknown key, keycode is : {:?}", keycode);
|
||||
} else {
|
||||
client::process_event(&event, None);
|
||||
client::process_event(&get_keyboard_mode(), &event, None);
|
||||
}
|
||||
None
|
||||
}
|
||||
@ -330,7 +332,7 @@ pub fn is_long_press(event: &Event) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn release_remote_keys() {
|
||||
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();
|
||||
@ -339,23 +341,16 @@ pub fn release_remote_keys() {
|
||||
let event = event_type_to_event(event_type);
|
||||
// to-do: BUG
|
||||
// Release events should be sent to the corresponding sessions, instead of current session.
|
||||
client::process_event(&event, None);
|
||||
client::process_event(keyboard_mode, &event, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_keyboard_mode_enum() -> KeyboardMode {
|
||||
match client::get_keyboard_mode().as_str() {
|
||||
pub fn get_keyboard_mode_enum(keyboard_mode: &str) -> KeyboardMode {
|
||||
match keyboard_mode {
|
||||
"map" => KeyboardMode::Map,
|
||||
"translate" => KeyboardMode::Translate,
|
||||
"legacy" => KeyboardMode::Legacy,
|
||||
_ => {
|
||||
// Set "map" as default mode if version >= 1.2.0.
|
||||
if crate::is_peer_version_ge("1.2.0") {
|
||||
KeyboardMode::Map
|
||||
} else {
|
||||
KeyboardMode::Legacy
|
||||
}
|
||||
}
|
||||
_ => KeyboardMode::Map,
|
||||
}
|
||||
}
|
||||
|
||||
@ -583,6 +578,8 @@ pub fn event_type_to_event(event_type: EventType) -> Event {
|
||||
unicode: None,
|
||||
platform_code: 0,
|
||||
position_code: 0,
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
extra_data: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
65
src/lang.rs
65
src/lang.rs
@ -1,5 +1,7 @@
|
||||
use hbb_common::regex::Regex;
|
||||
use std::ops::Deref;
|
||||
|
||||
mod ar;
|
||||
mod ca;
|
||||
mod cn;
|
||||
mod cs;
|
||||
@ -17,6 +19,8 @@ mod it;
|
||||
mod ja;
|
||||
mod ko;
|
||||
mod kz;
|
||||
mod lt;
|
||||
mod lv;
|
||||
mod nl;
|
||||
mod pl;
|
||||
mod ptbr;
|
||||
@ -32,8 +36,6 @@ mod tr;
|
||||
mod tw;
|
||||
mod ua;
|
||||
mod vn;
|
||||
mod lt;
|
||||
mod ar;
|
||||
|
||||
pub const LANGS: &[(&str, &str)] = &[
|
||||
("en", "English"),
|
||||
@ -69,6 +71,7 @@ pub const LANGS: &[(&str, &str)] = &[
|
||||
("sl", "Slovenščina"),
|
||||
("ro", "Română"),
|
||||
("lt", "Lietuvių"),
|
||||
("lv", "Latviešu"),
|
||||
("ar", "العربية"),
|
||||
];
|
||||
|
||||
@ -134,19 +137,71 @@ pub fn translate_locale(name: String, locale: &str) -> String {
|
||||
"sl" => sl::T.deref(),
|
||||
"ro" => ro::T.deref(),
|
||||
"lt" => lt::T.deref(),
|
||||
"lv" => lv::T.deref(),
|
||||
"ar" => ar::T.deref(),
|
||||
_ => en::T.deref(),
|
||||
};
|
||||
let (name, placeholder_value) = extract_placeholder(&name);
|
||||
let replace = |s: &&str| {
|
||||
let mut s = s.to_string();
|
||||
if let Some(value) = placeholder_value.as_ref() {
|
||||
s = s.replace("{}", &value);
|
||||
}
|
||||
s
|
||||
};
|
||||
if let Some(v) = m.get(&name as &str) {
|
||||
if v.is_empty() {
|
||||
if lang != "en" {
|
||||
if let Some(v) = en::T.get(&name as &str) {
|
||||
return v.to_string();
|
||||
return replace(v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return v.to_string();
|
||||
return replace(v);
|
||||
}
|
||||
}
|
||||
name
|
||||
replace(&name.as_str())
|
||||
}
|
||||
|
||||
// Matching pattern is {}
|
||||
// Write {value} in the UI and {} in the translation file
|
||||
//
|
||||
// Example:
|
||||
// Write in the UI: translate("There are {24} hours in a day")
|
||||
// Write in the translation file: ("There are {} hours in a day", "{} hours make up a day")
|
||||
fn extract_placeholder(input: &str) -> (String, Option<String>) {
|
||||
if let Ok(re) = Regex::new(r#"\{(.*?)\}"#) {
|
||||
if let Some(captures) = re.captures(input) {
|
||||
if let Some(inner_match) = captures.get(1) {
|
||||
let name = re.replace(input, "{}").to_string();
|
||||
let value = inner_match.as_str().to_string();
|
||||
return (name, Some(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
(input.to_string(), None)
|
||||
}
|
||||
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_extract_placeholders() {
|
||||
use super::extract_placeholder as f;
|
||||
|
||||
assert_eq!(f(""), ("".to_string(), None));
|
||||
assert_eq!(
|
||||
f("{3} sessions"),
|
||||
("{} sessions".to_string(), Some("3".to_string()))
|
||||
);
|
||||
assert_eq!(f(" } { "), (" } { ".to_string(), None));
|
||||
// Allow empty value
|
||||
assert_eq!(
|
||||
f("{} sessions"),
|
||||
("{} sessions".to_string(), Some("".to_string()))
|
||||
);
|
||||
// Match only the first one
|
||||
assert_eq!(
|
||||
f("{2} times {4} makes {8}"),
|
||||
("{} times {4} makes {8}".to_string(), Some("2".to_string()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "اسم مستخدم او كلمة مرور خاطئة"),
|
||||
("The verification code is incorrect or has expired", "رمز التحقق غير صحيح او منتهي"),
|
||||
("Edit Tag", "تحرير علامة"),
|
||||
("Unremember Password", "عدم تذكر كلمة المرور"),
|
||||
("Forget Password", "عدم تذكر كلمة المرور"),
|
||||
("Favorites", "المفضلة"),
|
||||
("Add to Favorites", "اضافة للمفضلة"),
|
||||
("Remove from Favorites", "ازالة من المفضلة"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Credencials incorrectes"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Editar tag"),
|
||||
("Unremember Password", "Contrasenya oblidada"),
|
||||
("Forget Password", "Contrasenya oblidada"),
|
||||
("Favorites", "Preferits"),
|
||||
("Add to Favorites", "Afegir a preferits"),
|
||||
("Remove from Favorites", "Treure de preferits"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "提供的登录信息错误"),
|
||||
("The verification code is incorrect or has expired", "验证码错误或已超时"),
|
||||
("Edit Tag", "修改标签"),
|
||||
("Unremember Password", "忘记密码"),
|
||||
("Forget Password", "忘记密码"),
|
||||
("Favorites", "收藏"),
|
||||
("Add to Favorites", "加入到收藏"),
|
||||
("Remove from Favorites", "从收藏中删除"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", "请升级专业版服务器到{}或更高版本!"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Nesprávné přihlašovací údaje"),
|
||||
("The verification code is incorrect or has expired", "Ověřovací kód je nesprávný, nebo jeho platnost vypršela"),
|
||||
("Edit Tag", "Upravit štítek"),
|
||||
("Unremember Password", "Přestat si pamatovat heslo"),
|
||||
("Forget Password", "Přestat si pamatovat heslo"),
|
||||
("Favorites", "Oblíbené"),
|
||||
("Add to Favorites", "Přidat do oblíbených"),
|
||||
("Remove from Favorites", "Odebrat z oblíbených"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Forkerte registreringsdata"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Rediger nøgleord"),
|
||||
("Unremember Password", "Glem adgangskoden"),
|
||||
("Forget Password", "Glem adgangskoden"),
|
||||
("Favorites", "Favoritter"),
|
||||
("Add to Favorites", "Tilføj til favoritter"),
|
||||
("Remove from Favorites", "Fjern favoritter"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -146,9 +146,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Set Password", "Passwort festlegen"),
|
||||
("OS Password", "Betriebssystem-Passwort"),
|
||||
("install_tip", "Aufgrund der Benutzerkontensteuerung (UAC) kann RustDesk in manchen Fällen nicht ordnungsgemäß funktionieren. Um die Benutzerkontensteuerung zu umgehen, klicken Sie bitte auf die Schaltfläche unten und installieren RustDesk auf dem System."),
|
||||
("Click to upgrade", "Upgrade"),
|
||||
("Click to upgrade", "Zum Upgraden klicken"),
|
||||
("Click to download", "Zum Herunterladen klicken"),
|
||||
("Click to update", "Update"),
|
||||
("Click to update", "Zum Aktualisieren klicken"),
|
||||
("Configure", "Konfigurieren"),
|
||||
("config_acc", "Um Ihren PC aus der Ferne zu steuern, müssen Sie RustDesk Zugriffsrechte erteilen."),
|
||||
("config_screen", "Um aus der Ferne auf Ihren PC zugreifen zu können, müssen Sie RustDesk die Berechtigung \"Bildschirmaufnahme\" erteilen."),
|
||||
@ -193,7 +193,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logging in...", "Anmelden …"),
|
||||
("Enable RDP session sharing", "RDP-Sitzungsfreigabe aktivieren"),
|
||||
("Auto Login", "Automatisch anmelden (nur gültig, wenn Sie \"Nach Sitzungsende sperren\" aktiviert haben)"),
|
||||
("Enable Direct IP Access", "Direkten IP-Zugang aktivieren"),
|
||||
("Enable Direct IP Access", "Direkten IP-Zugriff aktivieren"),
|
||||
("Rename", "Umbenennen"),
|
||||
("Space", "Speicherplatz"),
|
||||
("Create Desktop Shortcut", "Desktop-Verknüpfung erstellen"),
|
||||
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Falsche Anmeldedaten"),
|
||||
("The verification code is incorrect or has expired", "Der Verifizierungscode ist falsch oder abgelaufen"),
|
||||
("Edit Tag", "Tag bearbeiten"),
|
||||
("Unremember Password", "Gespeichertes Passwort löschen"),
|
||||
("Forget Password", "Gespeichertes Passwort löschen"),
|
||||
("Favorites", "Favoriten"),
|
||||
("Add to Favorites", "Zu Favoriten hinzufügen"),
|
||||
("Remove from Favorites", "Aus Favoriten entfernen"),
|
||||
@ -427,7 +427,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Elevation Error", "Berechtigungsfehler"),
|
||||
("Ask the remote user for authentication", "Den entfernten Benutzer zur Authentifizierung auffordern"),
|
||||
("Choose this if the remote account is administrator", "Wählen Sie dies, wenn das entfernte Konto Administrator ist."),
|
||||
("Transmit the username and password of administrator", "Übermitteln Sie den Benutzernamen und das Passwort des Administrators"),
|
||||
("Transmit the username and password of administrator", "Benutzernamen und Passwort des Administrators übertragen"),
|
||||
("still_click_uac_tip", "Der entfernte Benutzer muss immer noch im UAC-Fenster von RustDesk auf \"Ja\" klicken."),
|
||||
("Request Elevation", "Erhöhte Rechte anfordern"),
|
||||
("wait_accept_uac_tip", "Bitte warten Sie, bis der entfernte Benutzer den UAC-Dialog akzeptiert hat."),
|
||||
@ -541,7 +541,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Change Color", "Farbe ändern"),
|
||||
("Primary Color", "Primärfarbe"),
|
||||
("HSV Color", "HSV-Farbe"),
|
||||
("Installation Successful!", ""),
|
||||
("Installation failed!", ""),
|
||||
("Installation Successful!", "Installation erfolgreich!"),
|
||||
("Installation failed!", "Installation fehlgeschlagen!"),
|
||||
("Reverse mouse wheel", "Mausrad rückwärts drehen"),
|
||||
("{} sessions", "{} Sitzungen"),
|
||||
("scam_title", "Sie werden möglicherweise BETROGEN!"),
|
||||
("scam_text1", "Wenn Sie mit jemandem telefonieren, den Sie NICHT KENNEN, dem Sie NICHT VERTRAUEN und der Sie gebeten hat, RustDesk zu benutzen und den Dienst zu starten, fahren Sie nicht fort und legen Sie sofort auf."),
|
||||
("scam_text2", "Es handelt sich wahrscheinlich um einen Betrüger, der versucht, Ihr Geld oder andere private Informationen zu stehlen."),
|
||||
("Don't show again", "Nicht mehr anzeigen"),
|
||||
("I Agree", "Ich bin einverstanden"),
|
||||
("Decline", "Ablehnen"),
|
||||
("Timeout in minutes", "Zeitüberschreitung in Minuten"),
|
||||
("auto_disconnect_option_tip", "Automatisches Schließen eingehender Sitzungen bei Inaktivität des Benutzers"),
|
||||
("Connection failed due to inactivity", "Automatische Trennung der Verbindung aufgrund von Inaktivität"),
|
||||
("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!"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Λάθος διαπιστευτήρια"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Επεξεργασία ετικέτας"),
|
||||
("Unremember Password", "Διαγραφή απομνημονευμένου κωδικού"),
|
||||
("Forget Password", "Διαγραφή απομνημονευμένου κωδικού"),
|
||||
("Favorites", "Αγαπημένα"),
|
||||
("Add to Favorites", "Προσθήκη στα αγαπημένα"),
|
||||
("Remove from Favorites", "Κατάργηση από τα Αγαπημένα"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -77,5 +77,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "Failed to refresh address book"),
|
||||
("push_ab_failed_tip", "Failed to sync address book to server"),
|
||||
("synced_peer_readded_tip", "The devices that were present in the recent sessions will be synchronized back to the address book."),
|
||||
("View Mode", "View mode"),
|
||||
("Block user input", "Block User Input"),
|
||||
("Start session recording", "Start Session Recording"),
|
||||
("Stop session recording", "Stop Session Recording"),
|
||||
("Enable remote configuration modification", "Enable Remote Configuration Modification"),
|
||||
("scam_title", "You May Be Being SCAMMED!"),
|
||||
("scam_text1", "If you are on the phone with someone you DON'T know AND TRUST who has asked you to use RustDesk and start the service, do not proceed and hang up immediately."),
|
||||
("scam_text2", "They are likely a scammer trying to steal your money or other private information."),
|
||||
("Don't show again", "Don't show again"),
|
||||
("I Agree", "I Agree"),
|
||||
("Decline", "Decline"),
|
||||
("auto_disconnect_option_tip", "Automatically close incoming sessions on user inactivity"),
|
||||
("Connection failed due to inactivity", "Automatically disconnected due to inactivity"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Please upgrade RustDesk Server Pro to version {} or newer!")
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Identigilo aŭ pasvorto erara"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Redakti etikedo"),
|
||||
("Unremember Password", "Forgesi pasvorton"),
|
||||
("Forget Password", "Forgesi pasvorton"),
|
||||
("Favorites", "Favorataj"),
|
||||
("Add to Favorites", "Aldoni al la favorataj"),
|
||||
("Remove from Favorites", "Forigi el la favorataj"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Credenciales incorrectas"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Editar tag"),
|
||||
("Unremember Password", "Olvidar contraseña"),
|
||||
("Forget Password", "Olvidar contraseña"),
|
||||
("Favorites", "Favoritos"),
|
||||
("Add to Favorites", "Agregar a favoritos"),
|
||||
("Remove from Favorites", "Quitar de favoritos"),
|
||||
@ -253,7 +253,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Touch mode", "Modo táctil"),
|
||||
("Mouse mode", "Modo ratón"),
|
||||
("One-Finger Tap", "Toque con un dedo"),
|
||||
("Left Mouse", "Ratón izquierdo"),
|
||||
("Left Mouse", "Botón izquierdo"),
|
||||
("One-Long Tap", "Un toque largo"),
|
||||
("Two-Finger Tap", "Toque con dos dedos"),
|
||||
("Right Mouse", "Botón derecho"),
|
||||
@ -283,7 +283,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you accept?", "¿Aceptas?"),
|
||||
("Open System Setting", "Configuración del sistema abierto"),
|
||||
("How to get Android input permission?", "¿Cómo obtener el permiso de entrada de Android?"),
|
||||
("android_input_permission_tip1", "Para que un dispositivo remoto controle su dispositivo Android a través del mouse o toque, debe permitir que RustDesk use el servicio de \"Accesibilidad\"."),
|
||||
("android_input_permission_tip1", "Para que un dispositivo remoto controle su dispositivo Android a través del ratón o toque, debe permitir que RustDesk use el servicio de \"Accesibilidad\"."),
|
||||
("android_input_permission_tip2", "Vaya a la página de configuración del sistema que se abrirá a continuación, busque y acceda a [Servicios instalados], active el servicio [RustDesk Input]."),
|
||||
("android_new_connection_tip", "Se recibió una nueva solicitud de control para el dispositivo actual."),
|
||||
("android_service_will_start_tip", "Habilitar la captura de pantalla iniciará automáticamente el servicio, lo que permitirá que otros dispositivos soliciten una conexión desde este dispositivo."),
|
||||
@ -541,7 +541,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Change Color", "Cambiar Color"),
|
||||
("Primary Color", "Color Primario"),
|
||||
("HSV Color", "Color HSV"),
|
||||
("Installation Successful!", ""),
|
||||
("Installation failed!", ""),
|
||||
("Installation Successful!", "Instalación exitosa"),
|
||||
("Installation failed!", "La instalación ha fallado"),
|
||||
("Reverse mouse wheel", "Invertir rueda del ratón"),
|
||||
("{} sessions", "{} sesiones"),
|
||||
("scam_title", "Podrías estar siendo ESTAFADO!"),
|
||||
("scam_text1", "Si estás al teléfono con alguien a quien NO conoces y en quien NO confías y te ha pedido que uses RustDesk e inicies el servicio, no lo hagas y cuelga inmediatamente."),
|
||||
("scam_text2", "Probablemente son estafadores tratando de robar tu dinero o información privada."),
|
||||
("Don't show again", "No mostrar de nuevo"),
|
||||
("I Agree", "Estoy de acuerdo"),
|
||||
("Decline", "Rechazar"),
|
||||
("Timeout in minutes", "Tiempo de espera en minutos"),
|
||||
("auto_disconnect_option_tip", "Cerrar sesiones entrantes automáticamente por inactividad del usuario."),
|
||||
("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"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "اعتبارنامه نادرست است"),
|
||||
("The verification code is incorrect or has expired", "کد تأیید نادرست است یا منقضی شده است"),
|
||||
("Edit Tag", "ویرایش برچسب"),
|
||||
("Unremember Password", "رمز عبور ذخیره نشود"),
|
||||
("Forget Password", "رمز عبور ذخیره نشود"),
|
||||
("Favorites", "اتصالات دلخواه"),
|
||||
("Add to Favorites", "افزودن به علاقه مندی ها"),
|
||||
("Remove from Favorites", "از علاقه مندی ها حذف شود"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Identifiant ou mot de passe erroné"),
|
||||
("The verification code is incorrect or has expired", "Le code de vérification est incorrect ou a expiré"),
|
||||
("Edit Tag", "Gestion étiquettes"),
|
||||
("Unremember Password", "Oublier le Mot de passe"),
|
||||
("Forget Password", "Oublier le Mot de passe"),
|
||||
("Favorites", "Favoris"),
|
||||
("Add to Favorites", "Ajouter aux Favoris"),
|
||||
("Remove from Favorites", "Retirer des favoris"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Hibás felhasználónév vagy jelszó"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Címke szerkesztése"),
|
||||
("Unremember Password", "A jelszó megjegyzésének törlése"),
|
||||
("Forget Password", "A jelszó megjegyzésének törlése"),
|
||||
("Favorites", "Kedvencek"),
|
||||
("Add to Favorites", "Hozzáadás a kedvencekhez"),
|
||||
("Remove from Favorites", "Eltávolítás a kedvencekből"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Nama pengguna atau kata sandi salah"),
|
||||
("The verification code is incorrect or has expired", "Kode verifikasi salah atau sudah kadaluarsa"),
|
||||
("Edit Tag", "Ubah Tag"),
|
||||
("Unremember Password", "Lupakan Kata Sandi"),
|
||||
("Forget Password", "Lupakan Kata Sandi"),
|
||||
("Favorites", "Favorit"),
|
||||
("Add to Favorites", "Tambah ke Favorit"),
|
||||
("Remove from Favorites", "Hapus dari favorit"),
|
||||
@ -473,7 +473,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("empty_favorite_tip", "Belum ada rekan favorit?\nTemukan seseorang untuk terhubung dan tambahkan ke favorit!"),
|
||||
("empty_lan_tip", "Sepertinya kami belum menemukan rekan"),
|
||||
("empty_address_book_tip", "Tampaknya saat ini tidak ada rekan yang terdaftar dalam buku alamat Anda"),
|
||||
("eg: admin", ""),
|
||||
("eg: admin", "contoh: admin"),
|
||||
("Empty Username", "Nama pengguna kosong"),
|
||||
("Empty Password", "Kata sandi kosong"),
|
||||
("Me", "Saya"),
|
||||
@ -541,7 +541,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Change Color", "Ganti warna"),
|
||||
("Primary Color", "Warna utama"),
|
||||
("HSV Color", "Warna HSV"),
|
||||
("Installation Successful!", ""),
|
||||
("Installation failed!", ""),
|
||||
("Installation Successful!", "Instalasi berhasil!"),
|
||||
("Installation failed!", "Instalasi gagal!"),
|
||||
("Reverse mouse wheel", "Balikkan arah scroll mouse!"),
|
||||
("{} sessions", "sesi {}"),
|
||||
("scam_title", "Kemungkinan Anda Sedang DITIPU!"),
|
||||
("scam_text1", "Jika Anda sedang berbicara di telepon dengan seseorang yang TIDAK dikenal dan mereka meminta anda untuk menggunakan RustDesk, jangan lanjutkan dan segera tutup panggilan."),
|
||||
("scam_text2", "Kemungkinan besar mereka adalah komplotan penipu yang berusaha mencuri uang atau informasi pribadi anda."),
|
||||
("Don't show again", "Jangan tampilkan lagi"),
|
||||
("I Agree", "Saya setuju"),
|
||||
("Decline", "Tolak"),
|
||||
("Timeout in minutes", "Batasan Waktu dalam Menit"),
|
||||
("auto_disconnect_option_tip", "Secara otomatis akan menutup sesi ketika pengguna tidak beraktivitas"),
|
||||
("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!"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -75,9 +75,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you want to enter again?", "Vuoi riprovare?"),
|
||||
("Connection Error", "Errore di connessione"),
|
||||
("Error", "Errore"),
|
||||
("Reset by the peer", "Reimpostata dal peer"),
|
||||
("Reset by the peer", "Reimpostata dal dispositivo remoto"),
|
||||
("Connecting...", "Connessione..."),
|
||||
("Connection in progress. Please wait.", "Connessione in corso..."),
|
||||
("Connection in progress. Please wait.", "Connessione..."),
|
||||
("Please try 1 minute later", "Riprova fra 1 minuto"),
|
||||
("Login Error", "Errore accesso"),
|
||||
("Successful", "Completato"),
|
||||
@ -210,7 +210,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Settings", "Impostazioni"),
|
||||
("Username", "Nome utente"),
|
||||
("Invalid port", "Numero porta non valido"),
|
||||
("Closed manually by the peer", "Chiuso manualmente dal peer"),
|
||||
("Closed manually by the peer", "Chiuso manualmente dal dispositivo remoto"),
|
||||
("Enable remote configuration modification", "Abilita la modifica remota della configurazione"),
|
||||
("Run without install", "Esegui senza installare"),
|
||||
("Connect via relay", "Collegati tramite relay"),
|
||||
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Credenziali errate"),
|
||||
("The verification code is incorrect or has expired", "Il codice di verifica non è corretto o è scaduto"),
|
||||
("Edit Tag", "Modifica etichetta"),
|
||||
("Unremember Password", "Dimentica password"),
|
||||
("Forget Password", "Dimentica password"),
|
||||
("Favorites", "Preferiti"),
|
||||
("Add to Favorites", "Aggiungi ai preferiti"),
|
||||
("Remove from Favorites", "Rimuovi dai preferiti"),
|
||||
@ -301,9 +301,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Succeeded", "Completato"),
|
||||
("Someone turns on privacy mode, exit", "Qualcuno ha attivato la modalità privacy, uscita"),
|
||||
("Unsupported", "Non supportato"),
|
||||
("Peer denied", "Peer negato"),
|
||||
("Peer denied", "Acvesso negato al dispositivo remoto"),
|
||||
("Please install plugins", "Installa i plugin"),
|
||||
("Peer exit", "Uscita peer"),
|
||||
("Peer exit", "Uscita dal dispostivo remoto"),
|
||||
("Failed to turn off", "Impossibile spegnere"),
|
||||
("Turned off", "Spegni"),
|
||||
("In privacy mode", "In modalità privacy"),
|
||||
@ -392,7 +392,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland richiede Ubuntu 21.04 o versione successiva."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland richiede una versione superiore della distribuzione Linux.\nProva X11 desktop o cambia il sistema operativo."),
|
||||
("JumpLink", "Vai a"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato peer)."),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato dispositivo remoto)."),
|
||||
("Show RustDesk", "Visualizza RustDesk"),
|
||||
("This PC", "Questo PC"),
|
||||
("or", "O"),
|
||||
@ -477,7 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Empty Username", "Nome utente vuoto"),
|
||||
("Empty Password", "Password vuota"),
|
||||
("Me", "Io"),
|
||||
("identical_file_tip", "Questo file è identico a quello del peer."),
|
||||
("identical_file_tip", "Questo file è identico a quello nel dispositivo remoto."),
|
||||
("show_monitors_tip", "Visualizza schermi nella barra strumenti"),
|
||||
("View Mode", "Modalità visualizzazione"),
|
||||
("login_linux_tip", "Accedi all'account Linux remoto"),
|
||||
@ -498,8 +498,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Firma digitale"),
|
||||
("Copy Fingerprint", "Copia firma digitale"),
|
||||
("no fingerprints", "Nessuna firma digitale"),
|
||||
("Select a peer", "Seleziona un peer"),
|
||||
("Select peers", "Seleziona peer"),
|
||||
("Select a peer", "Seleziona dispositivo remoto"),
|
||||
("Select peers", "Seleziona dispositivi remoti"),
|
||||
("Plugins", "Plugin"),
|
||||
("Uninstall", "Disinstalla"),
|
||||
("Update", "Aggiorna"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("HSV Color", "Colore HSV"),
|
||||
("Installation Successful!", "Installazione completata"),
|
||||
("Installation failed!", "Installazione fallita"),
|
||||
("Reverse mouse wheel", "Rotella mouse inversa"),
|
||||
("{} sessions", "{} sessioni"),
|
||||
("scam_title", "Potresti essere stato TRUFFATO!"),
|
||||
("scam_text1", "Se sei al telefono con qualcuno che NON conosci NON DI TUA FIDUCIA che ti ha chiesto di usare RustDesk e di avviare il servizio, non procedere e riattacca subito."),
|
||||
("scam_text2", "Probabilmente è un truffatore che cerca di rubare i tuoi soldi o altre informazioni private."),
|
||||
("Don't show again", "Non visualizzare più"),
|
||||
("I Agree", "Accetto"),
|
||||
("Decline", "Non accetto"),
|
||||
("Timeout in minutes", "Timeout in minuti"),
|
||||
("auto_disconnect_option_tip", ""),
|
||||
("Connection failed due to inactivity", "Connessione non riuscita a causa di inattività"),
|
||||
("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!"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "資格情報が間違っています"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "タグを編集"),
|
||||
("Unremember Password", "パスワードの記憶を解除"),
|
||||
("Forget Password", "パスワードの記憶を解除"),
|
||||
("Favorites", "お気に入り"),
|
||||
("Add to Favorites", "お気に入りに追加"),
|
||||
("Remove from Favorites", "お気に入りから削除"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "틀린 인증 정보"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "태그 수정"),
|
||||
("Unremember Password", "패스워드 기억하지 않기"),
|
||||
("Forget Password", "패스워드 기억하지 않기"),
|
||||
("Favorites", "즐겨찾기"),
|
||||
("Add to Favorites", "즐겨찾기에 추가"),
|
||||
("Remove from Favorites", "즐겨찾기에서 삭제"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Бұрыс тіркелгі деректер"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Тақты Өндеу"),
|
||||
("Unremember Password", "Құпия сөзді Ұмыту"),
|
||||
("Forget Password", "Құпия сөзді Ұмыту"),
|
||||
("Favorites", "Таңдаулылар"),
|
||||
("Add to Favorites", "Таңдаулыларға Қосу"),
|
||||
("Remove from Favorites", "Таңдаулылардан алып тастау"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Klaidingi kredencialai"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Redaguoti žymą"),
|
||||
("Unremember Password", "Nebeprisiminti slaptažodžio"),
|
||||
("Forget Password", "Nebeprisiminti slaptažodžio"),
|
||||
("Favorites", "Parankiniai"),
|
||||
("Add to Favorites", "Įtraukti į parankinius"),
|
||||
("Remove from Favorites", "Pašalinti iš parankinių"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
563
src/lang/lv.rs
Normal file
563
src/lang/lv.rs
Normal file
@ -0,0 +1,563 @@
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Statuss"),
|
||||
("Your Desktop", "Jūsu darbvirsma"),
|
||||
("desk_tip", "Jūsu darbvirsmai var piekļūt ar šo ID un paroli."),
|
||||
("Password", "Parole"),
|
||||
("Ready", "Gatavs"),
|
||||
("Established", "Izveidots"),
|
||||
("connecting_status", "Notiek savienojuma izveide ar RustDesk tīklu..."),
|
||||
("Enable Service", "Iespējot servisu"),
|
||||
("Start Service", "Sākt servisu"),
|
||||
("Service is running", "Pakalpojums darbojas"),
|
||||
("Service is not running", "Pakalpojums nedarbojas"),
|
||||
("not_ready_status", "Nav gatavs. Lūdzu, pārbaudiet savienojumu"),
|
||||
("Control Remote Desktop", "Vadīt attālo darbvirsmu"),
|
||||
("Transfer File", "Pārsūtīt failu"),
|
||||
("Connect", "Savienoties"),
|
||||
("Recent Sessions", "Pēdējās sesijas"),
|
||||
("Address Book", "Adrešu grāmata"),
|
||||
("Confirmation", "Apstiprinājums"),
|
||||
("TCP Tunneling", "TCP tunelēšana"),
|
||||
("Remove", "Noņemt"),
|
||||
("Refresh random password", "Atsvaidzināt nejaušu paroli"),
|
||||
("Set your own password", "Iestatiet savu paroli"),
|
||||
("Enable Keyboard/Mouse", "Iespējot tastatūru/peli"),
|
||||
("Enable Clipboard", "Iespējot starpliktuvi"),
|
||||
("Enable File Transfer", "Iespējot failu pārsūtīšanu"),
|
||||
("Enable TCP Tunneling", "Iespējot TCP tunelēšanu"),
|
||||
("IP Whitelisting", "IP baltais saraksts"),
|
||||
("ID/Relay Server", "ID/releja serveris"),
|
||||
("Import Server Config", "Importēt servera konfigurāciju"),
|
||||
("Export Server Config", "Eksportēt servera konfigurāciju"),
|
||||
("Import server configuration successfully", "Servera konfigurācija veiksmīgi importēta"),
|
||||
("Export server configuration successfully", "Servera konfigurācija veiksmīgi eksportēta"),
|
||||
("Invalid server configuration", "Nederīga servera konfigurācija"),
|
||||
("Clipboard is empty", "Starpliktuve ir tukša"),
|
||||
("Stop service", "Apturēt servisu"),
|
||||
("Change ID", "Mainīt ID"),
|
||||
("Your new ID", "Jūsu jaunais ID"),
|
||||
("length %min% to %max%", "garums %min% līdz %max%"),
|
||||
("starts with a letter", "sākas ar burtu"),
|
||||
("allowed characters", "atļautās rakstzīmes"),
|
||||
("id_change_tip", "Atļautas tikai rakstzīmes a-z, A-Z, 0-9 un _ (pasvītrojums). Pirmajam burtam ir jābūt a-z, A-Z. Garums no 6 līdz 16."),
|
||||
("Website", "Tīmekļa vietne"),
|
||||
("About", "Par"),
|
||||
("Slogan_tip", "Radīts ar sirdi šajā haotiskajā pasaulē!"),
|
||||
("Privacy Statement", "Paziņojums par konfidencialitāti"),
|
||||
("Mute", "Izslēgt skaņu"),
|
||||
("Build Date", "Būvēšanas datums"),
|
||||
("Version", "Versija"),
|
||||
("Home", "Sākums"),
|
||||
("Audio Input", "Audio ievade"),
|
||||
("Enhancements", "Uzlabojumi"),
|
||||
("Hardware Codec", "Aparatūras kodeks"),
|
||||
("Adaptive bitrate", "Adaptīvais bitu pārraides ātrums"),
|
||||
("ID Server", "ID serveris"),
|
||||
("Relay Server", "Releja serveris"),
|
||||
("API Server", "API serveris"),
|
||||
("Key", "Atslēga"),
|
||||
("invalid_http", "jāsākas ar http:// vai https://"),
|
||||
("Invalid IP", "Nederīga IP"),
|
||||
("Invalid format", "Nederīgs formāts"),
|
||||
("server_not_support", "Serveris vēl neatbalsta"),
|
||||
("Not available", "Nav pieejams"),
|
||||
("Too frequent", "Pārāk bieži"),
|
||||
("Cancel", "Atcelt"),
|
||||
("Skip", "Izlaist"),
|
||||
("Close", "Aizvērt"),
|
||||
("Retry", "Mēģināt vēlreiz"),
|
||||
("OK", "Labi"),
|
||||
("Password Required", "Nepieciešama parole"),
|
||||
("Please enter your password", "Lūdzu, ievadiet paroli"),
|
||||
("Remember password", "Atcerēties paroli"),
|
||||
("Wrong Password", "Nepareiza parole"),
|
||||
("Do you want to enter again?", "Vai vēlaties ievadīt vēlreiz?"),
|
||||
("Connection Error", "Savienojuma kļūda"),
|
||||
("Error", "Kļūda"),
|
||||
("Reset by the peer", "Atiestatīts ar sesiju"),
|
||||
("Connecting...", "Notiek savienojuma izveide..."),
|
||||
("Connection in progress. Please wait.", "Notiek savienošanās. Lūdzu, uzgaidiet."),
|
||||
("Please try 1 minute later", "Lūdzu, mēģiniet 1 minūti vēlāk"),
|
||||
("Login Error", "Pieteikšanās kļūda"),
|
||||
("Successful", "Veiksmīgi"),
|
||||
("Connected, waiting for image...", "Savienots, gaida attēlu..."),
|
||||
("Name", "Nosaukums"),
|
||||
("Type", "Tips"),
|
||||
("Modified", "Modificēšanas dat."),
|
||||
("Size", "Lielums"),
|
||||
("Show Hidden Files", "Rādīt slēptos failus"),
|
||||
("Receive", "Saņemt"),
|
||||
("Send", "Sūtīt"),
|
||||
("Refresh File", "Atsvaidzināt failu"),
|
||||
("Local", "Vietējais"),
|
||||
("Remote", "Attālais"),
|
||||
("Remote Computer", "Attālais dators"),
|
||||
("Local Computer", "Vietējais dators"),
|
||||
("Confirm Delete", "Apstiprināt dzēšanu"),
|
||||
("Delete", "Dzēst"),
|
||||
("Properties", "Rekvizīti"),
|
||||
("Multi Select", "Vairākatlase"),
|
||||
("Select All", "Atlasīt visu"),
|
||||
("Unselect All", "Noņemt atlasi visiem"),
|
||||
("Empty Directory", "Tukša direktorija"),
|
||||
("Not an empty directory", "Nav tukša direktorija"),
|
||||
("Are you sure you want to delete this file?", "Vai tiešām vēlaties dzēst šo failu?"),
|
||||
("Are you sure you want to delete this empty directory?", "Vai tiešām vēlaties dzēst šo tukšo direktoriju?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Vai tiešām vēlaties dzēst šī direktorija failu?"),
|
||||
("Do this for all conflicts", "Pielietot visiem konfliktiem"),
|
||||
("This is irreversible!", "Tas ir neatgriezeniski!"),
|
||||
("Deleting", "Dzēšana"),
|
||||
("files", "faili"),
|
||||
("Waiting", "Gaida"),
|
||||
("Finished", "Pabeigts"),
|
||||
("Speed", "Ātrums"),
|
||||
("Custom Image Quality", "Pielāgota attēla kvalitāte"),
|
||||
("Privacy mode", "Privātuma režīms"),
|
||||
("Block user input", "Bloķēt lietotāja ievadi"),
|
||||
("Unblock user input", "Atbloķēt lietotāja ievadi"),
|
||||
("Adjust Window", "Pielāgot logu"),
|
||||
("Original", "Oriģināls"),
|
||||
("Shrink", "Saraut"),
|
||||
("Stretch", "Izstiept"),
|
||||
("Scrollbar", "Ritjosla"),
|
||||
("ScrollAuto", "Ritināt automātiski"),
|
||||
("Good image quality", "Laba attēla kvalitāte"),
|
||||
("Balanced", "Sabalansēts"),
|
||||
("Optimize reaction time", "Optimizēt reakcijas laiku"),
|
||||
("Custom", "Pielāgots"),
|
||||
("Show remote cursor", "Rādīt attālo kursoru"),
|
||||
("Show quality monitor", "Rādīt kvalitātes monitoru"),
|
||||
("Disable clipboard", "Atspējot starpliktuvi"),
|
||||
("Lock after session end", "Bloķēt pēc sesijas beigām"),
|
||||
("Insert", "Ievietot"),
|
||||
("Insert Lock", "Ievietot Bloķēt"),
|
||||
("Refresh", "Atsvaidzināt"),
|
||||
("ID does not exist", "ID neeksistē"),
|
||||
("Failed to connect to rendezvous server", "Neizdevās izveidot savienojumu ar starpposma serveri"),
|
||||
("Please try later", "Lūdzu, mēģiniet vēlāk"),
|
||||
("Remote desktop is offline", "Attālā darbvirsma ir bezsaistē"),
|
||||
("Key mismatch", "Atslēgu neatbilstība"),
|
||||
("Timeout", "Noildze"),
|
||||
("Failed to connect to relay server", "Neizdevās izveidot savienojumu ar releja serveri"),
|
||||
("Failed to connect via rendezvous server", "Neizdevās izveidot savienojumu, izmantojot starpposma serveri"),
|
||||
("Failed to connect via relay server", "Neizdevās izveidot savienojumu, izmantojot releja serveri"),
|
||||
("Failed to make direct connection to remote desktop", "Neizdevās izveidot tiešu savienojumu ar attālo darbvirsmu"),
|
||||
("Set Password", "Uzstādīt paroli"),
|
||||
("OS Password", "OS parole"),
|
||||
("install_tip", "UAC dēļ RustDesk dažos gadījumos nevar pareizi darboties kā attālā puse. Lai izvairītos no UAC, lūdzu, noklikšķiniet uz tālāk esošās pogas, lai instalētu RustDesk sistēmā."),
|
||||
("Click to upgrade", "Jaunināt"),
|
||||
("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."),
|
||||
("Installing ...", "Notiek instalēšana..."),
|
||||
("Install", "Uzstādīt"),
|
||||
("Installation", "Instalēšana"),
|
||||
("Installation Path", "Instalācijas ceļš"),
|
||||
("Create start menu shortcuts", "Izveidot sākuma izvēlnes īsceļus"),
|
||||
("Create desktop icon", "Izveidot darbvirsmas ikonu"),
|
||||
("agreement_tip", "Sākot instalēšanu, jūs piekrītat licences līgumam."),
|
||||
("Accept and Install", "Pieņemt un instalēt"),
|
||||
("End-user license agreement", "Gala lietotāja licences līgums"),
|
||||
("Generating ...", "Ģenerēšana..."),
|
||||
("Your installation is lower version.", "Jūsu instalācijai ir zemāka versija."),
|
||||
("not_close_tcp_tip", "Neaizveriet šo logu, kamēr izmantojat tuneli"),
|
||||
("Listening ...", "Klausās..."),
|
||||
("Remote Host", "Attālais resursdators"),
|
||||
("Remote Port", "Attālais ports"),
|
||||
("Action", "Darbība"),
|
||||
("Add", "Pievienot"),
|
||||
("Local Port", "Vietējais ports"),
|
||||
("Local Address", "Vietējā adrese"),
|
||||
("Change Local Port", "Mainīt vietējo portu"),
|
||||
("setup_server_tip", "Lai iegūtu ātrāku savienojumu, lūdzu, iestatiet savu serveri"),
|
||||
("Too short, at least 6 characters.", "Pārāk īss, vismaz 6 rakstzīmes."),
|
||||
("The confirmation is not identical.", "Apstiprinājums nav identisks."),
|
||||
("Permissions", "Atļaujas"),
|
||||
("Accept", "Pieņemt"),
|
||||
("Dismiss", "Noraidīt"),
|
||||
("Disconnect", "Atvienot"),
|
||||
("Allow using keyboard and mouse", "Atļaut izmantot tastatūru un peli"),
|
||||
("Allow using clipboard", "Atļaut izmantot starpliktuvi"),
|
||||
("Allow hearing sound", "Atļaut klausīties skaņu"),
|
||||
("Allow file copy and paste", "Atļaut failu kopēšanu un ielīmēšanu"),
|
||||
("Connected", "Savienots"),
|
||||
("Direct and encrypted connection", "Tiešs un šifrēts savienojums"),
|
||||
("Relayed and encrypted connection", "Pārslēgts un šifrēts savienojums"),
|
||||
("Direct and unencrypted connection", "Tiešs un nešifrēts savienojums"),
|
||||
("Relayed and unencrypted connection", "Pārslēgts un nešifrēts savienojums"),
|
||||
("Enter Remote ID", "Ievadiet attālo ID"),
|
||||
("Enter your password", "Ievadiet savu paroli"),
|
||||
("Logging in...", "Ielogoties..."),
|
||||
("Enable RDP session sharing", "Iespējot RDP sesiju koplietošanu"),
|
||||
("Auto Login", "Automātiskā pieteikšanās (derīga tikai tad, ja esat iestatījis \"Bloķēt pēc sesijas beigām\")"),
|
||||
("Enable Direct IP Access", "Iespējot tiešo IP piekļuvi"),
|
||||
("Rename", "Pārdēvēt"),
|
||||
("Space", "Vieta"),
|
||||
("Create Desktop Shortcut", "Izveidot saīsni uz darbvirsmas"),
|
||||
("Change Path", "Mainīt ceļu"),
|
||||
("Create Folder", "Izveidot mapi"),
|
||||
("Please enter the folder name", "Lūdzu, ievadiet mapes nosaukumu"),
|
||||
("Fix it", "Salabot to"),
|
||||
("Warning", "Brīdinājums"),
|
||||
("Login screen using Wayland is not supported", "Pieteikšanās ekrāns, izmantojot Wayland netiek atbalstīts"),
|
||||
("Reboot required", "Nepieciešama restartēšana"),
|
||||
("Unsupported display server", "Neatbalstīts displeja serveris"),
|
||||
("x11 expected", "x11 paredzams"),
|
||||
("Port", "Ports"),
|
||||
("Settings", "Iestatījumi"),
|
||||
("Username", "Lietotājvārds"),
|
||||
("Invalid port", "Nederīgs ports"),
|
||||
("Closed manually by the peer", "Sesija aizvērta manuāli"),
|
||||
("Enable remote configuration modification", "Iespējot attālās konfigurācijas modifikāciju"),
|
||||
("Run without install", "Palaist bez instalēšanas"),
|
||||
("Connect via relay", "Savienot, izmantojot releju"),
|
||||
("Always connect via relay", "Vienmēr izveidot savienojumu, izmantojot releju"),
|
||||
("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"),
|
||||
("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."),
|
||||
("Logout", "Izlogoties"),
|
||||
("Tags", "Tagi"),
|
||||
("Search ID", "Meklēt ID"),
|
||||
("whitelist_sep", "Atdalīts ar komatu, semikolu, atstarpēm vai jaunu rindiņu"),
|
||||
("Add ID", "Pievienot ID"),
|
||||
("Add Tag", "Pievienot tagu"),
|
||||
("Unselect all tags", "Noņemt visu tagu atlasi"),
|
||||
("Network error", "Tīkla kļūda"),
|
||||
("Username missed", "Lietotājvārds ir izlaists"),
|
||||
("Password missed", "Parole nav ievadīta"),
|
||||
("Wrong credentials", "Nepareizs lietotājvārds vai parole"),
|
||||
("The verification code is incorrect or has expired", "Verifikācijas kods ir nepareizs vai tam ir beidzies derīguma termiņš"),
|
||||
("Edit Tag", "Rediģēt tagu"),
|
||||
("Forget Password", "Neatcerēties paroli"),
|
||||
("Favorites", "Izlase"),
|
||||
("Add to Favorites", "Pievienot pie izlases"),
|
||||
("Remove from Favorites", "Noņemt no izlases"),
|
||||
("Empty", "Tukšs"),
|
||||
("Invalid folder name", "Nederīgs mapes nosaukums"),
|
||||
("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."),
|
||||
("Remote ID", "Attālais ID"),
|
||||
("Paste", "Ielīmēt"),
|
||||
("Paste here?", "Ielīmēt šeit?"),
|
||||
("Are you sure to close the connection?", "Vai tiešām vēlaties aizvērt savienojumu?"),
|
||||
("Download new version", "Lejupielādēt jauno versiju"),
|
||||
("Touch mode", "Skārienrežīms"),
|
||||
("Mouse mode", "Peles režīms"),
|
||||
("One-Finger Tap", "Pieskāriens ar vienu pirkstu"),
|
||||
("Left Mouse", "Kreisā pele"),
|
||||
("One-Long Tap", "Viens garš pieskāriens"),
|
||||
("Two-Finger Tap", "Pieskāriens ar diviem pirkstiem"),
|
||||
("Right Mouse", "Labā pele"),
|
||||
("One-Finger Move", "Viena pirksta pārvietošana"),
|
||||
("Double Tap & Move", "Dubultskāriens & pārvietošana"),
|
||||
("Mouse Drag", "Peles vilkšana"),
|
||||
("Three-Finger vertically", "Trīs pirksti vertikāli"),
|
||||
("Mouse Wheel", "Peles ritenis"),
|
||||
("Two-Finger Move", "Divu pirkstu pārvietošana"),
|
||||
("Canvas Move", "Audekla pārvietošana"),
|
||||
("Pinch to Zoom", "Saspiediet, lai tuvinātu"),
|
||||
("Canvas Zoom", "Audekla tālummaiņa"),
|
||||
("Reset canvas", "Atiestatīt audeklu"),
|
||||
("No permission of file transfer", "Nav atļaujas failu pārsūtīšanai"),
|
||||
("Note", "Piezīme"),
|
||||
("Connection", "Savienojums"),
|
||||
("Share Screen", "Koplietot ekrānu"),
|
||||
("Chat", "Tērzēšana"),
|
||||
("Total", "Kopā"),
|
||||
("items", "vienumi"),
|
||||
("Selected", "Atlasīts"),
|
||||
("Screen Capture", "Ekrāna tveršana"),
|
||||
("Input Control", "Ievades vadība"),
|
||||
("Audio Capture", "Audio tveršana"),
|
||||
("File Connection", "Failu savienojums"),
|
||||
("Screen Connection", "Ekrāna savienojums"),
|
||||
("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_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_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_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"),
|
||||
("This file exists, skip or overwrite this file?", "Šis fails pastāv, izlaist vai pārrakstīt šo failu?"),
|
||||
("Quit", "Iziet"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
|
||||
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
|
||||
("Help", "Palīdzība"),
|
||||
("Failed", "Neizdevās"),
|
||||
("Succeeded", "Izdevās"),
|
||||
("Someone turns on privacy mode, exit", "Kāds ieslēdza privātuma režīmu, iziet"),
|
||||
("Unsupported", "Neatbalstīts"),
|
||||
("Peer denied", "Sesija noraidīta"),
|
||||
("Please install plugins", "Lūdzu, instalējiet spraudņus"),
|
||||
("Peer exit", "Vienādranga izeja"),
|
||||
("Failed to turn off", "Neizdevās izslēgt"),
|
||||
("Turned off", "Izslēgts"),
|
||||
("In privacy mode", "Privātuma režīmā"),
|
||||
("Out privacy mode", "Izslēgts privātuma režīms"),
|
||||
("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"),
|
||||
("Connection not allowed", "Savienojums nav atļauts"),
|
||||
("Legacy mode", "Novecojis režīms"),
|
||||
("Map mode", "Kartēšanas režīms"),
|
||||
("Translate mode", "Tulkošanas režīms"),
|
||||
("Use permanent password", "Izmantot pastāvīgo paroli"),
|
||||
("Use both passwords", "Izmantot abas paroles"),
|
||||
("Set permanent password", "Iestatīt pastāvīgo paroli"),
|
||||
("Enable Remote Restart", "Iespējot attālo restartēšanu"),
|
||||
("Allow remote restart", "Atļaut attālo restartēšanu"),
|
||||
("Restart Remote Device", "Restartēt attālo ierīci"),
|
||||
("Are you sure you want to restart", "Vai tiešām vēlaties restartēt"),
|
||||
("Restarting Remote Device", "Attālās ierīces restartēšana"),
|
||||
("remote_restarting_tip", "Attālā ierīce tiek restartēta, lūdzu, aizveriet šo ziņojuma lodziņu un pēc kāda laika izveidojiet savienojumu ar pastāvīgo paroli"),
|
||||
("Copied", "Kopēts"),
|
||||
("Exit Fullscreen", "Iziet no pilnekrāna"),
|
||||
("Fullscreen", "Pilnekrāna režīms"),
|
||||
("Mobile Actions", "Mobilās darbības"),
|
||||
("Select Monitor", "Atlasīt monitoru"),
|
||||
("Control Actions", "Kontroles darbības"),
|
||||
("Display Settings", "Displeja iestatījumi"),
|
||||
("Ratio", "Attiecība"),
|
||||
("Image Quality", "Attēla kvalitāte"),
|
||||
("Scroll Style", "Ritināšanas stils"),
|
||||
("Show Toolbar", "Rādīt rīkjoslu"),
|
||||
("Hide Toolbar", "Slēpt rīkjoslu"),
|
||||
("Direct Connection", "Tiešais savienojums"),
|
||||
("Relay Connection", "Releja savienojums"),
|
||||
("Secure Connection", "Drošs savienojums"),
|
||||
("Insecure Connection", "Nedrošs savienojums"),
|
||||
("Scale original", "Mērogs oriģināls"),
|
||||
("Scale adaptive", "Mērogs adaptīvs"),
|
||||
("General", "Vispārīgi"),
|
||||
("Security", "Drošība"),
|
||||
("Theme", "Motīvs"),
|
||||
("Dark Theme", "Tumšais motīvs"),
|
||||
("Light Theme", "Gaišais motīvs"),
|
||||
("Dark", "Tumšs"),
|
||||
("Light", "Gaišs"),
|
||||
("Follow System", "Sekot sistēmai"),
|
||||
("Enable hardware codec", "Iespējot aparatūras kodeku"),
|
||||
("Unlock Security Settings", "Atbloķēt drošības iestatījumus"),
|
||||
("Enable Audio", "Iespējot audio"),
|
||||
("Unlock Network Settings", "Atbloķēt tīkla iestatījumus"),
|
||||
("Server", "Serveris"),
|
||||
("Direct IP Access", "Tiešā IP piekļuve"),
|
||||
("Proxy", "Starpniekserveris"),
|
||||
("Apply", "Lietot"),
|
||||
("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"),
|
||||
("Network", "Tīkls"),
|
||||
("Enable RDP", "Iespējot RDP"),
|
||||
("Pin Toolbar", "Piespraust rīkjoslu"),
|
||||
("Unpin Toolbar", "Atspraust rīkjoslu"),
|
||||
("Recording", "Ierakstīšana"),
|
||||
("Directory", "Direktorija"),
|
||||
("Automatically record incoming sessions", "Automātiski ierakstīt ienākošās sesijas"),
|
||||
("Change", "Mainīt"),
|
||||
("Start session recording", "Sākt sesijas ierakstīšanu"),
|
||||
("Stop session recording", "Apturēt sesijas ierakstīšanu"),
|
||||
("Enable Recording Session", "Iespējot sesijas ierakstīšanu"),
|
||||
("Allow recording session", "Atļaut sesijas ierakstīšanu"),
|
||||
("Enable LAN Discovery", "Iespējot LAN atklāšanu"),
|
||||
("Deny LAN Discovery", "Liegt LAN atklāšanu"),
|
||||
("Write a message", "Rakstīt ziņojumu"),
|
||||
("Prompt", "Uzvedne"),
|
||||
("Please wait for confirmation of UAC...", "Lūdzu, uzgaidiet UAC apstiprinājumu..."),
|
||||
("elevated_foreground_window_tip", "Pašreizējā attālās darbvirsmas loga darbībai ir nepieciešamas lielākas tiesības, tāpēc tas īslaicīgi nevar izmantot peli un tastatūru. Varat pieprasīt attālajam lietotājam samazināt pašreizējo logu vai noklikšķināt uz pacēluma pogas savienojuma pārvaldības logā. Lai izvairītos no šīs problēmas, ieteicams instalēt programmatūru attālajā ierīcē."),
|
||||
("Disconnected", "Atvienots"),
|
||||
("Other", "Cits"),
|
||||
("Confirm before closing multiple tabs", "Apstiprināt pirms vairāku cilņu aizvēršanas"),
|
||||
("Keyboard Settings", "Tastatūras iestatījumi"),
|
||||
("Full Access", "Pilna piekļuve"),
|
||||
("Screen Share", "Ekrāna kopīgošana"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland nepieciešama Ubuntu 21.04 vai jaunāka versija."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland nepieciešama augstāka Linux distro versija. Lūdzu, izmēģiniet X11 desktop vai mainiet savu OS."),
|
||||
("JumpLink", "Skatīt"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Lūdzu, atlasiet kopīgojamo ekrānu (darbojieties sesijas pusē)."),
|
||||
("Show RustDesk", "Rādīt RustDesk"),
|
||||
("This PC", "Šis dators"),
|
||||
("or", "vai"),
|
||||
("Continue with", "Turpināt ar"),
|
||||
("Elevate", "Pacelt"),
|
||||
("Zoom cursor", "Tālummaiņas kursors"),
|
||||
("Accept sessions via password", "Pieņemt sesijas, izmantojot paroli"),
|
||||
("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"),
|
||||
("Use one-time password", "Izmantot vienreizējo paroli"),
|
||||
("One-time password length", "Vienreizējas 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"),
|
||||
("wayland_experiment_tip", "Wayland atbalsts ir eksperimentālā stadijā. Ja nepieciešama neuzraudzīta piekļuve, lūdzu, izmantojiet X11."),
|
||||
("Right click to select tabs", "Ar peles labo pogu noklikšķiniet, lai atlasītu cilnes"),
|
||||
("Skipped", "Izlaists"),
|
||||
("Add to Address Book", "Pievienot adrešu grāmatai"),
|
||||
("Group", "Grupa"),
|
||||
("Search", "Meklēt"),
|
||||
("Closed manually by web console", "Manuāli aizvērta tīmekļa konsole"),
|
||||
("Local keyboard type", "Vietējais tastatūras veids"),
|
||||
("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."),
|
||||
("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"),
|
||||
("Ask the remote user for authentication", "Lūdziet attālajam lietotājam autentifikāciju"),
|
||||
("Choose this if the remote account is administrator", "Izvēlieties šo, ja attālais konts ir administrators"),
|
||||
("Transmit the username and password of administrator", "Pārsūtīt administratora lietotājvārdu un paroli"),
|
||||
("still_click_uac_tip", "Joprojām attālajam lietotājam ir jānoklikšķina uz Labi UAC logā, kurā darbojas RustDesk."),
|
||||
("Request Elevation", "Pieprasīt paaugstinājumu"),
|
||||
("wait_accept_uac_tip", "Lūdzu, uzgaidiet, līdz attālais lietotājs pieņems UAC dialoglodziņu."),
|
||||
("Elevate successfully", "Paaugstināšana veiksmīga"),
|
||||
("uppercase", "lielie burti"),
|
||||
("lowercase", "mazie burti"),
|
||||
("digit", "cipars"),
|
||||
("special character", "speciāla rakstzīme"),
|
||||
("length>=8", "garums>=8"),
|
||||
("Weak", "Vāji"),
|
||||
("Medium", "Vidējs"),
|
||||
("Strong", "Spēcīgs"),
|
||||
("Switch Sides", "Pārslēgt puses"),
|
||||
("Please confirm if you want to share your desktop?", "Lūdzu, apstipriniet, vai vēlaties koplietot savu darbvirsmu?"),
|
||||
("Display", "Displejs"),
|
||||
("Default View Style", "Noklusējuma skata stils"),
|
||||
("Default Scroll Style", "Noklusējuma ritināšanas stils"),
|
||||
("Default Image Quality", "Noklusējuma attēla kvalitāte"),
|
||||
("Default Codec", "Noklusējuma kodeks"),
|
||||
("Bitrate", "Bitu pārraides ātrums"),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "Auto"),
|
||||
("Other Default Options", "Citas noklusējuma opcijas"),
|
||||
("Voice call", "Balss zvans"),
|
||||
("Text chat", "Teksta tērzēšana"),
|
||||
("Stop voice call", "Apturēt balss zvanu"),
|
||||
("relay_hint_tip", "Iespējams, nav iespējams izveidot savienojumu tieši; varat mēģināt izveidot savienojumu, izmantojot releju. Turklāt, ja vēlaties izmantot releju pirmajā mēģinājumā, ID varat pievienot sufiksu \"/r\". vai pēdējo sesiju kartē atlasiet opciju \"Vienmēr izveidot savienojumu, izmantojot releju\", ja tāda pastāv."),
|
||||
("Reconnect", "Atkārtoti savienot"),
|
||||
("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"),
|
||||
("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"),
|
||||
("Sort by", "Kārtot pēc"),
|
||||
("New Connection", "Jauns savienojums"),
|
||||
("Restore", "Atjaun. uz leju"),
|
||||
("Minimize", "Minimizēt"),
|
||||
("Maximize", "Maksimizēt"),
|
||||
("Your Device", "Jūsu ierīce"),
|
||||
("empty_recent_tip", "Hmm, pēdējo sesiju nav!\nLaiks plānot jaunu."),
|
||||
("empty_favorite_tip", "Vēl nav iecienītākās sesijas?\nAtradīsim kādu, ar ko sazināties, un pievienosim to jūsu izlasei!"),
|
||||
("empty_lan_tip", "Ak nē! Šķiet, ka mēs vēl neesam atklājuši nevienu sesiju."),
|
||||
("empty_address_book_tip", "Ak vai, izskatās, ka jūsu adrešu grāmatā šobrīd nav neviena sesija."),
|
||||
("eg: admin", "piemēram: admin"),
|
||||
("Empty Username", "Tukšs lietotājvārds"),
|
||||
("Empty Password", "Tukša parole"),
|
||||
("Me", "Es"),
|
||||
("identical_file_tip", "Šis fails ir identisks sesijas failam."),
|
||||
("show_monitors_tip", "Rādīt monitorus rīkjoslā"),
|
||||
("enter_rustdesk_passwd_tip", "Ievadiet RustDesk paroli"),
|
||||
("View Mode", "Skatīšanas režīms"),
|
||||
("login_linux_tip", "Jums ir jāpiesakās attālajā Linux kontā, lai iespējotu X darbvirsmas sesiju"),
|
||||
("verify_rustdesk_password_tip", "Pārbaudīt RustDesk paroli"),
|
||||
("remember_account_tip", "Atcerēties šo kontu"),
|
||||
("os_account_desk_tip", "Šis konts tiek izmantots, lai pieteiktos attālajā operētājsistēmā un iespējotu darbvirsmas sesiju fonā"),
|
||||
("OS Account", "OS konts"),
|
||||
("another_user_login_title_tip", "Cits lietotājs jau ir pieteicies"),
|
||||
("another_user_login_text_tip", "Atvienot"),
|
||||
("xorg_not_found_title_tip", "Xorg nav atrasts"),
|
||||
("xorg_not_found_text_tip", "Lūdzu, instalējiet Xorg"),
|
||||
("no_desktop_title_tip", "Nav pieejama darbvirsma"),
|
||||
("no_desktop_text_tip", "Lūdzu, instalējiet GNOME darbvirsmu"),
|
||||
("No need to elevate", "Nav nepieciešams paaugstināt"),
|
||||
("System Sound", "Sistēmas skaņa"),
|
||||
("Default", "Noklusējums"),
|
||||
("New RDP", "Jauns RDP"),
|
||||
("Fingerprint", "Pirkstu nospiedums"),
|
||||
("Copy Fingerprint", "Kopēt pirkstu nospiedumu"),
|
||||
("no fingerprints", "nav pirkstu nospiedumu"),
|
||||
("Select a peer", "Atlasīt līdzīgu"),
|
||||
("Select peers", "Atlasīt līdzīgus"),
|
||||
("Plugins", "Spraudņi"),
|
||||
("Uninstall", "Atinstalēt"),
|
||||
("Update", "Atjaunināt"),
|
||||
("Enable", "Iespējot"),
|
||||
("Disable", "Atspējot"),
|
||||
("Options", "Opcijas"),
|
||||
("resolution_original_tip", "Sākotnējā izšķirtspēja"),
|
||||
("resolution_fit_local_tip", "Atbilst vietējai izšķirtspējai"),
|
||||
("resolution_custom_tip", "Pielāgota izšķirtspēja"),
|
||||
("Collapse toolbar", "Sakļaut rīkjoslu"),
|
||||
("Accept and Elevate", "Pieņemt un paaugstināt"),
|
||||
("accept_and_elevate_btn_tooltip", "Pieņemt savienojumu un paaugstināt UAC atļaujas."),
|
||||
("clipboard_wait_response_timeout_tip", "Noildze gaidot atbildi uz kopiju."),
|
||||
("Incoming connection", "Ienākošais savienojums"),
|
||||
("Outgoing connection", "Izejošais savienojums"),
|
||||
("Exit", "Iziet"),
|
||||
("Open", "Atvērt"),
|
||||
("logout_tip", "Vai tiešām vēlaties iziet?"),
|
||||
("Service", "Serviss"),
|
||||
("Start", "Sākt"),
|
||||
("Stop", "Apturēt"),
|
||||
("exceed_max_devices", "Esat sasniedzis maksimālo pārvaldāmo ierīču skaitu."),
|
||||
("Sync with recent sessions", "Sinhronizācija ar pēdējām sesijām"),
|
||||
("Sort tags", "Kārtot tagus"),
|
||||
("Open connection in new tab", "Atvērt savienojumu jaunā cilnē"),
|
||||
("Move tab to new window", "Pārvietot cilni uz jaunu logu"),
|
||||
("Can not be empty", "Nevar būt tukšs"),
|
||||
("Already exists", "Jau eksistē"),
|
||||
("Change Password", "Mainīt paroli"),
|
||||
("Refresh Password", "Atsvaidzināt paroli"),
|
||||
("ID", "ID"),
|
||||
("Grid View", "Režģa skats"),
|
||||
("List View", "Saraksta skats"),
|
||||
("Select", "Atlasīt"),
|
||||
("Toggle Tags", "Pārslēgt tagus"),
|
||||
("pull_ab_failed_tip", "Neizdevās atsvaidzināt adrešu grāmatu"),
|
||||
("push_ab_failed_tip", "Neizdevās sinhronizēt adrešu grāmatu ar serveri"),
|
||||
("synced_peer_readded_tip", "Ierīces, kas bija pēdējās sesijās, tiks sinhronizētas atpakaļ ar adrešu grāmatu."),
|
||||
("Change Color", "Mainīt krāsu"),
|
||||
("Primary Color", "Primārā krāsa"),
|
||||
("HSV Color", "HSV krāsa"),
|
||||
("Installation Successful!", "Instalēšana veiksmīga!"),
|
||||
("Installation failed!", "Instalēšana neizdevās!"),
|
||||
("Reverse mouse wheel", "Reversīvs peles ritenis"),
|
||||
("{} sessions", "{} sesijas"),
|
||||
("scam_title", "Tevi var APKRĀPT!"),
|
||||
("scam_text1", "Ja sarunājaties ar kādu, kuru nepazīstat un kurš ir lūdzis izmantot RustDesk, lai sāktu pakalpojumu, neturpiniet un nekavējoties nolieciet klausuli."),
|
||||
("scam_text2", "Viņi, visticamāk, ir krāpnieki, kas mēģina nozagt tavu naudu vai citu privātu informāciju."),
|
||||
("Don't show again", "Vairs nerādīt"),
|
||||
("I Agree", "Es piekrītu"),
|
||||
("Decline", "Noraidīt"),
|
||||
("Timeout in minutes", "Noildze minūtēs"),
|
||||
("auto_disconnect_option_tip", "Automātiski aizvērt ienākošās sesijas lietotāja neaktivitātes gadījumā"),
|
||||
("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!"),
|
||||
].iter().cloned().collect();
|
||||
}
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Verkeerde inloggegevens"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Label Bewerken"),
|
||||
("Unremember Password", "Wachtwoord vergeten"),
|
||||
("Forget Password", "Wachtwoord vergeten"),
|
||||
("Favorites", "Favorieten"),
|
||||
("Add to Favorites", "Toevoegen aan Favorieten"),
|
||||
("Remove from Favorites", "Verwijderen uit Favorieten"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("HSV Color", "HSV Kleur"),
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Błędne dane uwierzytelniające"),
|
||||
("The verification code is incorrect or has expired", "Kod weryfikacyjny jest niepoprawny lub wygasł"),
|
||||
("Edit Tag", "Edytuj tag"),
|
||||
("Unremember Password", "Zapomnij hasło"),
|
||||
("Forget Password", "Zapomnij hasło"),
|
||||
("Favorites", "Ulubione"),
|
||||
("Add to Favorites", "Dodaj do ulubionych"),
|
||||
("Remove from Favorites", "Usuń z ulubionych"),
|
||||
@ -528,20 +528,33 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Move tab to new window", "Przenieś zakładkę do nowego okna"),
|
||||
("Can not be empty", "Nie może być puste"),
|
||||
("Already exists", "Już istnieje"),
|
||||
("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!", ""),
|
||||
("Change Password", "Zmień hasło"),
|
||||
("Refresh Password", "Odśwież hasło"),
|
||||
("ID", "ID"),
|
||||
("Grid View", "Widok siatki"),
|
||||
("List View", "Widok listy"),
|
||||
("Select", "Wybierz"),
|
||||
("Toggle Tags", "Przełącz tagi"),
|
||||
("pull_ab_failed_tip", "Aktualizacja książki adresowej nie powiodła się"),
|
||||
("push_ab_failed_tip", "Nie udało się zsynchronizować książki adresowej z serwerem"),
|
||||
("synced_peer_readded_tip", "Urządzenia, które były obecne w ostatnich sesjach, zostaną ponownie dodane do książki adresowej"),
|
||||
("Change Color", "Zmień kolor"),
|
||||
("Primary Color", "Kolor podstawowy"),
|
||||
("HSV Color", "Kolor HSV"),
|
||||
("Installation Successful!", "Instalacja zakończona!"),
|
||||
("Installation failed!", "Instalacja nie powiodła się"),
|
||||
("Reverse mouse wheel", "Odwróć rolkę myszki"),
|
||||
("{} sessions", "{} sesji"),
|
||||
("scam_title", "Prawdopodobnie zostałeś OSZUKANY!"),
|
||||
("scam_text1", "Jeżeli rozmawiasz przez telefon z osobą której NIE ZNASZ i NIE UFASZ, która prosi Cię o uruchomienie programu RustDesk i uruchomienia usługi - nie rób tego i natychmiast się rozłącz."),
|
||||
("scam_text2", "Wygląda to na oszusta, który próbuje ukraść Twoje pieniądze lub inne prywatne informacje."),
|
||||
("Don't show again", "Nie pokazuj więcej"),
|
||||
("I Agree", "Zgadzam się"),
|
||||
("Decline", "Odmawiam"),
|
||||
("Timeout in minutes", "Czas bezczynności w minutach"),
|
||||
("auto_disconnect_option_tip", "Automatycznie rozłącz sesje przychodzące przy braku aktywności użytkownika"),
|
||||
("Connection failed due to inactivity", "Automatycznie rozłącz przy bezczynności"),
|
||||
("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!"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Nome de utilizador ou palavra-chave incorrectos"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Editar Tag"),
|
||||
("Unremember Password", "Esquecer Palavra-chave"),
|
||||
("Forget Password", "Esquecer Palavra-chave"),
|
||||
("Favorites", "Favoritos"),
|
||||
("Add to Favorites", "Adicionar aos Favoritos"),
|
||||
("Remove from Favorites", "Remover dos Favoritos"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Nome de usuário ou senha incorretos"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Editar Tag"),
|
||||
("Unremember Password", "Esquecer Senha"),
|
||||
("Forget Password", "Esquecer Senha"),
|
||||
("Favorites", "Favoritos"),
|
||||
("Add to Favorites", "Adicionar aos Favoritos"),
|
||||
("Remove from Favorites", "Remover dos Favoritos"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Nume sau parolă greșită"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Modifică etichetă"),
|
||||
("Unremember Password", "Uită parola"),
|
||||
("Forget Password", "Uită parola"),
|
||||
("Favorites", "Favorite"),
|
||||
("Add to Favorites", "Adaugă la Favorite"),
|
||||
("Remove from Favorites", "Șterge din Favorite"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Неправильные учётные данные"),
|
||||
("The verification code is incorrect or has expired", "Проверочный код неправильный или устарел"),
|
||||
("Edit Tag", "Изменить метку"),
|
||||
("Unremember Password", "Не сохранять пароль"),
|
||||
("Forget Password", "Не сохранять пароль"),
|
||||
("Favorites", "Избранное"),
|
||||
("Add to Favorites", "Добавить в избранное"),
|
||||
("Remove from Favorites", "Удалить из избранного"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Nesprávne prihlasovacie údaje"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Upraviť štítok"),
|
||||
("Unremember Password", "Zabudnúť heslo"),
|
||||
("Forget Password", "Zabudnúť heslo"),
|
||||
("Favorites", "Obľúbené"),
|
||||
("Add to Favorites", "Pridať medzi obľúbené"),
|
||||
("Remove from Favorites", "Odstrániť z obľúbených"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Napačne poverilnice"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Uredi oznako"),
|
||||
("Unremember Password", "Pozabi geslo"),
|
||||
("Forget Password", "Pozabi geslo"),
|
||||
("Favorites", "Priljubljene"),
|
||||
("Add to Favorites", "Dodaj med priljubljene"),
|
||||
("Remove from Favorites", "Odstrani iz priljubljenih"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Kredinciale të gabuara"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Edito tagun"),
|
||||
("Unremember Password", "Fjalëkalim jo i kujtueshëm"),
|
||||
("Forget Password", "Fjalëkalim jo i kujtueshëm"),
|
||||
("Favorites", "Te preferuarat"),
|
||||
("Add to Favorites", "Shto te të preferuarat"),
|
||||
("Remove from Favorites", "Hiq nga të preferuarat"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Pogrešno korisničko ime ili lozinka"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Izmeni oznaku"),
|
||||
("Unremember Password", "Zaboravi lozinku"),
|
||||
("Forget Password", "Zaboravi lozinku"),
|
||||
("Favorites", "Favoriti"),
|
||||
("Add to Favorites", "Dodaj u favorite"),
|
||||
("Remove from Favorites", "Izbaci iz favorita"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Fel användarnamn eller lösenord"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Ändra Tagg"),
|
||||
("Unremember Password", "Glöm lösenord"),
|
||||
("Forget Password", "Glöm lösenord"),
|
||||
("Favorites", "Favoriter"),
|
||||
("Add to Favorites", "Lägg till favorit"),
|
||||
("Remove from Favorites", "Ta bort från favoriter"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", ""),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", ""),
|
||||
("Unremember Password", ""),
|
||||
("Forget Password", ""),
|
||||
("Favorites", ""),
|
||||
("Add to Favorites", ""),
|
||||
("Remove from Favorites", ""),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "ข้อมูลสำหรับเข้าสู่ระบบไม่ถูกต้อง"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "แก้ไขแท็ก"),
|
||||
("Unremember Password", "ยกเลิกการจดจำรหัสผ่าน"),
|
||||
("Forget Password", "ยกเลิกการจดจำรหัสผ่าน"),
|
||||
("Favorites", "รายการโปรด"),
|
||||
("Add to Favorites", "เพิ่มไปยังรายการโปรด"),
|
||||
("Remove from Favorites", "ลบออกจากรายการโปรด"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Yanlış kimlik bilgileri"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Etiketi düzenle"),
|
||||
("Unremember Password", "Şifreyi Unut"),
|
||||
("Forget Password", "Şifreyi Unut"),
|
||||
("Favorites", "Favoriler"),
|
||||
("Add to Favorites", "Favorilere ekle"),
|
||||
("Remove from Favorites", "Favorilerden çıkar"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("HSV Color", "HSV Rengi"),
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "登入資訊錯誤"),
|
||||
("The verification code is incorrect or has expired", "驗證碼錯誤或已過期"),
|
||||
("Edit Tag", "編輯標籤"),
|
||||
("Unremember Password", "忘記密碼"),
|
||||
("Forget Password", "忘記密碼"),
|
||||
("Favorites", "我的最愛"),
|
||||
("Add to Favorites", "加入我的最愛"),
|
||||
("Remove from Favorites", "從我的最愛中移除"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("HSV Color", "HSV 色"),
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Неправильні дані"),
|
||||
("The verification code is incorrect or has expired", "Код підтвердження некоректний або протермінований"),
|
||||
("Edit Tag", "Редагувати тег"),
|
||||
("Unremember Password", "Не зберігати пароль"),
|
||||
("Forget Password", "Не зберігати пароль"),
|
||||
("Favorites", "Вибране"),
|
||||
("Add to Favorites", "Додати в обране"),
|
||||
("Remove from Favorites", "Видалити з обраного"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Chứng danh bị sai"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Chỉnh sửa Tag"),
|
||||
("Unremember Password", "Quên mật khẩu"),
|
||||
("Forget Password", "Quên mật khẩu"),
|
||||
("Favorites", "Ưa thích"),
|
||||
("Add to Favorites", "Thêm vào mục Ưa thích"),
|
||||
("Remove from Favorites", "Xóa khỏi mục Ưa thích"),
|
||||
@ -543,5 +543,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -627,8 +627,8 @@ pub fn toggle_blank_screen(_v: bool) {
|
||||
// https://unix.stackexchange.com/questions/17170/disable-keyboard-mouse-input-on-unix-under-x
|
||||
}
|
||||
|
||||
pub fn block_input(_v: bool) -> bool {
|
||||
true
|
||||
pub fn block_input(_v: bool) -> (bool, String) {
|
||||
(true, "".to_owned())
|
||||
}
|
||||
|
||||
pub fn is_installed() -> bool {
|
||||
@ -1074,7 +1074,7 @@ mod desktop {
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
if !self.sid.is_empty() && is_active_and_seat0(&self.sid) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
let seat0_values = get_values_of_seat0(&[0, 1, 2]);
|
||||
@ -1183,6 +1183,7 @@ pub fn uninstall_service(show_new_window: bool) -> bool {
|
||||
}
|
||||
|
||||
pub fn install_service() -> bool {
|
||||
let _installing = crate::platform::InstallingService::new();
|
||||
if !has_cmd("systemctl") {
|
||||
return false;
|
||||
}
|
||||
|
@ -572,8 +572,8 @@ pub fn toggle_blank_screen(_v: bool) {
|
||||
// https://unix.stackexchange.com/questions/17115/disable-keyboard-mouse-temporarily
|
||||
}
|
||||
|
||||
pub fn block_input(_v: bool) -> bool {
|
||||
true
|
||||
pub fn block_input(_v: bool) -> (bool, String) {
|
||||
(true, "".to_owned())
|
||||
}
|
||||
|
||||
pub fn is_installed() -> bool {
|
||||
|
@ -23,9 +23,18 @@ pub mod linux_desktop_manager;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::{message_proto::CursorData, ResultType};
|
||||
use std::sync::{Arc, Mutex};
|
||||
#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))]
|
||||
const SERVICE_INTERVAL: u64 = 300;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref INSTALLING_SERVICE: Arc<Mutex<bool>>= Default::default();
|
||||
}
|
||||
|
||||
pub fn installing_service() -> bool {
|
||||
INSTALLING_SERVICE.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn is_xfce() -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
@ -71,6 +80,21 @@ pub fn get_active_username() -> String {
|
||||
#[cfg(target_os = "android")]
|
||||
pub const PA_SAMPLE_RATE: u32 = 48000;
|
||||
|
||||
pub(crate) struct InstallingService; // please use new
|
||||
|
||||
impl InstallingService {
|
||||
pub fn new() -> Self {
|
||||
*INSTALLING_SERVICE.lock().unwrap() = true;
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InstallingService {
|
||||
fn drop(&mut self) {
|
||||
*INSTALLING_SERVICE.lock().unwrap() = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -1269,9 +1269,15 @@ pub fn toggle_blank_screen(v: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_input(v: bool) -> bool {
|
||||
pub fn block_input(v: bool) -> (bool, String) {
|
||||
let v = if v { TRUE } else { FALSE };
|
||||
unsafe { BlockInput(v) == TRUE }
|
||||
unsafe {
|
||||
if BlockInput(v) == TRUE {
|
||||
(true, "".to_owned())
|
||||
} else {
|
||||
(false, format!("Error code: {}", GetLastError()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_recent_document(path: &str) {
|
||||
@ -2167,6 +2173,7 @@ pub fn uninstall_service(show_new_window: bool) -> bool {
|
||||
|
||||
pub fn install_service() -> bool {
|
||||
log::info!("Installing service...");
|
||||
let _installing = crate::platform::InstallingService::new();
|
||||
let (_, _, _, exe) = get_install_info();
|
||||
let tmp_path = std::env::temp_dir().to_string_lossy().to_string();
|
||||
let tray_shortcut = get_tray_shortcut(&exe, &tmp_path).unwrap_or_default();
|
||||
|
@ -79,7 +79,9 @@ impl RendezvousMediator {
|
||||
crate::platform::linux_desktop_manager::start_xdesktop();
|
||||
loop {
|
||||
Config::reset_online();
|
||||
if Config::get_option("stop-service").is_empty() {
|
||||
if Config::get_option("stop-service").is_empty()
|
||||
&& !crate::platform::installing_service()
|
||||
{
|
||||
if !nat_tested {
|
||||
crate::test_nat_type();
|
||||
nat_tested = true;
|
||||
|
@ -204,6 +204,7 @@ pub struct Connection {
|
||||
delay_response_instant: Instant,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
start_cm_ipc_para: Option<StartCmIpcPara>,
|
||||
auto_disconnect_timer: Option<(Instant, u64)>,
|
||||
}
|
||||
|
||||
impl ConnInner {
|
||||
@ -343,6 +344,7 @@ impl Connection {
|
||||
rx_desktop_ready,
|
||||
tx_cm_stream_ready,
|
||||
}),
|
||||
auto_disconnect_timer: None,
|
||||
};
|
||||
let addr = hbb_common::try_into_v4(addr);
|
||||
if !conn.on_open(addr).await {
|
||||
@ -469,11 +471,6 @@ impl Connection {
|
||||
back_notification::PrivacyModeState::PrvOffSucceeded,
|
||||
)
|
||||
}
|
||||
ipc::PrivacyModeState::OffFailed => {
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::PrvOffFailed,
|
||||
)
|
||||
}
|
||||
ipc::PrivacyModeState::OffByPeer => {
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
@ -605,6 +602,13 @@ impl Connection {
|
||||
_ = second_timer.tick() => {
|
||||
#[cfg(windows)]
|
||||
conn.portable_check();
|
||||
if let Some((instant, minute)) = conn.auto_disconnect_timer.as_ref() {
|
||||
if instant.elapsed().as_secs() > minute * 60 {
|
||||
conn.send_close_reason_no_retry("Connection failed due to inactivity").await;
|
||||
conn.on_close("auto disconnect", true).await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = test_delay_timer.tick() => {
|
||||
if last_recv_time.elapsed() >= SEC30 {
|
||||
@ -667,10 +671,10 @@ impl Connection {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn handle_input(receiver: std_mpsc::Receiver<MessageInput>, tx: Sender) {
|
||||
let mut block_input_mode = false;
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
{
|
||||
rdev::set_dw_mouse_extra_info(enigo::ENIGO_INPUT_EXTRA_VALUE);
|
||||
rdev::set_dw_keyboard_extra_info(enigo::ENIGO_INPUT_EXTRA_VALUE);
|
||||
rdev::set_mouse_extra_info(enigo::ENIGO_INPUT_EXTRA_VALUE);
|
||||
rdev::set_keyboard_extra_info(enigo::ENIGO_INPUT_EXTRA_VALUE);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
reset_input_ondisconn();
|
||||
@ -695,29 +699,34 @@ impl Connection {
|
||||
handle_pointer(&msg, id);
|
||||
}
|
||||
MessageInput::BlockOn => {
|
||||
if crate::platform::block_input(true) {
|
||||
let (ok, msg) = crate::platform::block_input(true);
|
||||
if ok {
|
||||
block_input_mode = true;
|
||||
} else {
|
||||
Self::send_block_input_error(
|
||||
&tx,
|
||||
back_notification::BlockInputState::BlkOnFailed,
|
||||
msg,
|
||||
);
|
||||
}
|
||||
}
|
||||
MessageInput::BlockOff => {
|
||||
if crate::platform::block_input(false) {
|
||||
let (ok, msg) = crate::platform::block_input(false);
|
||||
if ok {
|
||||
block_input_mode = false;
|
||||
} else {
|
||||
Self::send_block_input_error(
|
||||
&tx,
|
||||
back_notification::BlockInputState::BlkOffFailed,
|
||||
msg,
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
MessageInput::BlockOnPlugin(_peer) => {
|
||||
if crate::platform::block_input(true) {
|
||||
let (ok, _msg) = crate::platform::block_input(true);
|
||||
if ok {
|
||||
block_input_mode = true;
|
||||
}
|
||||
let _r = PLUGIN_BLOCK_INPUT_TX_RX
|
||||
@ -729,7 +738,8 @@ impl Connection {
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
MessageInput::BlockOffPlugin(_peer) => {
|
||||
if crate::platform::block_input(false) {
|
||||
let (ok, _msg) = crate::platform::block_input(false);
|
||||
if ok {
|
||||
block_input_mode = false;
|
||||
}
|
||||
let _r = PLUGIN_BLOCK_INPUT_TX_RX
|
||||
@ -1139,6 +1149,7 @@ impl Connection {
|
||||
let mut s = s.write().unwrap();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let _h = try_start_record_cursor_pos();
|
||||
self.auto_disconnect_timer = Self::get_auto_disconenct_timer();
|
||||
s.add_connection(self.inner.clone(), &noperms);
|
||||
}
|
||||
}
|
||||
@ -1199,9 +1210,16 @@ impl Connection {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn send_block_input_error(s: &Sender, state: back_notification::BlockInputState) {
|
||||
pub fn send_block_input_error(
|
||||
s: &Sender,
|
||||
state: back_notification::BlockInputState,
|
||||
details: String,
|
||||
) {
|
||||
let mut misc = Misc::new();
|
||||
let mut back_notification = BackNotification::new();
|
||||
let mut back_notification = BackNotification {
|
||||
details,
|
||||
..Default::default()
|
||||
};
|
||||
back_notification.set_block_input_state(state);
|
||||
misc.set_back_notification(back_notification);
|
||||
let mut msg_out = Message::new();
|
||||
@ -1351,6 +1369,12 @@ impl Connection {
|
||||
log::error!("ipc to connection manager exit: {}", err);
|
||||
}
|
||||
});
|
||||
#[cfg(all(windows, feature = "flutter"))]
|
||||
std::thread::spawn(|| {
|
||||
if crate::is_server() && !crate::check_process("--tray", false) {
|
||||
crate::platform::run_as_user(vec!["--tray"]).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1606,6 +1630,7 @@ impl Connection {
|
||||
}
|
||||
self.input_mouse(me, self.inner.id());
|
||||
}
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
Some(message::Union::PointerDeviceEvent(pde)) => {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
@ -1641,6 +1666,7 @@ impl Connection {
|
||||
MOUSE_MOVE_TIME.store(get_time(), Ordering::SeqCst);
|
||||
self.input_pointer(pde, self.inner.id());
|
||||
}
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
Some(message::Union::KeyEvent(..)) => {}
|
||||
@ -1696,6 +1722,7 @@ impl Connection {
|
||||
self.input_key(me, false);
|
||||
}
|
||||
}
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
Some(message::Union::Clipboard(_cb)) =>
|
||||
{
|
||||
@ -1884,6 +1911,7 @@ impl Connection {
|
||||
Some(misc::Union::ChatMessage(c)) => {
|
||||
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
|
||||
self.chat_unanswered = true;
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
Some(misc::Union::Option(o)) => {
|
||||
self.update_options(&o).await;
|
||||
@ -1892,6 +1920,7 @@ impl Connection {
|
||||
if r {
|
||||
super::video_service::refresh();
|
||||
}
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
Some(misc::Union::VideoReceived(_)) => {
|
||||
video_service::notify_video_frame_fetched(
|
||||
@ -2021,6 +2050,7 @@ impl Connection {
|
||||
let mut msg = Message::new();
|
||||
msg.set_misc(misc);
|
||||
self.send(msg).await;
|
||||
self.update_auto_disconnect_timer();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@ -2199,13 +2229,16 @@ impl Connection {
|
||||
match q {
|
||||
BoolOption::Yes => {
|
||||
let msg_out = if !video_service::is_privacy_mode_supported() {
|
||||
crate::common::make_privacy_mode_msg(
|
||||
crate::common::make_privacy_mode_msg_with_details(
|
||||
back_notification::PrivacyModeState::PrvNotSupported,
|
||||
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
|
||||
)
|
||||
} else {
|
||||
match privacy_mode::turn_on_privacy(self.inner.id) {
|
||||
Ok(true) => {
|
||||
if video_service::test_create_capturer(self.inner.id, 5_000) {
|
||||
let err_msg =
|
||||
video_service::test_create_capturer(self.inner.id, 5_000);
|
||||
if err_msg.is_empty() {
|
||||
video_service::set_privacy_mode_conn_id(self.inner.id);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
back_notification::PrivacyModeState::PrvOnSucceeded,
|
||||
@ -2216,8 +2249,9 @@ impl Connection {
|
||||
);
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
let _ = privacy_mode::turn_off_privacy(self.inner.id);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
crate::common::make_privacy_mode_msg_with_details(
|
||||
back_notification::PrivacyModeState::PrvOnFailed,
|
||||
err_msg,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -2229,8 +2263,9 @@ impl Connection {
|
||||
if video_service::get_privacy_mode_conn_id() == 0 {
|
||||
let _ = privacy_mode::turn_off_privacy(0);
|
||||
}
|
||||
crate::common::make_privacy_mode_msg(
|
||||
crate::common::make_privacy_mode_msg_with_details(
|
||||
back_notification::PrivacyModeState::PrvOnFailed,
|
||||
e.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -2239,8 +2274,9 @@ impl Connection {
|
||||
}
|
||||
BoolOption::No => {
|
||||
let msg_out = if !video_service::is_privacy_mode_supported() {
|
||||
crate::common::make_privacy_mode_msg(
|
||||
crate::common::make_privacy_mode_msg_with_details(
|
||||
back_notification::PrivacyModeState::PrvNotSupported,
|
||||
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
|
||||
)
|
||||
} else {
|
||||
video_service::set_privacy_mode_conn_id(0);
|
||||
@ -2278,7 +2314,7 @@ impl Connection {
|
||||
lock_screen().await;
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let data = if self.chat_unanswered || self.file_transferred {
|
||||
let data = if self.chat_unanswered || self.file_transferred && cfg!(feature = "flutter") {
|
||||
ipc::Data::Disconnected
|
||||
} else {
|
||||
ipc::Data::Close
|
||||
@ -2378,6 +2414,26 @@ impl Connection {
|
||||
}
|
||||
self.pressed_modifiers.clear();
|
||||
}
|
||||
|
||||
fn get_auto_disconenct_timer() -> Option<(Instant, u64)> {
|
||||
if Config::get_option("allow-auto-disconnect") == "Y" {
|
||||
let mut minute: u64 = Config::get_option("auto-disconnect-timeout")
|
||||
.parse()
|
||||
.unwrap_or(10);
|
||||
if minute == 0 {
|
||||
minute = 10;
|
||||
}
|
||||
Some((Instant::now(), minute))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn update_auto_disconnect_timer(&mut self) {
|
||||
self.auto_disconnect_timer
|
||||
.as_mut()
|
||||
.map(|t| t.0 = Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
@ -2406,6 +2462,8 @@ async fn start_ipc(
|
||||
if let Ok(s) = crate::ipc::connect(1000, "_cm").await {
|
||||
stream = Some(s);
|
||||
} else {
|
||||
#[allow(unused_mut)]
|
||||
#[allow(unused_assignments)]
|
||||
let mut args = vec!["--cm"];
|
||||
if crate::hbbs_http::sync::is_pro() && password::hide_cm() {
|
||||
args.push("--hide");
|
||||
@ -2553,8 +2611,9 @@ mod privacy_mode {
|
||||
),
|
||||
Err(e) => {
|
||||
log::error!("Failed to turn off privacy mode {}", e);
|
||||
crate::common::make_privacy_mode_msg(
|
||||
crate::common::make_privacy_mode_msg_with_details(
|
||||
back_notification::PrivacyModeState::PrvOffFailed,
|
||||
e.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -317,17 +317,23 @@ fn create_capturer(
|
||||
}
|
||||
|
||||
// This function works on privacy mode. Windows only for now.
|
||||
pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
|
||||
pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> String {
|
||||
let test_begin = Instant::now();
|
||||
while test_begin.elapsed().as_millis() < timeout_millis as _ {
|
||||
if let Ok((_, current, display)) = get_current_display() {
|
||||
if let Ok(_) = create_capturer(privacy_mode_id, display, true, current, false) {
|
||||
return true;
|
||||
loop {
|
||||
let err = match get_current_display() {
|
||||
Ok((_, current, display)) => {
|
||||
match create_capturer(privacy_mode_id, display, true, current, false) {
|
||||
Ok(_) => return "".to_owned(),
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
Err(e) => e,
|
||||
};
|
||||
if test_begin.elapsed().as_millis() >= timeout_millis as _ {
|
||||
return err.to_string();
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@ -1025,7 +1031,7 @@ fn try_get_displays() -> ResultType<Vec<Display>> {
|
||||
// displays = Display::all()?;
|
||||
// }
|
||||
// }
|
||||
Ok( Display::all()?)
|
||||
Ok(Display::all()?)
|
||||
}
|
||||
|
||||
pub(super) fn get_current_display_2(mut all: Vec<Display>) -> ResultType<(usize, usize, Display)> {
|
||||
|
97
src/tray.rs
97
src/tray.rs
@ -1,5 +1,11 @@
|
||||
use crate::{client::translate, ipc::Data};
|
||||
use hbb_common::{allow_err, log, tokio};
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub fn start_tray() {
|
||||
use hbb_common::{allow_err, log};
|
||||
allow_err!(make_tray());
|
||||
}
|
||||
|
||||
@ -40,29 +46,42 @@ pub fn make_tray() -> hbb_common::ResultType<()> {
|
||||
let event_loop = EventLoopBuilder::new().build();
|
||||
|
||||
let tray_menu = Menu::new();
|
||||
let quit_i = MenuItem::new(crate::client::translate("Exit".to_owned()), true, None);
|
||||
let open_i = MenuItem::new(crate::client::translate("Open".to_owned()), true, None);
|
||||
let quit_i = MenuItem::new(translate("Exit".to_owned()), true, None);
|
||||
let open_i = MenuItem::new(translate("Open".to_owned()), true, None);
|
||||
tray_menu.append_items(&[&open_i, &quit_i]);
|
||||
|
||||
let _tray_icon = Some(
|
||||
TrayIconBuilder::new()
|
||||
.with_menu(Box::new(tray_menu))
|
||||
.with_tooltip(format!(
|
||||
let tooltip = |count: usize| {
|
||||
if count == 0 {
|
||||
format!(
|
||||
"{} {}",
|
||||
crate::get_app_name(),
|
||||
crate::lang::translate("Service is running".to_owned())
|
||||
))
|
||||
translate("Service is running".to_owned()),
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{} - {}\n{}",
|
||||
crate::get_app_name(),
|
||||
translate("Ready".to_owned()),
|
||||
translate("{".to_string() + &format!("{count}") + "} sessions"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let tray_icon = Some(
|
||||
TrayIconBuilder::new()
|
||||
.with_menu(Box::new(tray_menu))
|
||||
.with_tooltip(tooltip(0))
|
||||
.with_icon(icon)
|
||||
.build()?,
|
||||
);
|
||||
let tray_icon = Arc::new(Mutex::new(tray_icon));
|
||||
|
||||
let menu_channel = MenuEvent::receiver();
|
||||
let tray_channel = TrayEvent::receiver();
|
||||
#[cfg(windows)]
|
||||
let (ipc_sender, ipc_receiver) = std::sync::mpsc::channel::<Data>();
|
||||
let mut docker_hiden = false;
|
||||
|
||||
let open_func = move || {
|
||||
if cfg!(not(feature = "flutter"))
|
||||
{
|
||||
if cfg!(not(feature = "flutter")) {
|
||||
crate::run_me::<&str>(vec![]).ok();
|
||||
return;
|
||||
}
|
||||
@ -89,6 +108,10 @@ pub fn make_tray() -> hbb_common::ResultType<()> {
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
std::thread::spawn(move || {
|
||||
start_query_session_count(ipc_sender.clone());
|
||||
});
|
||||
event_loop.run(move |_event, _, control_flow| {
|
||||
if !docker_hiden {
|
||||
#[cfg(target_os = "macos")]
|
||||
@ -121,5 +144,55 @@ pub fn make_tray() -> hbb_common::ResultType<()> {
|
||||
open_func();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if let Ok(data) = ipc_receiver.try_recv() {
|
||||
match data {
|
||||
Data::ControlledSessionCount(count) => {
|
||||
tray_icon
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.map(|t| t.set_tooltip(Some(tooltip(count))));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start_query_session_count(sender: std::sync::mpsc::Sender<Data>) {
|
||||
let mut last_count = 0;
|
||||
loop {
|
||||
if let Ok(mut c) = crate::ipc::connect(1000, "").await {
|
||||
let mut timer = tokio::time::interval(Duration::from_secs(1));
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = c.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::error!("ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(Some(Data::ControlledSessionCount(count))) => {
|
||||
if count != last_count {
|
||||
last_count = count;
|
||||
sender.send(Data::ControlledSessionCount(count)).ok();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
_ = timer.tick() => {
|
||||
c.send(&Data::ControlledSessionCount(0)).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hbb_common::sleep(1.).await;
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +334,7 @@ class SessionList: Reactor.Component {
|
||||
{this.type != "lan" && <li #rename>{translate('Rename')}</li>}
|
||||
{this.type != "fav" && <li #remove>{translate('Remove')}</li>}
|
||||
{is_win && <li #shortcut>{translate('Create Desktop Shortcut')}</li>}
|
||||
<li #forget-password>{translate('Unremember Password')}</li>
|
||||
<li #forget-password>{translate('Forget Password')}</li>
|
||||
{(!this.type || this.type == "fav") && <li #add-fav>{translate('Add to Favorites')}</li>}
|
||||
{(!this.type || this.type == "fav") && <li #remove-fav>{translate('Remove from Favorites')}</li>}
|
||||
{this.type == "ab" && <li #edit-tag>{translate('Edit Tag')}</li>}
|
||||
|
@ -409,8 +409,8 @@ impl sciter::EventHandler for SciterSession {
|
||||
fn login(String, String, String, bool);
|
||||
fn new_rdp();
|
||||
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
|
||||
fn enter();
|
||||
fn leave();
|
||||
fn enter(String);
|
||||
fn leave(String);
|
||||
fn ctrl_alt_del();
|
||||
fn transfer_file();
|
||||
fn tunnel();
|
||||
|
@ -273,12 +273,12 @@ function handler.onMouse(evt)
|
||||
case Event.MOUSE_ENTER:
|
||||
entered = true;
|
||||
stdout.println("enter");
|
||||
handler.enter();
|
||||
handler.enter(handler.get_keyboard_mode());
|
||||
return keyboard_enabled;
|
||||
case Event.MOUSE_LEAVE:
|
||||
entered = false;
|
||||
stdout.println("leave");
|
||||
handler.leave();
|
||||
handler.leave(handler.get_keyboard_mode());
|
||||
if (is_left_down && handler.peer_platform() == "Android") {
|
||||
is_left_down = false;
|
||||
handler.send_mouse((1 << 3) | 2, 0, 0, evt.altKey,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::input::{MOUSE_BUTTON_LEFT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP};
|
||||
use crate::input::{MOUSE_BUTTON_LEFT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP, MOUSE_TYPE_WHEEL};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use std::{collections::HashMap, sync::atomic::AtomicBool};
|
||||
use std::{
|
||||
@ -168,13 +168,31 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
|
||||
pub fn get_keyboard_mode(&self) -> String {
|
||||
self.lc.read().unwrap().keyboard_mode.clone()
|
||||
let mode = self.lc.read().unwrap().keyboard_mode.clone();
|
||||
if ["map", "translate", "legacy"].contains(&(&mode as &str)) {
|
||||
mode
|
||||
} else {
|
||||
if self.get_peer_version() > hbb_common::get_version_number("1.2.0") {
|
||||
"map"
|
||||
} else {
|
||||
"legacy"
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_keyboard_mode(&mut self, value: String) {
|
||||
self.lc.write().unwrap().save_keyboard_mode(value);
|
||||
}
|
||||
|
||||
pub fn get_reverse_mouse_wheel(&self) -> String {
|
||||
self.lc.read().unwrap().reverse_mouse_wheel.clone()
|
||||
}
|
||||
|
||||
pub fn save_reverse_mouse_wheel(&mut self, value: String) {
|
||||
self.lc.write().unwrap().save_reverse_mouse_wheel(value);
|
||||
}
|
||||
|
||||
pub fn save_view_style(&mut self, value: String) {
|
||||
self.lc.write().unwrap().save_view_style(value);
|
||||
}
|
||||
@ -554,28 +572,15 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn enter(&self) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
match &self.lc.read().unwrap().keyboard_mode as _ {
|
||||
"legacy" => rdev::set_get_key_unicode(true),
|
||||
"translate" => rdev::set_get_key_unicode(true),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enter(&self, keyboard_mode: String) {
|
||||
IS_IN.store(true, Ordering::SeqCst);
|
||||
keyboard::client::change_grab_status(GrabState::Run);
|
||||
keyboard::client::change_grab_status(GrabState::Run, &keyboard_mode);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn leave(&self) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
rdev::set_get_key_unicode(false);
|
||||
}
|
||||
pub fn leave(&self, keyboard_mode: String) {
|
||||
IS_IN.store(false, Ordering::SeqCst);
|
||||
keyboard::client::change_grab_status(GrabState::Wait);
|
||||
keyboard::client::change_grab_status(GrabState::Wait, &keyboard_mode);
|
||||
}
|
||||
|
||||
// flutter only TODO new input
|
||||
@ -612,17 +617,19 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
#[cfg(any(target_os = "ios"))]
|
||||
pub fn handle_flutter_key_event(
|
||||
&self,
|
||||
_keyboard_mode: &str,
|
||||
_name: &str,
|
||||
platform_code: i32,
|
||||
position_code: i32,
|
||||
lock_modes: i32,
|
||||
down_or_up: bool,
|
||||
_platform_code: i32,
|
||||
_position_code: i32,
|
||||
_lock_modes: i32,
|
||||
_down_or_up: bool,
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub fn handle_flutter_key_event(
|
||||
&self,
|
||||
keyboard_mode: &str,
|
||||
_name: &str,
|
||||
platform_code: i32,
|
||||
position_code: i32,
|
||||
@ -652,8 +659,10 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
platform_code,
|
||||
position_code: position_code as _,
|
||||
event_type,
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
extra_data: 0,
|
||||
};
|
||||
keyboard::client::process_event(&event, Some(lock_modes));
|
||||
keyboard::client::process_event(keyboard_mode, &event, Some(lock_modes));
|
||||
}
|
||||
|
||||
// flutter only TODO new input
|
||||
@ -730,6 +739,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
});
|
||||
}
|
||||
"pan_update" => {
|
||||
let (x, y) = self.get_scroll_xy((x, y));
|
||||
touch_evt.set_pan_update(TouchPanUpdate {
|
||||
x,
|
||||
y,
|
||||
@ -753,6 +763,21 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
send_pointer_device_event(evt, alt, ctrl, shift, command, self);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn is_scroll_reverse_mode(&self) -> bool {
|
||||
self.lc.read().unwrap().reverse_mouse_wheel.eq("Y")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_scroll_xy(&self, xy: (i32, i32)) -> (i32, i32) {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.is_scroll_reverse_mode() {
|
||||
return (-xy.0, -xy.1);
|
||||
}
|
||||
xy
|
||||
}
|
||||
|
||||
pub fn send_mouse(
|
||||
&self,
|
||||
mask: i32,
|
||||
@ -772,6 +797,12 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
let (x, y) = if mask == MOUSE_TYPE_WHEEL {
|
||||
self.get_scroll_xy((x, y))
|
||||
} else {
|
||||
(x, y)
|
||||
};
|
||||
|
||||
// #[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let (alt, ctrl, shift, command) =
|
||||
keyboard::client::get_modifiers_state(alt, ctrl, shift, command);
|
||||
|
Loading…
x
Reference in New Issue
Block a user